diff --git a/SafeExamBrowser.Contracts/I18n/Key.cs b/SafeExamBrowser.Contracts/I18n/Key.cs
index 832dfe5e..16c021fc 100644
--- a/SafeExamBrowser.Contracts/I18n/Key.cs
+++ b/SafeExamBrowser.Contracts/I18n/Key.cs
@@ -21,7 +21,15 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_StartupErrorTitle,
Notification_AboutTooltip,
SplashScreen_InitializeBrowser,
+ SplashScreen_InitializeProcessMonitoring,
+ SplashScreen_InitializeTaskbar,
+ SplashScreen_InitializeWorkArea,
+ SplashScreen_RestoreWorkArea,
+ SplashScreen_ShutdownProcedure,
SplashScreen_StartupProcedure,
+ SplashScreen_StopProcessMonitoring,
+ SplashScreen_WaitExplorerStartup,
+ SplashScreen_WaitExplorerTermination,
Version
}
}
diff --git a/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs b/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs
new file mode 100644
index 00000000..77f717ca
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2017 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.Monitoring
+{
+ public interface IKeyboardInterceptor
+ {
+ }
+}
diff --git a/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs b/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs
new file mode 100644
index 00000000..a69d7f2e
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Monitoring/IMouseInterceptor.cs
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2017 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.Monitoring
+{
+ public interface IMouseInterceptor
+ {
+ }
+}
diff --git a/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs b/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs
new file mode 100644
index 00000000..7b12ba0c
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017 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.Monitoring
+{
+ public interface IProcessMonitor
+ {
+ ///
+ /// Starts a new instance of the Windows explorer shell.
+ ///
+ void StartExplorerShell();
+
+ ///
+ /// Starts monitoring the Windows explorer, i.e. any newly created instances of
+ /// explorer.exe will automatically be terminated.
+ ///
+ void StartMonitoringExplorer();
+
+ ///
+ /// Stops monitoring the Windows explorer.
+ ///
+ void StopMonitoringExplorer();
+
+ ///
+ /// Terminates the Windows explorer shell, i.e. the taskbar.
+ ///
+ void CloseExplorerShell();
+ }
+}
diff --git a/SafeExamBrowser.Contracts/Monitoring/IWindowMonitor.cs b/SafeExamBrowser.Contracts/Monitoring/IWindowMonitor.cs
new file mode 100644
index 00000000..92a90720
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Monitoring/IWindowMonitor.cs
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2017 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.Monitoring
+{
+ public interface IWindowMonitor
+ {
+ }
+}
diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
index bb6b714b..66ee8d3a 100644
--- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
+++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
@@ -58,6 +58,10 @@
+
+
+
+
diff --git a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs
index 8f72e6a8..5bcad222 100644
--- a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs
@@ -13,9 +13,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
public interface ISplashScreen
{
///
- /// Closes the splash screen.
+ /// Closes the splash screen on its own thread.
///
- void Close();
+ void InvokeClose();
+
+ ///
+ /// Shows the splash screen on its own thread.
+ ///
+ void InvokeShow();
///
/// Set the maximum of the splash screen's progress bar.
@@ -23,9 +28,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
void SetMaxProgress(int max);
///
- /// Shows the splash screen to the user.
+ /// Starts an animation indicating the user that something is going on.
///
- void Show();
+ void StartBusyIndication();
+
+ ///
+ /// Stops the busy animation, if it was running.
+ ///
+ void StopBusyIndication();
///
/// Updates the progress bar of the splash screen according to the specified amount.
diff --git a/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs b/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs
index d9463764..973fb184 100644
--- a/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs
@@ -30,10 +30,5 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// Sets the size of the taskbar.
///
void SetSize(int width, int height);
-
- ///
- /// Displays the taskbar on the screen.
- ///
- void Show();
}
}
diff --git a/SafeExamBrowser.Contracts/UserInterface/IUiElementFactory.cs b/SafeExamBrowser.Contracts/UserInterface/IUiElementFactory.cs
index a0b4177c..55fe0277 100644
--- a/SafeExamBrowser.Contracts/UserInterface/IUiElementFactory.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/IUiElementFactory.cs
@@ -7,6 +7,7 @@
*/
using SafeExamBrowser.Contracts.Configuration;
+using SafeExamBrowser.Contracts.I18n;
namespace SafeExamBrowser.Contracts.UserInterface
{
@@ -17,6 +18,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
///
ITaskbarButton CreateApplicationButton(IApplicationInfo info);
+ ///
+ /// Creates a new splash screen which runs on its own thread.
+ ///
+ ISplashScreen CreateSplashScreen(ISettings settings, IText text);
+
///
/// Creates a taskbar notification, initialized with the given notification information.
///
diff --git a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs
index b31f6f6b..fa2c3934 100644
--- a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs
+++ b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs
@@ -7,9 +7,14 @@
*/
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
using SafeExamBrowser.Contracts.Behaviour;
+using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
+using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour
@@ -18,23 +23,52 @@ namespace SafeExamBrowser.Core.Behaviour
{
private ILogger logger;
private IMessageBox messageBox;
+ private IProcessMonitor processMonitor;
+ private ISettings settings;
+ private ISplashScreen splashScreen;
private IText text;
+ private IUiElementFactory uiFactory;
- public ShutdownController(ILogger logger, IMessageBox messageBox, IText text)
+ private IEnumerable ShutdownOperations
+ {
+ get
+ {
+ yield return StopProcessMonitoring;
+ yield return RestoreWorkArea;
+ yield return FinalizeApplicationLog;
+ }
+ }
+
+ public ShutdownController(
+ ILogger logger,
+ IMessageBox messageBox,
+ IProcessMonitor processMonitor,
+ ISettings settings,
+ IText text,
+ IUiElementFactory uiFactory)
{
this.logger = logger;
this.messageBox = messageBox;
+ this.processMonitor = processMonitor;
+ this.settings = settings;
this.text = text;
+ this.uiFactory = uiFactory;
}
public void FinalizeApplication()
{
try
{
- // TODO:
- // - Gather TODOs!
+ InitializeSplashScreen();
- logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
+ foreach (var operation in ShutdownOperations)
+ {
+ operation();
+ splashScreen.UpdateProgress();
+
+ // TODO: Remove!
+ Thread.Sleep(250);
+ }
}
catch (Exception e)
{
@@ -42,5 +76,41 @@ namespace SafeExamBrowser.Core.Behaviour
messageBox.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error);
}
}
+
+ private void InitializeSplashScreen()
+ {
+ splashScreen = uiFactory.CreateSplashScreen(settings, text);
+ splashScreen.SetMaxProgress(ShutdownOperations.Count());
+ splashScreen.UpdateText(Key.SplashScreen_ShutdownProcedure);
+ splashScreen.InvokeShow();
+ }
+
+ private void StopProcessMonitoring()
+ {
+ logger.Info("Stopping process monitoring.");
+ splashScreen.UpdateText(Key.SplashScreen_StopProcessMonitoring);
+
+ // TODO
+
+ processMonitor.StopMonitoringExplorer();
+ }
+
+ private void RestoreWorkArea()
+ {
+ logger.Info("Restoring work area.");
+ splashScreen.UpdateText(Key.SplashScreen_RestoreWorkArea);
+
+ // TODO
+
+ splashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup);
+ splashScreen.StartBusyIndication();
+ processMonitor.StartExplorerShell();
+ splashScreen.StopBusyIndication();
+ }
+
+ private void FinalizeApplicationLog()
+ {
+ logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
+ }
}
}
diff --git a/SafeExamBrowser.Core/Behaviour/StartupController.cs b/SafeExamBrowser.Core/Behaviour/StartupController.cs
index 3c7b3535..5d698945 100644
--- a/SafeExamBrowser.Core/Behaviour/StartupController.cs
+++ b/SafeExamBrowser.Core/Behaviour/StartupController.cs
@@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
+using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour
@@ -25,6 +26,7 @@ namespace SafeExamBrowser.Core.Behaviour
private ILogger logger;
private IMessageBox messageBox;
private INotificationInfo aboutInfo;
+ private IProcessMonitor processMonitor;
private ISettings settings;
private ISplashScreen splashScreen;
private ITaskbar taskbar;
@@ -53,8 +55,8 @@ namespace SafeExamBrowser.Core.Behaviour
ILogger logger,
IMessageBox messageBox,
INotificationInfo aboutInfo,
+ IProcessMonitor processMonitor,
ISettings settings,
- ISplashScreen splashScreen,
ITaskbar taskbar,
IText text,
IUiElementFactory uiFactory)
@@ -64,8 +66,8 @@ namespace SafeExamBrowser.Core.Behaviour
this.logger = logger;
this.messageBox = messageBox;
this.aboutInfo = aboutInfo;
+ this.processMonitor = processMonitor;
this.settings = settings;
- this.splashScreen = splashScreen;
this.taskbar = taskbar;
this.text = text;
this.uiFactory = uiFactory;
@@ -112,62 +114,63 @@ namespace SafeExamBrowser.Core.Behaviour
private void InitializeSplashScreen()
{
+ splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(StartupOperations.Count());
splashScreen.UpdateText(Key.SplashScreen_StartupProcedure);
+ splashScreen.InvokeShow();
}
private void HandleCommandLineArguments()
{
- logger.Info("Parsing command line arguments.");
-
// TODO
}
private void DetectOperatingSystem()
{
- logger.Info("Detecting operating system.");
-
// TODO
}
private void EstablishWcfServiceConnection()
{
- logger.Info("Establishing connection to WCF service.");
-
// TODO
}
private void DeactivateWindowsFeatures()
{
- logger.Info("Deactivating Windows Update.");
-
- // TODO
-
- logger.Info("Disabling lock screen options.");
-
// TODO
}
private void InitializeProcessMonitoring()
{
logger.Info("Initializing process monitoring.");
+ splashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring);
// TODO
+
+ processMonitor.StartMonitoringExplorer();
}
private void InitializeWorkArea()
{
logger.Info("Initializing work area.");
+ splashScreen.UpdateText(Key.SplashScreen_InitializeWorkArea);
// TODO
- // - Killing explorer.exe
// - Minimizing all open windows
// - Emptying clipboard
+
+ splashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination);
+ splashScreen.StartBusyIndication();
+ processMonitor.CloseExplorerShell();
+ splashScreen.StopBusyIndication();
}
private void InitializeTaskbar()
{
logger.Info("Initializing taskbar.");
+ splashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar);
+
+ // TODO
var aboutNotification = uiFactory.CreateNotification(aboutInfo);
@@ -179,6 +182,8 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("Initializing browser.");
splashScreen.UpdateText(Key.SplashScreen_InitializeBrowser);
+ // TODO
+
var browserButton = uiFactory.CreateApplicationButton(browserInfo);
browserController.RegisterApplicationButton(browserButton);
@@ -188,6 +193,7 @@ namespace SafeExamBrowser.Core.Behaviour
private void FinishInitialization()
{
logger.Info("Application successfully initialized!");
+ splashScreen.InvokeClose();
}
}
}
diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml
index acc6147b..6629783b 100644
--- a/SafeExamBrowser.Core/I18n/Text.xml
+++ b/SafeExamBrowser.Core/I18n/Text.xml
@@ -5,7 +5,15 @@
An unexpected error occurred during the startup procedure! Please consult the application log for more information...
Startup Error
About Safe Exam Browser
- Initializing browser.
- Initiating startup procedure.
+ Initializing browser
+ Initializing process monitoring
+ Initializing taskbar
+ Initializing work area
+ Restoring work area
+ Initiating shutdown procedure
+ Initiating startup procedure
+ Stopping process monitoring
+ Waiting for Windows explorer to start up
+ Waiting for Windows explorer to shut down
Version
\ No newline at end of file
diff --git a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs
new file mode 100644
index 00000000..2f50e75c
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017 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 System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using SafeExamBrowser.Contracts.Logging;
+using SafeExamBrowser.Contracts.Monitoring;
+
+namespace SafeExamBrowser.Monitoring.Processes
+{
+ public class ProcessMonitor : IProcessMonitor
+ {
+ private ILogger logger;
+
+ public ProcessMonitor(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ public void StartExplorerShell()
+ {
+ var process = new Process();
+ var explorerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "explorer.exe");
+
+ Log("Restarting explorer shell...");
+
+ process.StartInfo.CreateNoWindow = true;
+ process.StartInfo.FileName = explorerPath;
+ process.Start();
+
+ while (User32.GetShellWindowHandle() == IntPtr.Zero)
+ {
+ Thread.Sleep(20);
+ }
+
+ process.Refresh();
+ Log($"Explorer shell successfully started with PID = {process.Id}.");
+ process.Close();
+ }
+
+ public void StartMonitoringExplorer()
+ {
+ // TODO
+ }
+
+ public void StopMonitoringExplorer()
+ {
+ // TODO
+ }
+
+ public void CloseExplorerShell()
+ {
+ var processId = User32.GetShellProcessId();
+ var explorerProcesses = Process.GetProcessesByName("explorer");
+ var shellProcess = explorerProcesses.FirstOrDefault(p => p.Id == processId);
+
+ if (shellProcess != null)
+ {
+ Log($"Found explorer shell processes with PID = {processId}. Sending close message...");
+ User32.PostCloseMessageToShell();
+
+ while (!shellProcess.HasExited)
+ {
+ shellProcess.Refresh();
+ Thread.Sleep(20);
+ }
+
+ Log($"Successfully terminated explorer shell process with PID = {processId}.");
+ }
+ else
+ {
+ Log("The explorer shell seems to already be terminated. Skipping this step...");
+ }
+ }
+
+ private void Log(string message)
+ {
+ logger.Info($"[{nameof(ProcessMonitor)}] {message}");
+ }
+ }
+}
diff --git a/SafeExamBrowser.Monitoring/Properties/AssemblyInfo.cs b/SafeExamBrowser.Monitoring/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..c51c021f
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SafeExamBrowser.Monitoring")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SafeExamBrowser.Monitoring")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ef563531-4eb5-44b9-a5ec-d6d6f204469b")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
new file mode 100644
index 00000000..d710e122
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
@@ -0,0 +1,59 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {EF563531-4EB5-44B9-A5EC-D6D6F204469B}
+ Library
+ Properties
+ SafeExamBrowser.Monitoring
+ SafeExamBrowser.Monitoring
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {47da5933-bef8-4729-94e6-abde2db12262}
+ SafeExamBrowser.Contracts
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Monitoring/User32.cs b/SafeExamBrowser.Monitoring/User32.cs
new file mode 100644
index 00000000..eca68cd4
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/User32.cs
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017 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 System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace SafeExamBrowser.Monitoring
+{
+ ///
+ /// Provides access to the native Windows API exposed by user32.dll.
+ ///
+ internal static class User32
+ {
+ internal static IntPtr GetShellWindowHandle()
+ {
+ return FindWindow("Shell_TrayWnd", null);
+ }
+
+ internal static uint GetShellProcessId()
+ {
+ var handle = FindWindow("Shell_TrayWnd", null);
+ var threadId = GetWindowThreadProcessId(handle, out uint processId);
+
+ return processId;
+ }
+
+ ///
+ /// The close message 0x5B4 posted to the shell is undocumented and not officially supported:
+ /// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965
+ ///
+ internal static void PostCloseMessageToShell()
+ {
+ var taskbarHandle = FindWindow("Shell_TrayWnd", null);
+ var success = PostMessage(taskbarHandle, 0x5B4, IntPtr.Zero, IntPtr.Zero);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ }
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+ }
+}
diff --git a/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml.cs
index f3dba797..3a1db4b2 100644
--- a/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml.cs
+++ b/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml.cs
@@ -58,7 +58,9 @@ namespace SafeExamBrowser.UserInterface.Controls
Button.ToolTip = info.Tooltip;
Button.Content = IconResourceLoader.Load(info.IconResource);
+ Button.MouseEnter += (o, args) => InstancePopup.IsOpen = instances.Count > 1;
Button.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || ActiveBar.IsMouseOver;
+ ActiveBar.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || Button.IsMouseOver;
InstancePopup.MouseLeave += (o, args) => InstancePopup.IsOpen = false;
InstancePopup.Opened += (o, args) => ActiveBar.Width = Double.NaN;
InstancePopup.Closed += (o, args) => ActiveBar.Width = 40;
diff --git a/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml.cs b/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml.cs
index 71315caa..77a3e3d2 100644
--- a/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml.cs
+++ b/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml.cs
@@ -20,7 +20,7 @@ namespace SafeExamBrowser.UserInterface.Controls
private void Button_Click(object sender, RoutedEventArgs e)
{
- Application.Current.Shutdown();
+ Application.Current.MainWindow.Close();
}
}
}
diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs
index 0fc74c58..e6c03749 100644
--- a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs
+++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs
@@ -30,11 +30,31 @@ namespace SafeExamBrowser.UserInterface
InitializeSplashScreen();
}
+ public void InvokeClose()
+ {
+ Dispatcher.Invoke(Close);
+ }
+
+ public void InvokeShow()
+ {
+ Dispatcher.Invoke(Show);
+ }
+
public void SetMaxProgress(int max)
{
model.MaxProgress = max;
}
+ public void StartBusyIndication()
+ {
+ model.StartBusyIndication();
+ }
+
+ public void StopBusyIndication()
+ {
+ model.StopBusyIndication();
+ }
+
public void UpdateProgress(int amount = 1)
{
model.CurrentProgress += amount;
diff --git a/SafeExamBrowser.UserInterface/UiElementFactory.cs b/SafeExamBrowser.UserInterface/UiElementFactory.cs
index a0f00f69..9956843c 100644
--- a/SafeExamBrowser.UserInterface/UiElementFactory.cs
+++ b/SafeExamBrowser.UserInterface/UiElementFactory.cs
@@ -6,7 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System.Threading;
using SafeExamBrowser.Contracts.Configuration;
+using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.Controls;
@@ -19,6 +21,31 @@ namespace SafeExamBrowser.UserInterface
return new ApplicationButton(info);
}
+ public ISplashScreen CreateSplashScreen(ISettings settings, IText text)
+ {
+ SplashScreen splashScreen = null;
+ var splashReadyEvent = new AutoResetEvent(false);
+ var splashScreenThread = new Thread(() =>
+ {
+ splashScreen = new SplashScreen(settings, text);
+ splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown();
+ splashScreen.Show();
+
+ splashReadyEvent.Set();
+
+ System.Windows.Threading.Dispatcher.Run();
+ });
+
+ splashScreenThread.SetApartmentState(ApartmentState.STA);
+ splashScreenThread.Name = "Splash Screen Thread";
+ splashScreenThread.IsBackground = true;
+ splashScreenThread.Start();
+
+ splashReadyEvent.WaitOne();
+
+ return splashScreen;
+ }
+
public ITaskbarNotification CreateNotification(INotificationInfo info)
{
return new NotificationIcon(info);
diff --git a/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs b/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs
index e3d858c2..bcd474ad 100644
--- a/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs
+++ b/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs
@@ -7,6 +7,7 @@
*/
using System.ComponentModel;
+using System.Timers;
namespace SafeExamBrowser.UserInterface.ViewModels
{
@@ -15,6 +16,7 @@ namespace SafeExamBrowser.UserInterface.ViewModels
private int currentProgress;
private int maxProgress;
private string status;
+ private Timer busyTimer;
public event PropertyChangedEventHandler PropertyChanged;
@@ -56,5 +58,41 @@ namespace SafeExamBrowser.UserInterface.ViewModels
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
}
}
+
+ public void StartBusyIndication()
+ {
+ StopBusyIndication();
+
+ busyTimer = new Timer
+ {
+ AutoReset = true,
+ Interval = 750
+ };
+
+ busyTimer.Elapsed += BusyTimer_Elapsed;
+ busyTimer.Start();
+ }
+
+ public void StopBusyIndication()
+ {
+ busyTimer?.Stop();
+ busyTimer?.Close();
+ }
+
+ private void BusyTimer_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.sln b/SafeExamBrowser.sln
index d36f315a..70768e7c 100644
--- a/SafeExamBrowser.sln
+++ b/SafeExamBrowser.sln
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Contracts",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Browser", "SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj", "{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Monitoring", "SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj", "{EF563531-4EB5-44B9-A5EC-D6D6F204469B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -51,6 +53,10 @@ Global
{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/SafeExamBrowser/App.cs b/SafeExamBrowser/App.cs
index 99fd1492..7cc469c5 100644
--- a/SafeExamBrowser/App.cs
+++ b/SafeExamBrowser/App.cs
@@ -26,7 +26,11 @@ namespace SafeExamBrowser
}
catch (Exception e)
{
- MessageBox.Show(e.Message + "\n\n" + e.StackTrace, "Fatal Error");
+ MessageBox.Show(e.Message + "\n\n" + e.StackTrace, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ finally
+ {
+ mutex.Close();
}
}
@@ -38,7 +42,7 @@ namespace SafeExamBrowser
}
else
{
- MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed");
+ MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
@@ -51,43 +55,6 @@ namespace SafeExamBrowser
{
base.OnStartup(e);
- ShowSplashScreen();
- InitializeApplication();
- }
-
- protected override void OnExit(ExitEventArgs e)
- {
- instances.ShutdownController.FinalizeApplication();
-
- base.OnExit(e);
- }
-
- private void ShowSplashScreen()
- {
- instances.BuildModulesRequiredBySplashScreen();
-
- var splashReadyEvent = new AutoResetEvent(false);
- var splashScreenThread = new Thread(() =>
- {
- instances.SplashScreen = new UserInterface.SplashScreen(instances.Settings, instances.Text);
- instances.SplashScreen.Closed += (o, args) => instances.SplashScreen.Dispatcher.InvokeShutdown();
- instances.SplashScreen.Show();
-
- splashReadyEvent.Set();
-
- System.Windows.Threading.Dispatcher.Run();
- });
-
- splashScreenThread.SetApartmentState(ApartmentState.STA);
- splashScreenThread.Name = "Splash Screen Thread";
- splashScreenThread.IsBackground = true;
- splashScreenThread.Start();
-
- splashReadyEvent.WaitOne();
- }
-
- private void InitializeApplication()
- {
instances.BuildObjectGraph();
var success = instances.StartupController.TryInitializeApplication();
@@ -95,14 +62,19 @@ namespace SafeExamBrowser
if (success)
{
MainWindow = instances.Taskbar;
+ MainWindow.Closing += (o, args) => ShutdownApplication();
MainWindow.Show();
}
else
{
Shutdown();
}
+ }
- instances.SplashScreen?.Dispatcher.InvokeAsync(instances.SplashScreen.Close);
+ private void ShutdownApplication()
+ {
+ MainWindow.Hide();
+ instances.ShutdownController.FinalizeApplication();
}
}
}
diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs
index ce6f5c55..7b7af9fe 100644
--- a/SafeExamBrowser/CompositionRoot.cs
+++ b/SafeExamBrowser/CompositionRoot.cs
@@ -11,11 +11,13 @@ using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
+using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Core.Behaviour;
using SafeExamBrowser.Core.Configuration;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
+using SafeExamBrowser.Monitoring.Processes;
using SafeExamBrowser.UserInterface;
namespace SafeExamBrowser
@@ -24,23 +26,18 @@ namespace SafeExamBrowser
{
private IApplicationController browserController;
private IApplicationInfo browserInfo;
+ private ILogger logger;
private IMessageBox messageBox;
private INotificationInfo aboutInfo;
- private ILogger logger;
+ private IProcessMonitor processMonitor;
+ private ISettings settings;
+ private IText text;
private IUiElementFactory uiFactory;
+ private ITextResource textResource;
- public ISettings Settings { get; private set; }
public IShutdownController ShutdownController { get; private set; }
public IStartupController StartupController { get; private set; }
- public SplashScreen SplashScreen { get; set; }
public Taskbar Taskbar { get; private set; }
- public IText Text { get; private set; }
-
- public void BuildModulesRequiredBySplashScreen()
- {
- Settings = new Settings();
- Text = new Text(new XmlTextResource());
- }
public void BuildObjectGraph()
{
@@ -48,14 +45,18 @@ namespace SafeExamBrowser
browserInfo = new BrowserApplicationInfo();
logger = new Logger();
messageBox = new WpfMessageBox();
+ settings = new Settings();
Taskbar = new Taskbar();
+ textResource = new XmlTextResource();
uiFactory = new UiElementFactory();
- logger.Subscribe(new LogFileWriter(Settings));
+ logger.Subscribe(new LogFileWriter(settings));
- aboutInfo = new AboutNotificationInfo(Text);
- ShutdownController = new ShutdownController(logger, messageBox, Text);
- StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, Settings, SplashScreen, Taskbar, Text, uiFactory);
+ text = new Text(textResource);
+ aboutInfo = new AboutNotificationInfo(text);
+ processMonitor = new ProcessMonitor(logger);
+ ShutdownController = new ShutdownController(logger, messageBox, processMonitor, settings, text, uiFactory);
+ StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, processMonitor, settings, Taskbar, text, uiFactory);
}
}
}
diff --git a/SafeExamBrowser/SafeExamBrowser.csproj b/SafeExamBrowser/SafeExamBrowser.csproj
index af8765a8..044aa236 100644
--- a/SafeExamBrowser/SafeExamBrowser.csproj
+++ b/SafeExamBrowser/SafeExamBrowser.csproj
@@ -111,6 +111,10 @@
{3d6fdbb6-a4af-4626-bb2b-bf329d44f9cc}
SafeExamBrowser.Core
+
+ {ef563531-4eb5-44b9-a5ec-d6d6f204469b}
+ SafeExamBrowser.Monitoring
+
{e1be031a-4354-41e7-83e8-843ded4489ff}
SafeExamBrowser.UserInterface