diff --git a/SafeExamBrowser.Browser/BrowserApplicationController.cs b/SafeExamBrowser.Browser/BrowserApplicationController.cs
index 6683b61b..13eb9a6c 100644
--- a/SafeExamBrowser.Browser/BrowserApplicationController.cs
+++ b/SafeExamBrowser.Browser/BrowserApplicationController.cs
@@ -53,6 +53,11 @@ namespace SafeExamBrowser.Browser
public void Terminate()
{
+ foreach (var instance in instances)
+ {
+ instance.Window.Close();
+ }
+
Cef.Shutdown();
}
diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
index f6ff08aa..8ccf808b 100644
--- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
+++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
@@ -71,10 +71,6 @@
{47DA5933-BEF8-4729-94E6-ABDE2DB12262}
SafeExamBrowser.Contracts
-
- {73724659-4150-4792-a94e-42f5f3c1b696}
- SafeExamBrowser.WindowsApi
-
\ No newline at end of file
diff --git a/SafeExamBrowser.Configuration/WorkingArea.cs b/SafeExamBrowser.Configuration/WorkingArea.cs
index 74c53ba9..804b1b03 100644
--- a/SafeExamBrowser.Configuration/WorkingArea.cs
+++ b/SafeExamBrowser.Configuration/WorkingArea.cs
@@ -10,24 +10,26 @@ using System.Windows.Forms;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
-using SafeExamBrowser.WindowsApi;
-using SafeExamBrowser.WindowsApi.Types;
+using SafeExamBrowser.Contracts.WindowsApi;
+using SafeExamBrowser.Contracts.WindowsApi.Types;
namespace SafeExamBrowser.Configuration
{
public class WorkingArea : IWorkingArea
{
private ILogger logger;
+ private INativeMethods nativeMethods;
private RECT? originalWorkingArea;
- public WorkingArea(ILogger logger)
+ public WorkingArea(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
+ this.nativeMethods = nativeMethods;
}
public void InitializeFor(ITaskbar taskbar)
{
- originalWorkingArea = User32.GetWorkingArea();
+ originalWorkingArea = nativeMethods.GetWorkingArea();
LogWorkingArea("Saved original working area", originalWorkingArea.Value);
@@ -40,15 +42,15 @@ namespace SafeExamBrowser.Configuration
};
LogWorkingArea("Trying to set new working area", area);
- User32.SetWorkingArea(area);
- LogWorkingArea("Working area is now set to", User32.GetWorkingArea());
+ nativeMethods.SetWorkingArea(area);
+ LogWorkingArea("Working area is now set to", nativeMethods.GetWorkingArea());
}
public void Reset()
{
if (originalWorkingArea.HasValue)
{
- User32.SetWorkingArea(originalWorkingArea.Value);
+ nativeMethods.SetWorkingArea(originalWorkingArea.Value);
LogWorkingArea("Restored original working area", originalWorkingArea.Value);
}
}
diff --git a/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs b/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs
index 94157ca8..710acc68 100644
--- a/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs
+++ b/SafeExamBrowser.Contracts/Monitoring/IProcessMonitor.cs
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System;
+
namespace SafeExamBrowser.Contracts.Monitoring
{
public delegate void ExplorerStartedHandler();
@@ -18,6 +20,17 @@ namespace SafeExamBrowser.Contracts.Monitoring
///
event ExplorerStartedHandler ExplorerStarted;
+ ///
+ /// Terminates the Windows explorer shell, i.e. the taskbar.
+ ///
+ void CloseExplorerShell();
+
+ ///
+ /// Performs a check whether the process associated to the given window is allowed,
+ /// i.e. whether the specified window should be hidden.
+ ///
+ void OnWindowChanged(IntPtr window, out bool hide);
+
///
/// Starts a new instance of the Windows explorer shell.
///
@@ -33,10 +46,5 @@ namespace SafeExamBrowser.Contracts.Monitoring
/// 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
index 53f66c4f..8f79d0f1 100644
--- a/SafeExamBrowser.Contracts/Monitoring/IWindowMonitor.cs
+++ b/SafeExamBrowser.Contracts/Monitoring/IWindowMonitor.cs
@@ -6,10 +6,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System;
+
namespace SafeExamBrowser.Contracts.Monitoring
{
+ public delegate void WindowChangedHandler(IntPtr window, out bool hide);
+
public interface IWindowMonitor
{
+ ///
+ /// Event fired when the window monitor observes that the foreground window has changed.
+ ///
+ event WindowChangedHandler WindowChanged;
+
///
/// Hides all currently opened windows.
///
diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
index ddd82773..b34a3b01 100644
--- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
+++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
@@ -96,6 +96,8 @@
+
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Contracts/UserInterface/IWindow.cs b/SafeExamBrowser.Contracts/UserInterface/IWindow.cs
index 01869079..b6423f7e 100644
--- a/SafeExamBrowser.Contracts/UserInterface/IWindow.cs
+++ b/SafeExamBrowser.Contracts/UserInterface/IWindow.cs
@@ -15,6 +15,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
///
void BringToForeground();
+ ///
+ /// Closes the window.
+ ///
+ void Close();
+
///
/// Shows the window to the user.
///
diff --git a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
new file mode 100644
index 00000000..926b1828
--- /dev/null
+++ b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
@@ -0,0 +1,107 @@
+/*
+ * 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.Collections.Generic;
+using SafeExamBrowser.Contracts.WindowsApi.Types;
+
+namespace SafeExamBrowser.Contracts.WindowsApi
+{
+ public interface INativeMethods
+ {
+ ///
+ /// Retrieves a collection of handles to all currently open (i.e. visible) windows.
+ ///
+ ///
+ /// If the open windows could not be retrieved.
+ ///
+ IEnumerable GetOpenWindows();
+
+ ///
+ /// Retrieves the process identifier for the specified window handle.
+ ///
+ uint GetProcessIdFor(IntPtr window);
+
+ ///
+ /// Retrieves a window handle to the Windows taskbar. Returns IntPtr.Zero
+ /// if the taskbar could not be found (i.e. if it isn't running).
+ ///
+ IntPtr GetShellWindowHandle();
+
+ ///
+ /// Retrieves the process ID of the main Windows explorer instance controlling
+ /// desktop and taskbar or 0, if the process isn't running.
+ ///
+ uint GetShellProcessId();
+
+ ///
+ /// Retrieves the title of the specified window, or an empty string, if the
+ /// given window does not have a title.
+ ///
+ string GetWindowTitle(IntPtr window);
+
+ ///
+ /// Retrieves the currently configured working area of the primary screen.
+ ///
+ ///
+ /// If the working area could not be retrieved.
+ ///
+ RECT GetWorkingArea();
+
+ ///
+ /// Hides the given window.
+ ///
+ void HideWindow(IntPtr window);
+
+ ///
+ /// Minimizes all open windows.
+ ///
+ void MinimizeAllOpenWindows();
+
+ ///
+ /// Instructs the main Windows explorer process to shut down.
+ ///
+ ///
+ /// If the message could not be successfully posted. Does not apply if the process isn't running!
+ ///
+ void PostCloseMessageToShell();
+
+ ///
+ /// Registers a system event which will invoke the specified callback when the foreground window has changed.
+ /// Returns a handle to the newly registered Windows event hook.
+ ///
+ IntPtr RegisterSystemForegroundEvent(Action callback);
+
+ ///
+ /// Registers a system event which will invoke the specified callback when a window has received mouse capture.
+ /// Returns a handle to the newly registered Windows event hook.
+ ///
+ IntPtr RegisterSystemCaptureStartEvent(Action callback);
+
+ ///
+ /// Restores the specified window to its original size and position.
+ ///
+ void RestoreWindow(IntPtr window);
+
+ ///
+ /// Sets the working area of the primary screen according to the given dimensions.
+ ///
+ ///
+ /// If the working area could not be set.
+ ///
+ void SetWorkingArea(RECT bounds);
+
+ ///
+ /// Unregisters a previously registered system event.
+ ///
+ ///
+ /// If the event hook could not be successfully removed.
+ ///
+ void UnregisterSystemEvent(IntPtr handle);
+ }
+}
diff --git a/SafeExamBrowser.WindowsApi/Types/RECT.cs b/SafeExamBrowser.Contracts/WindowsApi/Types/RECT.cs
similarity index 91%
rename from SafeExamBrowser.WindowsApi/Types/RECT.cs
rename to SafeExamBrowser.Contracts/WindowsApi/Types/RECT.cs
index 44077323..7b883657 100644
--- a/SafeExamBrowser.WindowsApi/Types/RECT.cs
+++ b/SafeExamBrowser.Contracts/WindowsApi/Types/RECT.cs
@@ -8,7 +8,7 @@
using System.Runtime.InteropServices;
-namespace SafeExamBrowser.WindowsApi.Types
+namespace SafeExamBrowser.Contracts.WindowsApi.Types
{
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx.
diff --git a/SafeExamBrowser.Core/Behaviour/EventController.cs b/SafeExamBrowser.Core/Behaviour/EventController.cs
index 3004b473..dadcf1d3 100644
--- a/SafeExamBrowser.Core/Behaviour/EventController.cs
+++ b/SafeExamBrowser.Core/Behaviour/EventController.cs
@@ -16,27 +16,36 @@ namespace SafeExamBrowser.Core.Behaviour
{
public class EventController : IEventController
{
+ private ILogger logger;
private IProcessMonitor processMonitor;
private ITaskbar taskbar;
+ private IWindowMonitor windowMonitor;
private IWorkingArea workingArea;
- private ILogger logger;
- public EventController(ILogger logger, IProcessMonitor processMonitor, ITaskbar taskbar, IWorkingArea workingArea)
+ public EventController(
+ ILogger logger,
+ IProcessMonitor processMonitor,
+ ITaskbar taskbar,
+ IWindowMonitor windowMonitor,
+ IWorkingArea workingArea)
{
this.logger = logger;
this.processMonitor = processMonitor;
this.taskbar = taskbar;
+ this.windowMonitor = windowMonitor;
this.workingArea = workingArea;
}
public void Start()
{
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
+ windowMonitor.WindowChanged += processMonitor.OnWindowChanged;
}
public void Stop()
{
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
+ windowMonitor.WindowChanged -= processMonitor.OnWindowChanged;
}
private void ProcessMonitor_ExplorerStarted()
diff --git a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs
index ca924e80..a415e894 100644
--- a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs
+++ b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs
@@ -14,20 +14,63 @@ using System.Management;
using System.Threading;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
-using SafeExamBrowser.WindowsApi;
+using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Monitoring.Processes
{
public class ProcessMonitor : IProcessMonitor
{
private ILogger logger;
+ private INativeMethods nativeMethods;
private ManagementEventWatcher explorerWatcher;
public event ExplorerStartedHandler ExplorerStarted;
- public ProcessMonitor(ILogger logger)
+ public ProcessMonitor(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
+ this.nativeMethods = nativeMethods;
+ }
+
+ public void CloseExplorerShell()
+ {
+ var processId = nativeMethods.GetShellProcessId();
+ var explorerProcesses = Process.GetProcessesByName("explorer");
+ var shellProcess = explorerProcesses.FirstOrDefault(p => p.Id == processId);
+
+ if (shellProcess != null)
+ {
+ logger.Info($"Found explorer shell processes with PID = {processId}. Sending close message...");
+
+ nativeMethods.PostCloseMessageToShell();
+
+ while (!shellProcess.HasExited)
+ {
+ shellProcess.Refresh();
+ Thread.Sleep(20);
+ }
+
+ logger.Info($"Successfully terminated explorer shell process with PID = {processId}.");
+ }
+ else
+ {
+ logger.Info("The explorer shell seems to already be terminated. Skipping this step...");
+ }
+ }
+
+ public void OnWindowChanged(IntPtr window, out bool hide)
+ {
+ var processId = nativeMethods.GetProcessIdFor(window);
+ var process = Process.GetProcessById(Convert.ToInt32(processId));
+
+ if (process != null)
+ {
+ hide = process.ProcessName != "SafeExamBrowser";
+ }
+ else
+ {
+ hide = true;
+ }
}
public void StartExplorerShell()
@@ -41,7 +84,7 @@ namespace SafeExamBrowser.Monitoring.Processes
process.StartInfo.FileName = explorerPath;
process.Start();
- while (User32.GetShellWindowHandle() == IntPtr.Zero)
+ while (nativeMethods.GetShellWindowHandle() == IntPtr.Zero)
{
Thread.Sleep(20);
}
@@ -56,36 +99,14 @@ namespace SafeExamBrowser.Monitoring.Processes
explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe"));
explorerWatcher.EventArrived += new EventArrivedEventHandler(ExplorerWatcher_EventArrived);
explorerWatcher.Start();
+
+ logger.Info("Started monitoring process 'explorer.exe'.");
}
public void StopMonitoringExplorer()
{
explorerWatcher?.Stop();
- }
-
- public void CloseExplorerShell()
- {
- var processId = User32.GetShellProcessId();
- var explorerProcesses = Process.GetProcessesByName("explorer");
- var shellProcess = explorerProcesses.FirstOrDefault(p => p.Id == processId);
-
- if (shellProcess != null)
- {
- logger.Info($"Found explorer shell processes with PID = {processId}. Sending close message...");
- User32.PostCloseMessageToShell();
-
- while (!shellProcess.HasExited)
- {
- shellProcess.Refresh();
- Thread.Sleep(20);
- }
-
- logger.Info($"Successfully terminated explorer shell process with PID = {processId}.");
- }
- else
- {
- logger.Info("The explorer shell seems to already be terminated. Skipping this step...");
- }
+ logger.Info("Stopped monitoring 'explorer.exe'.");
}
private void ExplorerWatcher_EventArrived(object sender, EventArrivedEventArgs e)
diff --git a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
index 7c7308ff..4b2e9b30 100644
--- a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
+++ b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
@@ -68,10 +68,6 @@
{47da5933-bef8-4729-94e6-abde2db12262}
SafeExamBrowser.Contracts
-
- {73724659-4150-4792-a94e-42f5f3c1b696}
- SafeExamBrowser.WindowsApi
-
diff --git a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
index ac5df2a6..adc2df70 100644
--- a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
+++ b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
@@ -10,30 +10,36 @@ using System;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
-using SafeExamBrowser.WindowsApi;
+using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Monitoring.Windows
{
public class WindowMonitor : IWindowMonitor
{
+ private IntPtr captureStartHookHandle;
+ private IntPtr foregroundHookHandle;
private ILogger logger;
private IList minimizedWindows = new List();
+ private INativeMethods nativeMethods;
- public WindowMonitor(ILogger logger)
+ public event WindowChangedHandler WindowChanged;
+
+ public WindowMonitor(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
+ this.nativeMethods = nativeMethods;
}
public void HideAllWindows()
{
logger.Info("Saving windows to be minimized...");
- foreach (var handle in User32.GetOpenWindows())
+ foreach (var handle in nativeMethods.GetOpenWindows())
{
var window = new Window
{
Handle = handle,
- Title = User32.GetWindowTitle(handle)
+ Title = nativeMethods.GetWindowTitle(handle)
};
minimizedWindows.Add(window);
@@ -41,7 +47,7 @@ namespace SafeExamBrowser.Monitoring.Windows
}
logger.Info("Minimizing all open windows...");
- User32.MinimizeAllOpenWindows();
+ nativeMethods.MinimizeAllOpenWindows();
logger.Info("Open windows successfully minimized.");
}
@@ -51,7 +57,7 @@ namespace SafeExamBrowser.Monitoring.Windows
foreach (var window in minimizedWindows)
{
- User32.RestoreWindow(window.Handle);
+ nativeMethods.RestoreWindow(window.Handle);
logger.Info($"Restored window '{window.Title}' with handle = {window.Handle}.");
}
@@ -60,12 +66,42 @@ namespace SafeExamBrowser.Monitoring.Windows
public void StartMonitoringWindows()
{
- // TODO
+ captureStartHookHandle = nativeMethods.RegisterSystemCaptureStartEvent(OnWindowChanged);
+ logger.Info($"Registered system capture start event with handle = {captureStartHookHandle}.");
+
+ foregroundHookHandle = nativeMethods.RegisterSystemForegroundEvent(OnWindowChanged);
+ logger.Info($"Registered system foreground event with handle = {foregroundHookHandle}.");
}
public void StopMonitoringWindows()
{
- // TODO
+ if (captureStartHookHandle != IntPtr.Zero)
+ {
+ nativeMethods.UnregisterSystemEvent(captureStartHookHandle);
+ logger.Info($"Unregistered system capture start event with handle = {captureStartHookHandle}.");
+ }
+
+ if (foregroundHookHandle != IntPtr.Zero)
+ {
+ nativeMethods.UnregisterSystemEvent(foregroundHookHandle);
+ logger.Info($"Unregistered system foreground event with handle = {foregroundHookHandle}.");
+ }
+ }
+
+ private void OnWindowChanged(IntPtr window)
+ {
+ if (WindowChanged != null)
+ {
+ WindowChanged.Invoke(window, out bool hide);
+
+ if (hide)
+ {
+ var title = nativeMethods.GetWindowTitle(window);
+
+ nativeMethods.HideWindow(window);
+ logger.Info($"Hid window '{title}' with handle = {window}.");
+ }
+ }
}
private struct Window
diff --git a/SafeExamBrowser.WindowsApi/Constants/Constant.cs b/SafeExamBrowser.WindowsApi/Constants/Constant.cs
index 61af4425..9451edba 100644
--- a/SafeExamBrowser.WindowsApi/Constants/Constant.cs
+++ b/SafeExamBrowser.WindowsApi/Constants/Constant.cs
@@ -8,13 +8,45 @@
namespace SafeExamBrowser.WindowsApi.Constants
{
- static class Constant
+ internal static class Constant
{
- internal const int WM_COMMAND = 0x111;
+ ///
+ /// A window has received mouse capture. This event is sent by the system, never by servers.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx.
+ ///
+ internal const uint EVENT_SYSTEM_CAPTURESTART = 0x8;
+
+ ///
+ /// The foreground window has changed. The system sends this event even if the foreground window has changed to another window in
+ /// the same thread. Server applications never send this event.
+ /// For this event, the WinEventProc callback function's hwnd parameter is the handle to the window that is in the foreground, the
+ /// idObject parameter is OBJID_WINDOW, and the idChild parameter is CHILDID_SELF.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx.
+ ///
+ internal const uint EVENT_SYSTEM_FOREGROUND = 0x3;
///
/// Minimize all open windows.
///
internal const int MIN_ALL = 419;
+
+ ///
+ /// The callback function is not mapped into the address space of the process that generates the event. Because the hook function
+ /// is called across process boundaries, the system must queue events. Although this method is asynchronous, events are guaranteed
+ /// to be in sequential order.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd373640(v=vs.85).aspx.
+ ///
+ internal const uint WINEVENT_OUTOFCONTEXT = 0x0;
+
+ ///
+ /// Sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or
+ /// when an accelerator keystroke is translated.
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms647591(v=vs.85).aspx.
+ ///
+ internal const int WM_COMMAND = 0x111;
}
}
diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs
new file mode 100644
index 00000000..7244d2d7
--- /dev/null
+++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs
@@ -0,0 +1,184 @@
+/*
+* 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.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Text;
+using SafeExamBrowser.Contracts.WindowsApi;
+using SafeExamBrowser.Contracts.WindowsApi.Types;
+using SafeExamBrowser.WindowsApi.Constants;
+
+namespace SafeExamBrowser.WindowsApi
+{
+ public class NativeMethods : INativeMethods
+ {
+ private ConcurrentDictionary EventDelegates = new ConcurrentDictionary();
+
+ public IEnumerable GetOpenWindows()
+ {
+ var windows = new List();
+ var success = User32.EnumWindows(delegate (IntPtr hWnd, IntPtr lParam)
+ {
+ if (hWnd != GetShellWindowHandle() && User32.IsWindowVisible(hWnd) && User32.GetWindowTextLength(hWnd) > 0)
+ {
+ windows.Add(hWnd);
+ }
+
+ return true;
+ }, IntPtr.Zero);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ return windows;
+ }
+
+ public uint GetProcessIdFor(IntPtr window)
+ {
+ User32.GetWindowThreadProcessId(window, out uint processId);
+
+ return processId;
+ }
+
+ public IntPtr GetShellWindowHandle()
+ {
+ return User32.FindWindow("Shell_TrayWnd", null);
+ }
+
+ public uint GetShellProcessId()
+ {
+ var handle = GetShellWindowHandle();
+ var threadId = User32.GetWindowThreadProcessId(handle, out uint processId);
+
+ return processId;
+ }
+
+ public string GetWindowTitle(IntPtr window)
+ {
+ var length = User32.GetWindowTextLength(window);
+
+ if (length > 0)
+ {
+ var builder = new StringBuilder(length);
+
+ User32.GetWindowText(window, builder, length + 1);
+
+ return builder.ToString();
+ }
+
+ return string.Empty;
+ }
+
+ public RECT GetWorkingArea()
+ {
+ var workingArea = new RECT();
+ var success = User32.SystemParametersInfo(SPI.GETWORKAREA, 0, ref workingArea, SPIF.NONE);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ return workingArea;
+ }
+
+ public void HideWindow(IntPtr window)
+ {
+ User32.ShowWindow(window, (int)ShowWindowCommand.Hide);
+ }
+
+ public void MinimizeAllOpenWindows()
+ {
+ var handle = GetShellWindowHandle();
+
+ User32.SendMessage(handle, Constant.WM_COMMAND, (IntPtr)Constant.MIN_ALL, IntPtr.Zero);
+ }
+
+ public void PostCloseMessageToShell()
+ {
+ // NOTE: 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
+
+ var handle = GetShellWindowHandle();
+ var success = User32.PostMessage(handle, 0x5B4, IntPtr.Zero, IntPtr.Zero);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ }
+
+ public IntPtr RegisterSystemForegroundEvent(Action callback)
+ {
+ WinEventDelegate eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
+ {
+ callback(hwnd);
+ };
+
+ var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_FOREGROUND, Constant.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
+
+ // IMORTANT:
+ // Ensures that the callback does not get garbage collected prematurely, as it will be passed to unmanaged code!
+ // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash.
+ EventDelegates[handle] = eventProc;
+
+ return handle;
+ }
+
+ public IntPtr RegisterSystemCaptureStartEvent(Action callback)
+ {
+ WinEventDelegate eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
+ {
+ callback(hwnd);
+ };
+
+ var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
+
+ // IMORTANT:
+ // Ensures that the callback does not get garbage collected prematurely, as it will be passed to unmanaged code!
+ // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash.
+ EventDelegates[handle] = eventProc;
+
+ return handle;
+ }
+
+ public void RestoreWindow(IntPtr window)
+ {
+ User32.ShowWindow(window, (int)ShowWindowCommand.Restore);
+ }
+
+ public void SetWorkingArea(RECT bounds)
+ {
+ var success = User32.SystemParametersInfo(SPI.SETWORKAREA, 0, ref bounds, SPIF.UPDATEANDCHANGE);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ }
+
+ public void UnregisterSystemEvent(IntPtr handle)
+ {
+ var success = User32.UnhookWinEvent(handle);
+
+ if (!success)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ else
+ {
+ EventDelegates.TryRemove(handle, out WinEventDelegate d);
+ }
+ }
+ }
+}
diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
index 514773e0..2d9401db 100644
--- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
+++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
@@ -60,11 +60,17 @@
+
-
+
+
+ {47DA5933-BEF8-4729-94E6-ABDE2DB12262}
+ SafeExamBrowser.Contracts
+
+
\ No newline at end of file
diff --git a/SafeExamBrowser.WindowsApi/User32.cs b/SafeExamBrowser.WindowsApi/User32.cs
index b5562b3a..625001f5 100644
--- a/SafeExamBrowser.WindowsApi/User32.cs
+++ b/SafeExamBrowser.WindowsApi/User32.cs
@@ -7,193 +7,60 @@
*/
using System;
-using System.Collections.Generic;
-using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
+using SafeExamBrowser.Contracts.WindowsApi.Types;
using SafeExamBrowser.WindowsApi.Constants;
-using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
+ internal delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);
+ internal delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
+
///
/// Provides access to the native Windows API exposed by user32.dll.
///
- public static class User32
+ internal static class User32
{
- ///
- /// Retrieves a collection of handles to all currently open (i.e. visible) windows.
- ///
- public static IEnumerable GetOpenWindows()
- {
- var windows = new List();
- var success = EnumWindows(delegate (IntPtr hWnd, IntPtr lParam)
- {
- if (hWnd != GetShellWindowHandle() && IsWindowVisible(hWnd) && GetWindowTextLength(hWnd) > 0)
- {
- windows.Add(hWnd);
- }
-
- return true;
- }, IntPtr.Zero);
-
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
-
- return windows;
- }
-
- ///
- /// Retrieves a window handle to the Windows taskbar. Returns IntPtr.Zero
- /// if the taskbar could not be found (i.e. if it isn't running).
- ///
- public static IntPtr GetShellWindowHandle()
- {
- return FindWindow("Shell_TrayWnd", null);
- }
-
- ///
- /// Retrieves the process ID of the main Windows explorer instance controlling
- /// desktop and taskbar or 0, if the process isn't running.
- ///
- public static uint GetShellProcessId()
- {
- var handle = GetShellWindowHandle();
- var threadId = GetWindowThreadProcessId(handle, out uint processId);
-
- return processId;
- }
-
- ///
- /// Retrieves the title of the specified window, or an empty string, if the
- /// given window does not have a title.
- ///
- public static string GetWindowTitle(IntPtr window)
- {
- var length = GetWindowTextLength(window);
-
- if (length > 0)
- {
- var builder = new StringBuilder(length);
-
- GetWindowText(window, builder, length + 1);
-
- return builder.ToString();
- }
-
- return string.Empty;
- }
-
- ///
- /// Retrieves the currently configured working area of the primary screen.
- ///
- ///
- /// If the working area could not be retrieved.
- ///
- public static RECT GetWorkingArea()
- {
- var workingArea = new RECT();
- var success = SystemParametersInfo(SPI.GETWORKAREA, 0, ref workingArea, SPIF.NONE);
-
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
-
- return workingArea;
- }
-
- ///
- /// Minimizes all open windows.
- ///
- public static void MinimizeAllOpenWindows()
- {
- var handle = GetShellWindowHandle();
-
- SendMessage(handle, Constant.WM_COMMAND, (IntPtr) Constant.MIN_ALL, IntPtr.Zero);
- }
-
- ///
- /// Instructs the main Windows explorer process to shut down.
- ///
- ///
- /// If the messge could not be successfully posted. Does not apply if the process isn't running!
- ///
- ///
- /// 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
- ///
- public static void PostCloseMessageToShell()
- {
- var handle = GetShellWindowHandle();
- var success = PostMessage(handle, 0x5B4, IntPtr.Zero, IntPtr.Zero);
-
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
- }
-
- ///
- /// Restores the specified window to its original size and position.
- ///
- public static void RestoreWindow(IntPtr window)
- {
- ShowWindow(window, (int) ShowWindowCommand.Restore);
- }
-
- ///
- /// Sets the working area of the primary screen according to the given dimensions.
- ///
- ///
- /// If the working area could not be set.
- ///
- public static void SetWorkingArea(RECT workingArea)
- {
- var success = SystemParametersInfo(SPI.SETWORKAREA, 0, ref workingArea, SPIF.UPDATEANDCHANGE);
-
- if (!success)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
- }
-
- private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
-
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
+ internal static extern bool EnumWindows(EnumWindowsDelegate enumProc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
+ internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
+ internal static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", SetLastError = true)]
- private static extern int GetWindowTextLength(IntPtr hWnd);
+ internal static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
- private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+ internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool IsWindowVisible(IntPtr hWnd);
+ internal static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
-
- [DllImport("user32.dll", SetLastError = true, EntryPoint = "SendMessage")]
- private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+ internal static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
- private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+ internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref RECT pvParam, SPIF fWinIni);
+ internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref RECT pvParam, SPIF fWinIni);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
}
}
diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs
index d9c61164..7ad71073 100644
--- a/SafeExamBrowser/CompositionRoot.cs
+++ b/SafeExamBrowser/CompositionRoot.cs
@@ -15,6 +15,7 @@ using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
+using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.Core.Behaviour;
using SafeExamBrowser.Core.Behaviour.Operations;
using SafeExamBrowser.Core.I18n;
@@ -22,6 +23,7 @@ using SafeExamBrowser.Core.Logging;
using SafeExamBrowser.Monitoring.Processes;
using SafeExamBrowser.Monitoring.Windows;
using SafeExamBrowser.UserInterface;
+using SafeExamBrowser.WindowsApi;
namespace SafeExamBrowser
{
@@ -31,6 +33,7 @@ namespace SafeExamBrowser
private IApplicationInfo browserInfo;
private IEventController eventController;
private ILogger logger;
+ private INativeMethods nativeMethods;
private INotificationInfo aboutInfo;
private IProcessMonitor processMonitor;
private ISettings settings;
@@ -49,6 +52,7 @@ namespace SafeExamBrowser
{
browserInfo = new BrowserApplicationInfo();
logger = new Logger();
+ nativeMethods = new NativeMethods();
settings = new Settings();
Taskbar = new Taskbar();
textResource = new XmlTextResource();
@@ -59,10 +63,10 @@ namespace SafeExamBrowser
text = new Text(textResource);
aboutInfo = new AboutNotificationInfo(text);
browserController = new BrowserApplicationController(settings, uiFactory);
- processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)));
- windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)));
- workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea)));
- eventController = new EventController(new ModuleLogger(logger, typeof(EventController)), processMonitor, Taskbar, workingArea);
+ processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
+ windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
+ workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea)), nativeMethods);
+ eventController = new EventController(new ModuleLogger(logger, typeof(EventController)), processMonitor, Taskbar, windowMonitor, workingArea);
ShutdownController = new ShutdownController(logger, settings, text, uiFactory);
StartupController = new StartupController(logger, settings, text, uiFactory);
diff --git a/SafeExamBrowser/SafeExamBrowser.csproj b/SafeExamBrowser/SafeExamBrowser.csproj
index 380ae343..3ba34103 100644
--- a/SafeExamBrowser/SafeExamBrowser.csproj
+++ b/SafeExamBrowser/SafeExamBrowser.csproj
@@ -143,6 +143,10 @@
{e1be031a-4354-41e7-83e8-843ded4489ff}
SafeExamBrowser.UserInterface
+
+ {73724659-4150-4792-A94E-42F5F3C1B696}
+ SafeExamBrowser.WindowsApi
+