diff --git a/SafeExamBrowser.Configuration/Settings/Settings.cs b/SafeExamBrowser.Configuration/Settings/Settings.cs
index f7b8b67a..25159977 100644
--- a/SafeExamBrowser.Configuration/Settings/Settings.cs
+++ b/SafeExamBrowser.Configuration/Settings/Settings.cs
@@ -15,6 +15,7 @@ namespace SafeExamBrowser.Configuration.Settings
internal class Settings : ISettings
{
public ConfigurationMode ConfigurationMode { get; set; }
+ public KioskMode KioskMode { get; set; }
public ServicePolicy ServicePolicy { get; set; }
public IBrowserSettings Browser { get; set; }
diff --git a/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs b/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs
index 675b1776..4b8e7c3e 100644
--- a/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs
+++ b/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs
@@ -14,5 +14,10 @@ namespace SafeExamBrowser.Contracts.Behaviour
/// Reverts any changes performed during the startup or runtime and releases all used resources.
///
void FinalizeApplication();
+
+ ///
+ /// Initializes a new session and starts performing the runtime logic / event handling.
+ ///
+ void StartSession();
}
}
diff --git a/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs b/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs
index 21dbda4e..81aa6b93 100644
--- a/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs
+++ b/SafeExamBrowser.Contracts/Configuration/Settings/ISettings.cs
@@ -25,13 +25,18 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
///
IKeyboardSettings Keyboard { get; }
+ ///
+ /// The kiosk mode which determines how the computer is locked down.
+ ///
+ KioskMode KioskMode { get; }
+
///
/// All mouse-related settings.
///
IMouseSettings Mouse { get; }
///
- /// The active service policy.
+ /// The active policy for the service component.
///
ServicePolicy ServicePolicy { get; }
diff --git a/SafeExamBrowser.Contracts/Configuration/Settings/KioskMode.cs b/SafeExamBrowser.Contracts/Configuration/Settings/KioskMode.cs
new file mode 100644
index 00000000..5c3da31c
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Configuration/Settings/KioskMode.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+namespace SafeExamBrowser.Contracts.Configuration.Settings
+{
+ public enum KioskMode
+ {
+ ///
+ /// No kiosk mode - should only be used for testing / debugging.
+ ///
+ None,
+
+ ///
+ /// Creates a new desktop and runs the client application on it, without modifying the default desktop.
+ ///
+ CreateNewDesktop,
+
+ ///
+ /// Terminates the Windows explorer shell and runs the client application on the default desktop.
+ ///
+ DisableExplorerShell
+ }
+}
diff --git a/SafeExamBrowser.Contracts/I18n/TextKey.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs
index 93fbfb11..8546c53f 100644
--- a/SafeExamBrowser.Contracts/I18n/TextKey.cs
+++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs
@@ -26,16 +26,20 @@ namespace SafeExamBrowser.Contracts.I18n
Notification_AboutTooltip,
Notification_LogTooltip,
RuntimeWindow_ApplicationRunning,
+ RuntimeWindow_StartSession,
+ RuntimeWindow_StopSession,
SplashScreen_CloseServiceConnection,
SplashScreen_EmptyClipboard,
SplashScreen_InitializeBrowser,
SplashScreen_InitializeConfiguration,
+ SplashScreen_InitializeKioskMode,
SplashScreen_InitializeProcessMonitoring,
SplashScreen_InitializeServiceConnection,
SplashScreen_InitializeTaskbar,
SplashScreen_InitializeWindowMonitoring,
SplashScreen_InitializeWorkingArea,
SplashScreen_RestoreWorkingArea,
+ SplashScreen_RevertKioskMode,
SplashScreen_ShutdownProcedure,
SplashScreen_StartEventHandling,
SplashScreen_StartKeyboardInterception,
diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
index f0e3a0e0..612cea5a 100644
--- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
+++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
@@ -81,6 +81,7 @@
+
diff --git a/SafeExamBrowser.Contracts/UserInterface/IRuntimeWindow.cs b/SafeExamBrowser.Contracts/UserInterface/IRuntimeWindow.cs
index bfb6ee9b..eae421e4 100644
--- a/SafeExamBrowser.Contracts/UserInterface/IRuntimeWindow.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/IRuntimeWindow.cs
@@ -7,15 +7,16 @@
*/
using SafeExamBrowser.Contracts.I18n;
+using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Contracts.UserInterface
{
- public interface IRuntimeWindow : IWindow
+ public interface IRuntimeWindow : ILogObserver, IWindow
{
///
/// Updates the status text of the runtime window. If the busy flag is set,
/// the window will show an animation to indicate a long-running operation.
///
- void UpdateStatus(TextKey key);
+ void UpdateStatus(TextKey key, bool showBusyIndication = false);
}
}
diff --git a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs
index 93a1a1c4..4b04e592 100644
--- a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs
@@ -10,18 +10,8 @@ using SafeExamBrowser.Contracts.I18n;
namespace SafeExamBrowser.Contracts.UserInterface
{
- public interface ISplashScreen
+ public interface ISplashScreen : IWindow
{
- ///
- /// Closes the splash screen on its own thread.
- ///
- void InvokeClose();
-
- ///
- /// Shows the splash screen on its own thread.
- ///
- void InvokeShow();
-
///
/// Updates the progress bar of the splash screen according to the specified amount.
///
diff --git a/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs b/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs
index 33ab4511..c5ce81f3 100644
--- a/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs
@@ -51,6 +51,12 @@ namespace SafeExamBrowser.Contracts.UserInterface
///
ISystemPowerSupplyControl CreatePowerSupplyControl();
+ ///
+ /// Creates a new runtime window which runs on its own thread.
+ ///
+ ///
+ IRuntimeWindow CreateRuntimeWindow(IRuntimeInfo runtimeInfo, IText text);
+
///
/// Creates a new splash screen which runs on its own thread.
///
diff --git a/SafeExamBrowser.Contracts/UserInterface/IWindow.cs b/SafeExamBrowser.Contracts/UserInterface/IWindow.cs
index 69b37802..813e262d 100644
--- a/SafeExamBrowser.Contracts/UserInterface/IWindow.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/IWindow.cs
@@ -27,6 +27,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
///
void Close();
+ ///
+ /// Hides the window.
+ ///
+ void Hide();
+
///
/// Shows the window to the user.
///
diff --git a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs
index 91c6799b..0c38c73e 100644
--- a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs
+++ b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs
@@ -72,7 +72,7 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text);
splashScreen.SetIndeterminate();
splashScreen.UpdateText(TextKey.SplashScreen_ShutdownProcedure);
- splashScreen.InvokeShow();
+ splashScreen.Show();
}
private void LogAndShowException(Exception e)
@@ -92,7 +92,7 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("--- Shutdown procedure failed! ---");
}
- splashScreen?.InvokeClose();
+ splashScreen?.Close();
}
}
}
diff --git a/SafeExamBrowser.Core/Behaviour/StartupController.cs b/SafeExamBrowser.Core/Behaviour/StartupController.cs
index d4bad03f..881bbc14 100644
--- a/SafeExamBrowser.Core/Behaviour/StartupController.cs
+++ b/SafeExamBrowser.Core/Behaviour/StartupController.cs
@@ -117,7 +117,7 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text);
splashScreen.SetMaxProgress(operationCount);
splashScreen.UpdateText(TextKey.SplashScreen_StartupProcedure);
- splashScreen.InvokeShow();
+ splashScreen.Show();
}
private void LogAndShowException(Exception e)
@@ -139,7 +139,7 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("--- Startup procedure aborted! ---");
}
- splashScreen?.InvokeClose();
+ splashScreen?.Close();
}
}
}
diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml
index 323e7e30..5755e73a 100644
--- a/SafeExamBrowser.Core/I18n/Text.xml
+++ b/SafeExamBrowser.Core/I18n/Text.xml
@@ -33,6 +33,12 @@
The application is running.
+
+ Starting new session
+
+
+ Stopping current session
+
Closing service connection
@@ -45,6 +51,9 @@
Initializing application configuration
+
+ Initializing kiosk mode
+
Initializing process monitoring
@@ -63,6 +72,9 @@
Restoring working area
+
+ Reverting kiosk mode
+
Initiating shutdown procedure
diff --git a/SafeExamBrowser.Runtime/App.cs b/SafeExamBrowser.Runtime/App.cs
index 65e98b1f..292d35e8 100644
--- a/SafeExamBrowser.Runtime/App.cs
+++ b/SafeExamBrowser.Runtime/App.cs
@@ -7,7 +7,6 @@
*/
using System;
-using System.ComponentModel;
using System.Threading;
using System.Windows;
@@ -56,6 +55,8 @@ namespace SafeExamBrowser.Runtime
{
base.OnStartup(e);
+ ShutdownMode = ShutdownMode.OnExplicitShutdown;
+
instances.BuildObjectGraph();
instances.LogStartupInformation();
@@ -63,9 +64,7 @@ namespace SafeExamBrowser.Runtime
if (success)
{
- MainWindow = instances.RuntimeWindow;
- MainWindow.Closing += MainWindow_Closing;
- MainWindow.Show();
+ instances.RuntimeController.StartSession();
}
else
{
@@ -75,15 +74,10 @@ namespace SafeExamBrowser.Runtime
protected override void OnExit(ExitEventArgs e)
{
+ instances.RuntimeController.FinalizeApplication();
instances.LogShutdownInformation();
base.OnExit(e);
}
-
- private void MainWindow_Closing(object sender, CancelEventArgs e)
- {
- MainWindow.Hide();
- instances.RuntimeController.FinalizeApplication();
- }
}
}
diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs
index 9e0c917b..8014d41d 100644
--- a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs
+++ b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs
@@ -6,25 +6,79 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-using System;
using SafeExamBrowser.Contracts.Behaviour;
+using SafeExamBrowser.Contracts.Configuration.Settings;
+using SafeExamBrowser.Contracts.I18n;
+using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
internal class KioskModeOperation : IOperation
{
+ private ILogger logger;
+ private ISettingsRepository settingsRepository;
+ private KioskMode kioskMode;
+
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
+ public KioskModeOperation(ILogger logger, ISettingsRepository settingsRepository)
+ {
+ this.logger = logger;
+ this.settingsRepository = settingsRepository;
+ }
+
public void Perform()
{
- // TODO
+ kioskMode = settingsRepository.Current.KioskMode;
+
+ logger.Info($"Initializing kiosk mode '{kioskMode}'...");
+ SplashScreen.UpdateText(TextKey.SplashScreen_InitializeKioskMode);
+
+ if (kioskMode == KioskMode.CreateNewDesktop)
+ {
+ CreateNewDesktop();
+ }
+ else
+ {
+ DisableExplorerShell();
+ }
}
public void Revert()
{
- // TODO
+ logger.Info($"Reverting kiosk mode '{kioskMode}'...");
+ SplashScreen.UpdateText(TextKey.SplashScreen_RevertKioskMode);
+
+ if (kioskMode == KioskMode.CreateNewDesktop)
+ {
+ CloseNewDesktop();
+ }
+ else
+ {
+ RestartExplorerShell();
+ }
+ }
+
+ private void CreateNewDesktop()
+ {
+
+ }
+
+ private void CloseNewDesktop()
+ {
+
+ }
+
+ private void DisableExplorerShell()
+ {
+
+ }
+
+ private void RestartExplorerShell()
+ {
+
}
}
}
diff --git a/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs b/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs
index ba7841af..e5bfd7c2 100644
--- a/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs
+++ b/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs
@@ -6,10 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System;
using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Communication;
+using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
@@ -19,66 +21,107 @@ namespace SafeExamBrowser.Runtime.Behaviour
{
internal class RuntimeController : IRuntimeController
{
- private ICommunication serviceProxy;
private Queue operations;
private ILogger logger;
+ private IRuntimeInfo runtimeInfo;
private IRuntimeWindow runtimeWindow;
+ private IServiceProxy serviceProxy;
private ISettingsRepository settingsRepository;
private IShutdownController shutdownController;
private IStartupController startupController;
-
- public ISettings Settings { private get; set; }
+ private Action terminationCallback;
+ private IText text;
+ private IUserInterfaceFactory uiFactory;
public RuntimeController(
- ICommunication serviceProxy,
ILogger logger,
- IRuntimeWindow runtimeWindow,
+ IRuntimeInfo runtimeInfo,
+ IServiceProxy serviceProxy,
ISettingsRepository settingsRepository,
IShutdownController shutdownController,
- IStartupController startupController)
+ IStartupController startupController,
+ Action terminationCallback,
+ IText text,
+ IUserInterfaceFactory uiFactory)
{
- this.serviceProxy = serviceProxy;
this.logger = logger;
- this.runtimeWindow = runtimeWindow;
+ this.runtimeInfo = runtimeInfo;
+ this.serviceProxy = serviceProxy;
this.settingsRepository = settingsRepository;
this.shutdownController = shutdownController;
this.startupController = startupController;
+ this.terminationCallback = terminationCallback;
+ this.text = text;
+ this.uiFactory = uiFactory;
operations = new Queue();
}
public bool TryInitializeApplication(Queue operations)
{
- operations = new Queue(operations);
-
var success = startupController.TryInitializeApplication(operations);
+ runtimeWindow = uiFactory.CreateRuntimeWindow(runtimeInfo, text);
+
if (success)
{
- Start();
+ this.operations = new Queue(operations);
+ logger.Subscribe(runtimeWindow);
}
return success;
}
+ public void StartSession()
+ {
+ runtimeWindow.Show();
+
+ logger.Info("Starting new session...");
+ runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_StartSession, true);
+
+ // TODO:
+ // - Initialize configuration
+ // - Initialize kiosk mode
+ // - Initialize session data
+ // - Start runtime communication host
+ // - Create and connect to client
+ // - Initialize session with service
+ // - Verify session integrity and start event handling
+ System.Threading.Thread.Sleep(10000);
+
+ runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning);
+
+ if (settingsRepository.Current.KioskMode == KioskMode.DisableExplorerShell)
+ {
+ runtimeWindow.Hide();
+ }
+ }
+
public void FinalizeApplication()
{
- Stop();
+ StopSession();
+
+ // TODO:
+ // - Disconnect from service
+ // - Terminate runtime communication host
+ // - Revert kiosk mode (or do that when stopping session?)
+
+ logger.Unsubscribe(runtimeWindow);
+ runtimeWindow.Close();
shutdownController.FinalizeApplication(new Queue(operations.Reverse()));
}
- private void Start()
+ private void StopSession()
{
- logger.Info("Starting event handling...");
- // TODO SplashScreen.UpdateText(TextKey.SplashScreen_StartEventHandling);
+ logger.Info("Stopping current session...");
+ runtimeWindow.Show();
+ runtimeWindow.BringToForeground();
+ runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_StopSession, true);
- runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning);
- }
-
- private void Stop()
- {
- logger.Info("Stopping event handling...");
- // TODO SplashScreen.UpdateText(TextKey.SplashScreen_StopEventHandling);
+ // TODO:
+ // - Terminate client (or does it terminate itself?)
+ // - Finalize session with service
+ // - Stop event handling and close session
}
}
}
diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs
index 56e9285d..0c58fc06 100644
--- a/SafeExamBrowser.Runtime/CompositionRoot.cs
+++ b/SafeExamBrowser.Runtime/CompositionRoot.cs
@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+using System.Windows;
using SafeExamBrowser.Configuration;
using SafeExamBrowser.Configuration.Settings;
using SafeExamBrowser.Contracts.Behaviour;
@@ -35,7 +36,6 @@ namespace SafeExamBrowser.Runtime
internal IRuntimeController RuntimeController { get; private set; }
internal Queue StartupOperations { get; private set; }
- internal RuntimeWindow RuntimeWindow { get; private set; }
internal void BuildObjectGraph()
{
@@ -56,16 +56,13 @@ namespace SafeExamBrowser.Runtime
var shutdownController = new ShutdownController(logger, runtimeInfo, text, uiFactory);
var startupController = new StartupController(logger, runtimeInfo, systemInfo, text, uiFactory);
- RuntimeWindow = new RuntimeWindow(new DefaultLogFormatter(), runtimeInfo, text);
- RuntimeController = new RuntimeController(serviceProxy, new ModuleLogger(logger, typeof(RuntimeController)), RuntimeWindow, settingsRepository, shutdownController, startupController);
-
- logger.Subscribe(RuntimeWindow);
+ RuntimeController = new RuntimeController(new ModuleLogger(logger, typeof(RuntimeController)), runtimeInfo, serviceProxy, settingsRepository, shutdownController, startupController, Application.Current.Shutdown, text, uiFactory);
StartupOperations = new Queue();
StartupOperations.Enqueue(new I18nOperation(logger, text));
StartupOperations.Enqueue(new ConfigurationOperation(logger, runtimeInfo, settingsRepository, text, uiFactory, args));
StartupOperations.Enqueue(new ServiceOperation(logger, serviceProxy, settingsRepository, text));
- StartupOperations.Enqueue(new KioskModeOperation());
+ StartupOperations.Enqueue(new KioskModeOperation(logger, settingsRepository));
}
internal void LogStartupInformation()
diff --git a/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs
index 22e33d58..fd0d9bb2 100644
--- a/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs
@@ -55,12 +55,30 @@ namespace SafeExamBrowser.UserInterface.Classic
public void BringToForeground()
{
- if (WindowState == WindowState.Minimized)
+ Dispatcher.Invoke(() =>
{
- WindowState = WindowState.Normal;
- }
+ if (WindowState == WindowState.Minimized)
+ {
+ WindowState = WindowState.Normal;
+ }
- Activate();
+ Activate();
+ });
+ }
+
+ public new void Close()
+ {
+ Dispatcher.Invoke(base.Close);
+ }
+
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
+ public new void Show()
+ {
+ Dispatcher.Invoke(base.Show);
}
public void UpdateAddress(string url)
diff --git a/SafeExamBrowser.UserInterface.Classic/LogWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/LogWindow.xaml.cs
index ddf013f0..49d3f9a4 100644
--- a/SafeExamBrowser.UserInterface.Classic/LogWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Classic/LogWindow.xaml.cs
@@ -47,6 +47,11 @@ namespace SafeExamBrowser.UserInterface.Classic
Dispatcher.Invoke(base.Close);
}
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
public new void Show()
{
Dispatcher.Invoke(base.Show);
diff --git a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml
index 1964c83a..e443bf17 100644
--- a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml
+++ b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml
@@ -8,7 +8,7 @@
mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="750" WindowStyle="None" WindowStartupLocation="CenterScreen"
Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
-
+
@@ -18,7 +18,7 @@
-
+
@@ -41,8 +41,8 @@
-
-
+
+
diff --git a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs
index 948211e9..f2f25a1a 100644
--- a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs
@@ -9,18 +9,22 @@
using System;
using System.Windows;
using System.Windows.Documents;
+using System.Windows.Input;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
+using SafeExamBrowser.UserInterface.Classic.ViewModels;
namespace SafeExamBrowser.UserInterface.Classic
{
- public partial class RuntimeWindow : Window, ILogObserver, IRuntimeWindow
+ public partial class RuntimeWindow : Window, IRuntimeWindow
{
+ private bool allowClose;
private ILogContentFormatter formatter;
private IRuntimeInfo runtimeInfo;
private IText text;
+ private RuntimeWindowViewModel model;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
@@ -44,6 +48,20 @@ namespace SafeExamBrowser.UserInterface.Classic
Dispatcher.Invoke(Activate);
}
+ public new void Close()
+ {
+ Dispatcher.Invoke(() =>
+ {
+ allowClose = true;
+ base.Close();
+ });
+ }
+
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
public void Notify(ILogContent content)
{
Dispatcher.Invoke(() =>
@@ -53,18 +71,41 @@ namespace SafeExamBrowser.UserInterface.Classic
});
}
- public void UpdateStatus(TextKey key)
+ public new void Show()
{
- Dispatcher.Invoke(() => StatusTextBlock.Text = text.Get(key));
+ Dispatcher.Invoke(base.Show);
+ }
+
+ public void UpdateStatus(TextKey key, bool showBusyIndication = false)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ AnimatedBorder.Visibility = showBusyIndication ? Visibility.Hidden : Visibility.Visible;
+ ProgressBar.Visibility = showBusyIndication ? Visibility.Visible : Visibility.Hidden;
+
+ model.StopBusyIndication();
+ model.Status = text.Get(key);
+
+ if (showBusyIndication)
+ {
+ model.StartBusyIndication();
+ }
+ });
}
private void InitializeRuntimeWindow()
{
Title = $"{runtimeInfo.ProgramTitle} - Version {runtimeInfo.ProgramVersion}";
+
InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 });
+
+ model = new RuntimeWindowViewModel();
+ StatusTextBlock.DataContext = model;
+
+ Closing += (o, args) => args.Cancel = !allowClose;
}
}
}
diff --git a/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj b/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj
index 9a743f2c..2d493715 100644
--- a/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj
+++ b/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj
@@ -113,10 +113,12 @@
+
+
Designer
diff --git a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs
index da9104a4..ab135041 100644
--- a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs
@@ -17,9 +17,17 @@ namespace SafeExamBrowser.UserInterface.Classic
{
public partial class SplashScreen : Window, ISplashScreen
{
+ private bool allowClose;
private SplashScreenViewModel model = new SplashScreenViewModel();
private IRuntimeInfo runtimeInfo;
private IText text;
+ private WindowClosingEventHandler closing;
+
+ event WindowClosingEventHandler IWindow.Closing
+ {
+ add { closing += value; }
+ remove { closing -= value; }
+ }
public SplashScreen(IRuntimeInfo runtimeInfo, IText text)
{
@@ -30,14 +38,28 @@ namespace SafeExamBrowser.UserInterface.Classic
InitializeSplashScreen();
}
- public void InvokeClose()
+ public void BringToForeground()
{
- Dispatcher.Invoke(Close);
+ Dispatcher.Invoke(Activate);
}
- public void InvokeShow()
+ public new void Close()
{
- Dispatcher.Invoke(Show);
+ Dispatcher.Invoke(() =>
+ {
+ allowClose = true;
+ base.Close();
+ });
+ }
+
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
+ public new void Show()
+ {
+ Dispatcher.Invoke(base.Show);
}
public void Progress(int amount = 1)
@@ -83,6 +105,8 @@ namespace SafeExamBrowser.UserInterface.Classic
// To prevent the progress bar going from max to min value at startup...
model.MaxProgress = 1;
+
+ Closing += (o, args) => args.Cancel = !allowClose;
}
}
}
diff --git a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs
index 961d7b35..ae3863fd 100644
--- a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs
@@ -15,6 +15,7 @@ using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.UserInterface.Classic.Controls;
+using SafeExamBrowser.UserInterface.Classic.Utilities;
using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBoxResult;
namespace SafeExamBrowser.UserInterface.Classic
@@ -76,6 +77,30 @@ namespace SafeExamBrowser.UserInterface.Classic
return new PowerSupplyControl();
}
+ public IRuntimeWindow CreateRuntimeWindow(IRuntimeInfo runtimeInfo, IText text)
+ {
+ RuntimeWindow runtimeWindow = null;
+ var windowReadyEvent = new AutoResetEvent(false);
+ var runtimeWindowThread = new Thread(() =>
+ {
+ runtimeWindow = new RuntimeWindow(new RuntimeWindowLogFormatter(), runtimeInfo, text);
+ runtimeWindow.Closed += (o, args) => runtimeWindow.Dispatcher.InvokeShutdown();
+
+ windowReadyEvent.Set();
+
+ System.Windows.Threading.Dispatcher.Run();
+ });
+
+ runtimeWindowThread.SetApartmentState(ApartmentState.STA);
+ runtimeWindowThread.Name = nameof(RuntimeWindow);
+ runtimeWindowThread.IsBackground = true;
+ runtimeWindowThread.Start();
+
+ windowReadyEvent.WaitOne();
+
+ return runtimeWindow;
+ }
+
public ISplashScreen CreateSplashScreen(IRuntimeInfo runtimeInfo, IText text)
{
SplashScreen splashScreen = null;
diff --git a/SafeExamBrowser.UserInterface.Classic/Utilities/RuntimeWindowLogFormatter.cs b/SafeExamBrowser.UserInterface.Classic/Utilities/RuntimeWindowLogFormatter.cs
new file mode 100644
index 00000000..d7c49d06
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Classic/Utilities/RuntimeWindowLogFormatter.cs
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using System;
+using SafeExamBrowser.Contracts.Logging;
+
+namespace SafeExamBrowser.UserInterface.Classic.Utilities
+{
+ internal class RuntimeWindowLogFormatter : ILogContentFormatter
+ {
+ public string Format(ILogContent content)
+ {
+ if (content is ILogText)
+ {
+ return (content as ILogText).Text;
+ }
+
+ if (content is ILogMessage)
+ {
+ return FormatLogMessage(content as ILogMessage);
+ }
+
+ throw new NotImplementedException($"The runtime window formatter is not yet implemented for log content of type {content.GetType()}!");
+ }
+
+ private string FormatLogMessage(ILogMessage message)
+ {
+ var time = message.DateTime.ToString("HH:mm:ss.fff");
+ var severity = message.Severity.ToString().ToUpper();
+
+ return $"{time} - {severity}: {message.Message}";
+ }
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs b/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs
new file mode 100644
index 00000000..7c411cba
--- /dev/null
+++ b/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using System.ComponentModel;
+using System.Timers;
+
+namespace SafeExamBrowser.UserInterface.Classic.ViewModels
+{
+ internal class RuntimeWindowViewModel : INotifyPropertyChanged
+ {
+ private string status;
+ private Timer timer;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Status
+ {
+ get
+ {
+ return status;
+ }
+ set
+ {
+ status = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
+ }
+ }
+
+ public void StartBusyIndication()
+ {
+ StopBusyIndication();
+
+ timer = new Timer
+ {
+ AutoReset = true,
+ Interval = 750
+ };
+
+ timer.Elapsed += Timer_Elapsed;
+ timer.Start();
+ }
+
+ public void StopBusyIndication()
+ {
+ timer?.Stop();
+ timer?.Close();
+ }
+
+ private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+ {
+ var next = Status ?? string.Empty;
+
+ if (next.EndsWith("..."))
+ {
+ next = Status.Substring(0, Status.Length - 3);
+ }
+ else
+ {
+ next += ".";
+ }
+
+ Status = next;
+ }
+ }
+}
diff --git a/SafeExamBrowser.UserInterface.Windows10/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Windows10/BrowserWindow.xaml.cs
index 3bd37eab..f4022464 100644
--- a/SafeExamBrowser.UserInterface.Windows10/BrowserWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Windows10/BrowserWindow.xaml.cs
@@ -53,12 +53,30 @@ namespace SafeExamBrowser.UserInterface.Windows10
public void BringToForeground()
{
- if (WindowState == WindowState.Minimized)
+ Dispatcher.Invoke(() =>
{
- WindowState = WindowState.Normal;
- }
+ if (WindowState == WindowState.Minimized)
+ {
+ WindowState = WindowState.Normal;
+ }
- Activate();
+ Activate();
+ });
+ }
+
+ public new void Close()
+ {
+ Dispatcher.Invoke(base.Close);
+ }
+
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
+ public new void Show()
+ {
+ Dispatcher.Invoke(base.Show);
}
public void UpdateAddress(string url)
diff --git a/SafeExamBrowser.UserInterface.Windows10/LogWindow.xaml.cs b/SafeExamBrowser.UserInterface.Windows10/LogWindow.xaml.cs
index a2e1f815..d2530f17 100644
--- a/SafeExamBrowser.UserInterface.Windows10/LogWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Windows10/LogWindow.xaml.cs
@@ -47,6 +47,11 @@ namespace SafeExamBrowser.UserInterface.Windows10
Dispatcher.Invoke(base.Close);
}
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
public new void Show()
{
Dispatcher.Invoke(base.Show);
diff --git a/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs
index ff7b4e78..59bff6df 100644
--- a/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Windows10/SplashScreen.xaml.cs
@@ -17,9 +17,17 @@ namespace SafeExamBrowser.UserInterface.Windows10
{
public partial class SplashScreen : Window, ISplashScreen
{
+ private bool allowClose;
private SplashScreenViewModel model = new SplashScreenViewModel();
private IRuntimeInfo runtimeInfo;
private IText text;
+ private WindowClosingEventHandler closing;
+
+ event WindowClosingEventHandler IWindow.Closing
+ {
+ add { closing += value; }
+ remove { closing -= value; }
+ }
public SplashScreen(IRuntimeInfo runtimeInfo, IText text)
{
@@ -30,14 +38,28 @@ namespace SafeExamBrowser.UserInterface.Windows10
InitializeSplashScreen();
}
- public void InvokeClose()
+ public void BringToForeground()
{
- Dispatcher.Invoke(Close);
+ Dispatcher.Invoke(Activate);
}
- public void InvokeShow()
+ public new void Close()
{
- Dispatcher.Invoke(Show);
+ Dispatcher.Invoke(() =>
+ {
+ allowClose = true;
+ base.Close();
+ });
+ }
+
+ public new void Hide()
+ {
+ Dispatcher.Invoke(base.Hide);
+ }
+
+ public new void Show()
+ {
+ Dispatcher.Invoke(base.Show);
}
public void Progress(int amount = 1)
@@ -83,6 +105,8 @@ namespace SafeExamBrowser.UserInterface.Windows10
// To prevent the progress bar going from max to min value at startup...
model.MaxProgress = 1;
+
+ Closing += (o, args) => args.Cancel = !allowClose;
}
}
}
diff --git a/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs
index 75f1ef37..ae4ceae9 100644
--- a/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs
@@ -77,6 +77,12 @@ namespace SafeExamBrowser.UserInterface.Windows10
return new PowerSupplyControl();
}
+ public IRuntimeWindow CreateRuntimeWindow(IRuntimeInfo runtimeInfo, IText text)
+ {
+ // TODO
+ throw new System.NotImplementedException();
+ }
+
public ISplashScreen CreateSplashScreen(IRuntimeInfo runtimeInfo, IText text)
{
SplashScreen splashScreen = null;