SEBWIN-220: Re-integrated system event hooks, now also running in separate threads. Decreased logging for EndpointNotFoundException in BaseProxy.
This commit is contained in:
parent
b50c208f46
commit
67ba5fcce3
20 changed files with 308 additions and 121 deletions
|
@ -9,6 +9,7 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Client.Operations;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
|
||||
|
@ -20,21 +21,58 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
|||
private Mock<ILogger> loggerMock;
|
||||
private Mock<IWindowMonitor> windowMonitorMock;
|
||||
|
||||
private WindowMonitorOperation sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
loggerMock = new Mock<ILogger>();
|
||||
windowMonitorMock = new Mock<IWindowMonitor>();
|
||||
|
||||
sut = new WindowMonitorOperation(loggerMock.Object, windowMonitorMock.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustPerformCorrectly()
|
||||
public void MustPerformCorrectlyForCreateNewDesktop()
|
||||
{
|
||||
var order = 0;
|
||||
var hideAll = 0;
|
||||
var startMonitoring = 0;
|
||||
var sut = new WindowMonitorOperation(KioskMode.CreateNewDesktop, loggerMock.Object, windowMonitorMock.Object);
|
||||
|
||||
windowMonitorMock.Setup(w => w.HideAllWindows()).Callback(() => hideAll = ++order);
|
||||
windowMonitorMock.Setup(w => w.StartMonitoringWindows()).Callback(() => startMonitoring = ++order);
|
||||
|
||||
sut.Perform();
|
||||
|
||||
windowMonitorMock.Verify(w => w.HideAllWindows(), Times.Never);
|
||||
windowMonitorMock.Verify(w => w.StartMonitoringWindows(), Times.Once);
|
||||
|
||||
Assert.AreEqual(0, hideAll);
|
||||
Assert.AreEqual(1, startMonitoring);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustRevertCorrectlyForCreateNewDesktop()
|
||||
{
|
||||
var order = 0;
|
||||
var stop = 0;
|
||||
var restore = 0;
|
||||
var sut = new WindowMonitorOperation(KioskMode.CreateNewDesktop, loggerMock.Object, windowMonitorMock.Object);
|
||||
|
||||
windowMonitorMock.Setup(w => w.StopMonitoringWindows()).Callback(() => stop = ++order);
|
||||
windowMonitorMock.Setup(w => w.RestoreHiddenWindows()).Callback(() => restore = ++order);
|
||||
|
||||
sut.Revert();
|
||||
|
||||
windowMonitorMock.Verify(w => w.StopMonitoringWindows(), Times.Once);
|
||||
windowMonitorMock.Verify(w => w.RestoreHiddenWindows(), Times.Never);
|
||||
|
||||
Assert.AreEqual(0, restore);
|
||||
Assert.AreEqual(1, stop);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustPerformCorrectlyForDisableExplorerShell()
|
||||
{
|
||||
var order = 0;
|
||||
var sut = new WindowMonitorOperation(KioskMode.DisableExplorerShell, loggerMock.Object, windowMonitorMock.Object);
|
||||
|
||||
windowMonitorMock.Setup(w => w.HideAllWindows()).Callback(() => Assert.AreEqual(++order, 1));
|
||||
windowMonitorMock.Setup(w => w.StartMonitoringWindows()).Callback(() => Assert.AreEqual(++order, 2));
|
||||
|
@ -46,9 +84,10 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustRevertCorrectly()
|
||||
public void MustRevertCorrectlyForDisableExplorerShell()
|
||||
{
|
||||
var order = 0;
|
||||
var sut = new WindowMonitorOperation(KioskMode.DisableExplorerShell, loggerMock.Object, windowMonitorMock.Object);
|
||||
|
||||
windowMonitorMock.Setup(w => w.StopMonitoringWindows()).Callback(() => Assert.AreEqual(++order, 1));
|
||||
windowMonitorMock.Setup(w => w.RestoreHiddenWindows()).Callback(() => Assert.AreEqual(++order, 2));
|
||||
|
@ -58,5 +97,13 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
|||
windowMonitorMock.Verify(w => w.StopMonitoringWindows(), Times.Once);
|
||||
windowMonitorMock.Verify(w => w.RestoreHiddenWindows(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDoNothingWithoutKioskMode()
|
||||
{
|
||||
var sut = new WindowMonitorOperation(KioskMode.None, loggerMock.Object, windowMonitorMock.Object);
|
||||
|
||||
windowMonitorMock.VerifyNoOtherCalls();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,17 +307,19 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private void WindowMonitor_WindowChanged(IntPtr window)
|
||||
{
|
||||
var allowed = processMonitor.BelongsToAllowedProcess(window);
|
||||
// TODO!
|
||||
|
||||
if (!allowed)
|
||||
{
|
||||
var success = windowMonitor.Hide(window);
|
||||
//var allowed = processMonitor.BelongsToAllowedProcess(window);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
windowMonitor.Close(window);
|
||||
}
|
||||
}
|
||||
//if (!allowed)
|
||||
//{
|
||||
// var success = windowMonitor.Hide(window);
|
||||
|
||||
// if (!success)
|
||||
// {
|
||||
// windowMonitor.Close(window);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ namespace SafeExamBrowser.Client
|
|||
private IText text;
|
||||
private ITextResource textResource;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
private IWindowMonitor windowMonitor;
|
||||
|
||||
internal IClientController ClientController { get; private set; }
|
||||
internal Taskbar Taskbar { get; private set; }
|
||||
|
@ -83,10 +84,10 @@ namespace SafeExamBrowser.Client
|
|||
processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods);
|
||||
uiFactory = new UserInterfaceFactory(text);
|
||||
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy)));
|
||||
windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods);
|
||||
|
||||
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, nameof(DisplayMonitor)), nativeMethods);
|
||||
var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods);
|
||||
var windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods);
|
||||
|
||||
Taskbar = new Taskbar(new ModuleLogger(logger, nameof(Taskbar)));
|
||||
|
||||
|
@ -98,8 +99,7 @@ namespace SafeExamBrowser.Client
|
|||
operations.Enqueue(new DelegateOperation(UpdateAppConfig));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildCommunicationHostOperation));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation));
|
||||
// TODO
|
||||
//operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildWindowMonitorOperation));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildProcessMonitorOperation));
|
||||
operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar));
|
||||
operations.Enqueue(new LazyInitializationOperation(BuildTaskbarOperation));
|
||||
|
@ -223,6 +223,11 @@ namespace SafeExamBrowser.Client
|
|||
return operation;
|
||||
}
|
||||
|
||||
private IOperation BuildWindowMonitorOperation()
|
||||
{
|
||||
return new WindowMonitorOperation(configuration.Settings.KioskMode, logger, windowMonitor);
|
||||
}
|
||||
|
||||
private void UpdateAppConfig()
|
||||
{
|
||||
ClientController.AppConfig = configuration.AppConfig;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.I18n;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -16,13 +17,15 @@ namespace SafeExamBrowser.Client.Operations
|
|||
{
|
||||
internal class WindowMonitorOperation : IOperation
|
||||
{
|
||||
private KioskMode kioskMode;
|
||||
private ILogger logger;
|
||||
private IWindowMonitor windowMonitor;
|
||||
|
||||
public IProgressIndicator ProgressIndicator { private get; set; }
|
||||
|
||||
public WindowMonitorOperation(ILogger logger, IWindowMonitor windowMonitor)
|
||||
public WindowMonitorOperation(KioskMode kioskMode, ILogger logger, IWindowMonitor windowMonitor)
|
||||
{
|
||||
this.kioskMode = kioskMode;
|
||||
this.logger = logger;
|
||||
this.windowMonitor = windowMonitor;
|
||||
}
|
||||
|
@ -32,8 +35,15 @@ namespace SafeExamBrowser.Client.Operations
|
|||
logger.Info("Initializing window monitoring...");
|
||||
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeWindowMonitoring);
|
||||
|
||||
windowMonitor.HideAllWindows();
|
||||
windowMonitor.StartMonitoringWindows();
|
||||
if (kioskMode == KioskMode.DisableExplorerShell)
|
||||
{
|
||||
windowMonitor.HideAllWindows();
|
||||
}
|
||||
|
||||
if (kioskMode != KioskMode.None)
|
||||
{
|
||||
windowMonitor.StartMonitoringWindows();
|
||||
}
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
@ -48,8 +58,15 @@ namespace SafeExamBrowser.Client.Operations
|
|||
logger.Info("Stopping window monitoring...");
|
||||
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopWindowMonitoring);
|
||||
|
||||
windowMonitor.StopMonitoringWindows();
|
||||
windowMonitor.RestoreHiddenWindows();
|
||||
if (kioskMode != KioskMode.None)
|
||||
{
|
||||
windowMonitor.StopMonitoringWindows();
|
||||
}
|
||||
|
||||
if (kioskMode == KioskMode.DisableExplorerShell)
|
||||
{
|
||||
windowMonitor.RestoreHiddenWindows();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,24 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
|||
Assert.IsFalse(connected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustHandleMissingEndpointCorrectly()
|
||||
{
|
||||
var proxy = new Mock<IProxyObject>();
|
||||
|
||||
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Throws<EndpointNotFoundException>();
|
||||
|
||||
var token = Guid.NewGuid();
|
||||
var connected = sut.Connect(token);
|
||||
|
||||
logger.Verify(l => l.Warn(It.IsAny<string>()), Times.AtLeastOnce());
|
||||
logger.Verify(l => l.Error(It.IsAny<string>()), Times.Never());
|
||||
logger.Verify(l => l.Error(It.IsAny<string>(), It.IsAny<Exception>()), Times.Never());
|
||||
proxyObjectFactory.Verify(f => f.CreateObject(It.IsAny<string>()), Times.Once);
|
||||
|
||||
Assert.IsFalse(connected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustFailToDisconnectIfNotConnected()
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
|
||||
public abstract class BaseHost : ICommunication, ICommunicationHost
|
||||
{
|
||||
private const int TWO_SECONDS = 2000;
|
||||
private const int FIVE_SECONDS = 5000;
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private string address;
|
||||
|
@ -137,11 +137,11 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
hostThread.IsBackground = true;
|
||||
hostThread.Start();
|
||||
|
||||
var success = startedEvent.WaitOne(TWO_SECONDS);
|
||||
var success = startedEvent.WaitOne(FIVE_SECONDS);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {TWO_SECONDS / 1000} seconds!", exception);
|
||||
throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {FIVE_SECONDS / 1000} seconds!", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
try
|
||||
{
|
||||
host?.Close();
|
||||
success = hostThread?.Join(TWO_SECONDS) == true;
|
||||
success = hostThread?.Join(FIVE_SECONDS) == true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -64,12 +64,16 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
|
||||
return success;
|
||||
}
|
||||
catch (EndpointNotFoundException)
|
||||
{
|
||||
Logger.Warn($"Endpoint '{address}' could not be found!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Failed to connect to endpoint '{address}'!", e);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool Disconnect()
|
||||
|
@ -209,7 +213,7 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
|
||||
private void BaseProxy_Faulted(object sender, EventArgs e)
|
||||
{
|
||||
Logger.Error("Communication channel has faulted!");
|
||||
Logger.Warn("Communication channel has faulted!");
|
||||
}
|
||||
|
||||
private void BaseProxy_Opened(object sender, EventArgs e)
|
||||
|
|
|
@ -181,6 +181,7 @@
|
|||
<Compile Include="UserInterface\MessageBox\MessageBoxAction.cs" />
|
||||
<Compile Include="UserInterface\MessageBox\MessageBoxIcon.cs" />
|
||||
<Compile Include="WindowsApi\Events\ProcessTerminatedEventHandler.cs" />
|
||||
<Compile Include="WindowsApi\Events\SystemEventCallback.cs" />
|
||||
<Compile Include="WindowsApi\IBounds.cs" />
|
||||
<Compile Include="WindowsApi\IDesktop.cs" />
|
||||
<Compile Include="WindowsApi\IDesktopFactory.cs" />
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.WindowsApi.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the callback for system event hooks.
|
||||
/// </summary>
|
||||
public delegate void SystemEventCallback(IntPtr window);
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
using SafeExamBrowser.Contracts.WindowsApi.Events;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.WindowsApi
|
||||
{
|
||||
|
@ -34,12 +35,12 @@ namespace SafeExamBrowser.Contracts.WindowsApi
|
|||
void DeregisterMouseHook(IMouseInterceptor interceptor);
|
||||
|
||||
/// <summary>
|
||||
/// Deregisters a previously registered system event.
|
||||
/// Deregisters a previously registered system event hook.
|
||||
/// </summary>
|
||||
/// <exception cref="System.ComponentModel.Win32Exception">
|
||||
/// If the event hook could not be successfully removed.
|
||||
/// </exception>
|
||||
void DeregisterSystemEvent(IntPtr handle);
|
||||
void DeregisterSystemEventHook(Guid hookId);
|
||||
|
||||
/// <summary>
|
||||
/// Empties the clipboard.
|
||||
|
@ -128,17 +129,17 @@ namespace SafeExamBrowser.Contracts.WindowsApi
|
|||
/// </summary>
|
||||
void RegisterMouseHook(IMouseInterceptor interceptor);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a system event which will invoke the specified callback when a window has received mouse capture.
|
||||
/// Returns the ID of the newly registered Windows event hook.
|
||||
/// </summary>
|
||||
Guid RegisterSystemCaptureStartEvent(SystemEventCallback callback);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback);
|
||||
Guid RegisterSystemForegroundEvent(SystemEventCallback callback);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the currently configured desktop wallpaper.
|
||||
|
|
|
@ -35,9 +35,11 @@ namespace SafeExamBrowser.Logging
|
|||
{
|
||||
var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
var severity = message.Severity.ToString().ToUpper();
|
||||
var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}";
|
||||
var threadId = message.ThreadInfo.Id < 10 ? $"0{message.ThreadInfo.Id}" : message.ThreadInfo.Id.ToString();
|
||||
var threadName = message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty;
|
||||
var threadInfo = $"[{threadId}{threadName}]";
|
||||
|
||||
return $"{date} [{threadInfo}] - {severity}: {message.Message}";
|
||||
return $"{date} {threadInfo} - {severity}: {message.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ namespace SafeExamBrowser.Monitoring.Windows
|
|||
{
|
||||
public class WindowMonitor : IWindowMonitor
|
||||
{
|
||||
private IntPtr captureStartHookHandle;
|
||||
private IntPtr foregroundHookHandle;
|
||||
private IntPtr activeWindow;
|
||||
private Guid? captureHookId;
|
||||
private Guid? foregroundHookId;
|
||||
private ILogger logger;
|
||||
private IList<Window> minimizedWindows = new List<Window>();
|
||||
private INativeMethods nativeMethods;
|
||||
|
@ -92,31 +93,36 @@ namespace SafeExamBrowser.Monitoring.Windows
|
|||
|
||||
public void StartMonitoringWindows()
|
||||
{
|
||||
captureStartHookHandle = nativeMethods.RegisterSystemCaptureStartEvent(OnWindowChanged);
|
||||
logger.Info($"Registered system capture start event with handle = {captureStartHookHandle}.");
|
||||
captureHookId = nativeMethods.RegisterSystemCaptureStartEvent(OnWindowChanged);
|
||||
logger.Info($"Registered system capture start event with ID = {captureHookId}.");
|
||||
|
||||
foregroundHookHandle = nativeMethods.RegisterSystemForegroundEvent(OnWindowChanged);
|
||||
logger.Info($"Registered system foreground event with handle = {foregroundHookHandle}.");
|
||||
foregroundHookId = nativeMethods.RegisterSystemForegroundEvent(OnWindowChanged);
|
||||
logger.Info($"Registered system foreground event with ID = {foregroundHookId}.");
|
||||
}
|
||||
|
||||
public void StopMonitoringWindows()
|
||||
{
|
||||
if (captureStartHookHandle != IntPtr.Zero)
|
||||
if (captureHookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterSystemEvent(captureStartHookHandle);
|
||||
logger.Info($"Unregistered system capture start event with handle = {captureStartHookHandle}.");
|
||||
nativeMethods.DeregisterSystemEventHook(captureHookId.Value);
|
||||
logger.Info($"Unregistered system capture start event with ID = {captureHookId}.");
|
||||
}
|
||||
|
||||
if (foregroundHookHandle != IntPtr.Zero)
|
||||
if (foregroundHookId.HasValue)
|
||||
{
|
||||
nativeMethods.DeregisterSystemEvent(foregroundHookHandle);
|
||||
logger.Info($"Unregistered system foreground event with handle = {foregroundHookHandle}.");
|
||||
nativeMethods.DeregisterSystemEventHook(foregroundHookId.Value);
|
||||
logger.Info($"Unregistered system foreground event with ID = {foregroundHookId}.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowChanged(IntPtr window)
|
||||
{
|
||||
WindowChanged?.Invoke(window);
|
||||
if (activeWindow != window)
|
||||
{
|
||||
logger.Debug($"Window has changed from {activeWindow} to {window}.");
|
||||
activeWindow = window;
|
||||
WindowChanged?.Invoke(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace SafeExamBrowser.Runtime
|
|||
|
||||
internal void BuildObjectGraph(Action shutdown)
|
||||
{
|
||||
const int STARTUP_TIMEOUT_MS = 30000;
|
||||
const int STARTUP_TIMEOUT_MS = 15000;
|
||||
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
var configuration = BuildConfigurationRepository();
|
||||
|
|
|
@ -139,7 +139,7 @@ namespace SafeExamBrowser.Runtime
|
|||
runtimeWindow.Show();
|
||||
runtimeWindow.BringToForeground();
|
||||
runtimeWindow.ShowProgressBar();
|
||||
logger.Info("Initiating session procedure...");
|
||||
logger.Info("### --- Session Start Procedure --- ###");
|
||||
|
||||
if (sessionRunning)
|
||||
{
|
||||
|
@ -152,7 +152,7 @@ namespace SafeExamBrowser.Runtime
|
|||
{
|
||||
RegisterSessionEvents();
|
||||
|
||||
logger.Info("Session is running.");
|
||||
logger.Info("### --- Session Running --- ###");
|
||||
runtimeWindow.HideProgressBar();
|
||||
runtimeWindow.UpdateText(TextKey.RuntimeWindow_ApplicationRunning);
|
||||
runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None;
|
||||
|
@ -166,7 +166,7 @@ namespace SafeExamBrowser.Runtime
|
|||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Session procedure {(result == OperationResult.Aborted ? "was aborted." : "has failed!")}");
|
||||
logger.Info($"### --- Session Start {(result == OperationResult.Aborted ? "Aborted" : "Failed")} --- ###");
|
||||
|
||||
if (result == OperationResult.Failed)
|
||||
{
|
||||
|
@ -187,7 +187,7 @@ namespace SafeExamBrowser.Runtime
|
|||
runtimeWindow.Show();
|
||||
runtimeWindow.BringToForeground();
|
||||
runtimeWindow.ShowProgressBar();
|
||||
logger.Info("Reverting session operations...");
|
||||
logger.Info("### --- Session Stop Procedure --- ###");
|
||||
|
||||
DeregisterSessionEvents();
|
||||
|
||||
|
@ -195,12 +195,12 @@ namespace SafeExamBrowser.Runtime
|
|||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Session is terminated.");
|
||||
logger.Info("### --- Session Terminated --- ###");
|
||||
sessionRunning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Session reversion was erroneous!");
|
||||
logger.Info("### --- Session Stop Failed --- ###");
|
||||
messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,9 +70,11 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels
|
|||
{
|
||||
var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
var severity = message.Severity.ToString().ToUpper();
|
||||
var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}";
|
||||
var threadId = message.ThreadInfo.Id < 10 ? $"0{message.ThreadInfo.Id}" : message.ThreadInfo.Id.ToString();
|
||||
var threadName = message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty;
|
||||
var threadInfo = $"[{threadId}{threadName}]";
|
||||
|
||||
var infoRun = new Run($"{date} [{threadInfo}] - ") { Foreground = Brushes.Gray };
|
||||
var infoRun = new Run($"{date} {threadInfo} - ") { Foreground = Brushes.Gray };
|
||||
var messageRun = new Run($"{severity}: {message.Message}{Environment.NewLine}") { Foreground = GetBrushFor(message.Severity) };
|
||||
|
||||
textBlock.Inlines.Add(infoRun);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
using SafeExamBrowser.WindowsApi.Constants;
|
||||
using SafeExamBrowser.WindowsApi.Delegates;
|
||||
|
@ -25,15 +24,13 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
private const int DELETE = 46;
|
||||
|
||||
private bool altPressed, ctrlPressed;
|
||||
private HookDelegate hookProc;
|
||||
private HookDelegate hookDelegate;
|
||||
|
||||
internal IntPtr Handle { get; private set; }
|
||||
internal AutoResetEvent InputEvent { get; private set; }
|
||||
internal IKeyboardInterceptor Interceptor { get; private set; }
|
||||
|
||||
internal KeyboardHook(IKeyboardInterceptor interceptor)
|
||||
{
|
||||
InputEvent = new AutoResetEvent(false);
|
||||
Interceptor = interceptor;
|
||||
}
|
||||
|
||||
|
@ -46,9 +43,9 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
// IMORTANT:
|
||||
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
|
||||
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
hookProc = new HookDelegate(LowLevelKeyboardProc);
|
||||
hookDelegate = new HookDelegate(LowLevelKeyboardProc);
|
||||
|
||||
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, moduleHandle, 0);
|
||||
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookDelegate, moduleHandle, 0);
|
||||
}
|
||||
|
||||
internal bool Detach()
|
||||
|
@ -58,8 +55,6 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
|
||||
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
InputEvent.Set();
|
||||
|
||||
if (nCode >= 0)
|
||||
{
|
||||
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
using SafeExamBrowser.WindowsApi.Constants;
|
||||
using SafeExamBrowser.WindowsApi.Delegates;
|
||||
|
@ -18,15 +17,13 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
{
|
||||
internal class MouseHook
|
||||
{
|
||||
private HookDelegate hookProc;
|
||||
private HookDelegate hookDelegate;
|
||||
|
||||
internal IntPtr Handle { get; private set; }
|
||||
internal AutoResetEvent InputEvent { get; private set; }
|
||||
internal IMouseInterceptor Interceptor { get; private set; }
|
||||
|
||||
internal MouseHook(IMouseInterceptor interceptor)
|
||||
{
|
||||
InputEvent = new AutoResetEvent(false);
|
||||
Interceptor = interceptor;
|
||||
}
|
||||
|
||||
|
@ -39,9 +36,9 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
// IMORTANT:
|
||||
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
|
||||
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
hookProc = new HookDelegate(LowLevelMouseProc);
|
||||
hookDelegate = new HookDelegate(LowLevelMouseProc);
|
||||
|
||||
Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, moduleHandle, 0);
|
||||
Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookDelegate, moduleHandle, 0);
|
||||
}
|
||||
|
||||
internal bool Detach()
|
||||
|
@ -51,8 +48,6 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
|
|||
|
||||
private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
InputEvent.Set();
|
||||
|
||||
if (nCode >= 0 && !Ignore(wParam.ToInt32()))
|
||||
{
|
||||
var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
|
||||
|
|
67
SafeExamBrowser.WindowsApi/Monitoring/SystemHook.cs
Normal file
67
SafeExamBrowser.WindowsApi/Monitoring/SystemHook.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 System.Threading;
|
||||
using SafeExamBrowser.Contracts.WindowsApi.Events;
|
||||
using SafeExamBrowser.WindowsApi.Constants;
|
||||
using SafeExamBrowser.WindowsApi.Delegates;
|
||||
|
||||
namespace SafeExamBrowser.WindowsApi.Monitoring
|
||||
{
|
||||
internal class SystemHook
|
||||
{
|
||||
private SystemEventCallback callback;
|
||||
private AutoResetEvent detachEvent, detachResultAvailableEvent;
|
||||
private bool detachSuccess;
|
||||
private EventDelegate eventDelegate;
|
||||
private uint eventId;
|
||||
|
||||
internal IntPtr Handle { get; private set; }
|
||||
internal Guid Id { get; private set; }
|
||||
|
||||
public SystemHook(SystemEventCallback callback, uint eventId)
|
||||
{
|
||||
this.callback = callback;
|
||||
this.detachEvent = new AutoResetEvent(false);
|
||||
this.detachResultAvailableEvent = new AutoResetEvent(false);
|
||||
this.eventId = eventId;
|
||||
this.Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
internal void Attach()
|
||||
{
|
||||
// IMORTANT:
|
||||
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
|
||||
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
eventDelegate = new EventDelegate(LowLevelSystemProc);
|
||||
|
||||
Handle = User32.SetWinEventHook(eventId, eventId, IntPtr.Zero, eventDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
|
||||
}
|
||||
|
||||
internal void AwaitDetach()
|
||||
{
|
||||
detachEvent.WaitOne();
|
||||
detachSuccess = User32.UnhookWinEvent(Handle);
|
||||
detachResultAvailableEvent.Set();
|
||||
}
|
||||
|
||||
internal bool Detach()
|
||||
{
|
||||
detachEvent.Set();
|
||||
detachResultAvailableEvent.WaitOne();
|
||||
|
||||
return detachSuccess;
|
||||
}
|
||||
|
||||
private void LowLevelSystemProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
{
|
||||
callback(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,8 +16,8 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Monitoring;
|
||||
using SafeExamBrowser.Contracts.WindowsApi;
|
||||
using SafeExamBrowser.Contracts.WindowsApi.Events;
|
||||
using SafeExamBrowser.WindowsApi.Constants;
|
||||
using SafeExamBrowser.WindowsApi.Delegates;
|
||||
using SafeExamBrowser.WindowsApi.Monitoring;
|
||||
using SafeExamBrowser.WindowsApi.Types;
|
||||
|
||||
|
@ -25,28 +25,28 @@ namespace SafeExamBrowser.WindowsApi
|
|||
{
|
||||
public class NativeMethods : INativeMethods
|
||||
{
|
||||
private ConcurrentDictionary<IntPtr, EventDelegate> EventDelegates = new ConcurrentDictionary<IntPtr, EventDelegate>();
|
||||
private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>();
|
||||
private ConcurrentDictionary<IntPtr, MouseHook> MouseHooks = new ConcurrentDictionary<IntPtr, MouseHook>();
|
||||
private ConcurrentDictionary<IntPtr, SystemHook> SystemHooks = new ConcurrentDictionary<IntPtr, SystemHook>();
|
||||
|
||||
/// <summary>
|
||||
/// Upon finalization, unregister all active system events and hooks...
|
||||
/// </summary>
|
||||
~NativeMethods()
|
||||
{
|
||||
foreach (var handle in EventDelegates.Keys)
|
||||
foreach (var hook in SystemHooks.Values)
|
||||
{
|
||||
User32.UnhookWinEvent(handle);
|
||||
hook.Detach();
|
||||
}
|
||||
|
||||
foreach (var handle in KeyboardHooks.Keys)
|
||||
foreach (var hook in KeyboardHooks.Values)
|
||||
{
|
||||
User32.UnhookWindowsHookEx(handle);
|
||||
hook.Detach();
|
||||
}
|
||||
|
||||
foreach (var handle in MouseHooks.Keys)
|
||||
foreach (var hook in MouseHooks.Values)
|
||||
{
|
||||
User32.UnhookWindowsHookEx(handle);
|
||||
hook.Detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
KeyboardHooks.TryRemove(hook.Handle, out KeyboardHook h);
|
||||
KeyboardHooks.TryRemove(hook.Handle, out _);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,20 +80,25 @@ namespace SafeExamBrowser.WindowsApi
|
|||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
MouseHooks.TryRemove(hook.Handle, out MouseHook h);
|
||||
MouseHooks.TryRemove(hook.Handle, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeregisterSystemEvent(IntPtr handle)
|
||||
public void DeregisterSystemEventHook(Guid hookId)
|
||||
{
|
||||
var success = User32.UnhookWinEvent(handle);
|
||||
var hook = SystemHooks.Values.FirstOrDefault(h => h.Id == hookId);
|
||||
|
||||
if (!success)
|
||||
if (hook != null)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
var success = hook.Detach();
|
||||
|
||||
EventDelegates.TryRemove(handle, out EventDelegate d);
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
SystemHooks.TryRemove(hook.Handle, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void EmptyClipboard()
|
||||
|
@ -236,6 +241,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
var hookThread = new Thread(() =>
|
||||
{
|
||||
var hook = new KeyboardHook(interceptor);
|
||||
var sleepEvent = new AutoResetEvent(false);
|
||||
|
||||
hook.Attach();
|
||||
KeyboardHooks[hook.Handle] = hook;
|
||||
|
@ -243,7 +249,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
|
||||
while (true)
|
||||
{
|
||||
hook.InputEvent.WaitOne();
|
||||
sleepEvent.WaitOne();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -260,6 +266,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
var hookThread = new Thread(() =>
|
||||
{
|
||||
var hook = new MouseHook(interceptor);
|
||||
var sleepEvent = new AutoResetEvent(false);
|
||||
|
||||
hook.Attach();
|
||||
MouseHooks[hook.Handle] = hook;
|
||||
|
@ -267,7 +274,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
|
||||
while (true)
|
||||
{
|
||||
hook.InputEvent.WaitOne();
|
||||
sleepEvent.WaitOne();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -278,38 +285,38 @@ namespace SafeExamBrowser.WindowsApi
|
|||
hookReadyEvent.WaitOne();
|
||||
}
|
||||
|
||||
public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback)
|
||||
public Guid RegisterSystemCaptureStartEvent(SystemEventCallback callback)
|
||||
{
|
||||
void evenDelegate(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, evenDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
|
||||
|
||||
// IMORTANT:
|
||||
// Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
|
||||
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
EventDelegates[handle] = evenDelegate;
|
||||
|
||||
return handle;
|
||||
return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_CAPTURESTART);
|
||||
}
|
||||
|
||||
public IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback)
|
||||
public Guid RegisterSystemForegroundEvent(SystemEventCallback callback)
|
||||
{
|
||||
void eventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_FOREGROUND);
|
||||
}
|
||||
|
||||
internal Guid RegisterSystemEvent(SystemEventCallback callback, uint eventId)
|
||||
{
|
||||
var hookId = default(Guid);
|
||||
var hookReadyEvent = new AutoResetEvent(false);
|
||||
var hookThread = new Thread(() =>
|
||||
{
|
||||
callback(hwnd);
|
||||
}
|
||||
var hook = new SystemHook(callback, eventId);
|
||||
|
||||
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
|
||||
hook.Attach();
|
||||
hookId = hook.Id;
|
||||
SystemHooks[hook.Handle] = hook;
|
||||
hookReadyEvent.Set();
|
||||
hook.AwaitDetach();
|
||||
});
|
||||
|
||||
// IMORTANT:
|
||||
// Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
|
||||
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
EventDelegates[handle] = eventDelegate;
|
||||
hookThread.SetApartmentState(ApartmentState.STA);
|
||||
hookThread.IsBackground = true;
|
||||
hookThread.Start();
|
||||
|
||||
return handle;
|
||||
hookReadyEvent.WaitOne();
|
||||
|
||||
return hookId;
|
||||
}
|
||||
|
||||
public void RemoveWallpaper()
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<Compile Include="DesktopFactory.cs" />
|
||||
<Compile Include="ExplorerShell.cs" />
|
||||
<Compile Include="Monitoring\MouseHook.cs" />
|
||||
<Compile Include="Monitoring\SystemHook.cs" />
|
||||
<Compile Include="Process.cs" />
|
||||
<Compile Include="ProcessFactory.cs" />
|
||||
<Compile Include="Constants\ACCESS_MASK.cs" />
|
||||
|
|
Loading…
Reference in a new issue