Renamed Key to TextKey and started implementing keyboard monitoring.

This commit is contained in:
dbuechel 2017-08-03 15:35:22 +02:00
parent cbb3e797b0
commit c9a53397a4
33 changed files with 462 additions and 47 deletions

View file

@ -34,7 +34,7 @@ namespace SafeExamBrowser.Browser.Handlers
if (settings.AllowDeveloperConsole)
{
model.AddItem((CefMenuCommand) DEV_CONSOLE_COMMAND, text.Get(Key.Browser_ShowDeveloperConsole));
model.AddItem((CefMenuCommand) DEV_CONSOLE_COMMAND, text.Get(TextKey.Browser_ShowDeveloperConsole));
}
}

View file

@ -14,6 +14,6 @@ namespace SafeExamBrowser.Contracts.I18n
/// Gets the text associated with the specified key. If the key was not found, a default text indicating
/// that the given key is not configured shall be returned.
/// </summary>
string Get(Key key);
string Get(TextKey key);
}
}

View file

@ -15,6 +15,6 @@ namespace SafeExamBrowser.Contracts.I18n
/// <summary>
/// Loads all text data from a resource.
/// </summary>
IDictionary<Key, string> LoadText();
IDictionary<TextKey, string> LoadText();
}
}

View file

@ -11,7 +11,7 @@ namespace SafeExamBrowser.Contracts.I18n
/// <summary>
/// Defines all text components of the user interface.
/// </summary>
public enum Key
public enum TextKey
{
Browser_ShowDeveloperConsole,
MessageBox_ShutdownError,

View file

@ -10,5 +10,9 @@ namespace SafeExamBrowser.Contracts.Monitoring
{
public interface IKeyboardInterceptor
{
/// <summary>
/// Returns <c>true</c> if the given key should be blocked, otherwise <c>false</c>.
/// </summary>
bool Block(uint keyCode, KeyModifier modifier);
}
}

View file

@ -0,0 +1,20 @@
/*
* 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;
namespace SafeExamBrowser.Contracts.Monitoring
{
[Flags]
public enum KeyModifier
{
None = 0,
Ctrl = 0b01,
Alt = 0b10
}
}

View file

@ -72,7 +72,7 @@
<Compile Include="Behaviour\IStartupController.cs" />
<Compile Include="Configuration\IWorkingArea.cs" />
<Compile Include="I18n\IText.cs" />
<Compile Include="I18n\Key.cs" />
<Compile Include="I18n\TextKey.cs" />
<Compile Include="Logging\ILogContent.cs" />
<Compile Include="Logging\ILogger.cs" />
<Compile Include="Logging\ILogMessage.cs" />
@ -84,6 +84,7 @@
<Compile Include="Monitoring\IMouseInterceptor.cs" />
<Compile Include="Monitoring\IProcessMonitor.cs" />
<Compile Include="Monitoring\IWindowMonitor.cs" />
<Compile Include="Monitoring\KeyModifier.cs" />
<Compile Include="UserInterface\IBrowserControl.cs" />
<Compile Include="UserInterface\IBrowserWindow.cs" />
<Compile Include="UserInterface\IMessageBox.cs" />

View file

@ -47,6 +47,6 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// Updates the status text of the splash screen. If the busy flag is set,
/// the splash screen will show an animation to indicate a long-running operation.
/// </summary>
void UpdateText(Key key, bool showBusyIndication = false);
void UpdateText(TextKey key, bool showBusyIndication = false);
}
}

View file

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.WindowsApi.Types;
namespace SafeExamBrowser.Contracts.WindowsApi
@ -71,6 +72,11 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// </exception>
void PostCloseMessageToShell();
/// <summary>
/// Registers a system hook for the given keyboard interceptor.
/// </summary>
void RegisterKeyboardHook(IKeyboardInterceptor interceptor);
/// <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.
@ -101,6 +107,14 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// </exception>
void SetWorkingArea(RECT bounds);
/// <summary>
/// Unregisters the system hook for the given keyboard interceptor.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the hook for the given interceptor could not be successfully removed.
/// </exception>
void UnregisterKeyboardHook(IKeyboardInterceptor interceptor);
/// <summary>
/// Unregisters a previously registered system event.
/// </summary>

View file

@ -24,9 +24,9 @@ namespace SafeExamBrowser.Core.UnitTests.I18n
var resource = new Mock<ITextResource>();
var sut = new Text(resource.Object);
resource.Setup(r => r.LoadText()).Returns<IDictionary<Key, string>>(null);
resource.Setup(r => r.LoadText()).Returns<IDictionary<TextKey, string>>(null);
var text = sut.Get((Key)(-1));
var text = sut.Get((TextKey)(-1));
Assert.IsNotNull(text);
}

View file

@ -41,7 +41,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Perform()
{
logger.Info("Initializing browser...");
SplashScreen.UpdateText(Key.SplashScreen_InitializeBrowser);
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeBrowser);
var browserButton = uiFactory.CreateApplicationButton(browserInfo);
@ -54,7 +54,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Revert()
{
logger.Info("Terminating browser...");
SplashScreen.UpdateText(Key.SplashScreen_TerminateBrowser);
SplashScreen.UpdateText(TextKey.SplashScreen_TerminateBrowser);
browserController.Terminate();
}

View file

@ -29,7 +29,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Perform()
{
logger.Info("Starting event handling...");
SplashScreen.UpdateText(Key.SplashScreen_StartEventHandling);
SplashScreen.UpdateText(TextKey.SplashScreen_StartEventHandling);
controller.Start();
}
@ -37,7 +37,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Revert()
{
logger.Info("Stopping event handling...");
SplashScreen.UpdateText(Key.SplashScreen_StopEventHandling);
SplashScreen.UpdateText(TextKey.SplashScreen_StopEventHandling);
controller.Stop();
}

View file

@ -30,12 +30,12 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Perform()
{
logger.Info("Initializing process monitoring...");
SplashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination, true);
SplashScreen.UpdateText(TextKey.SplashScreen_WaitExplorerTermination, true);
processMonitor.CloseExplorerShell();
processMonitor.StartMonitoringExplorer();
SplashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring);
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeProcessMonitoring);
// TODO
}
@ -43,11 +43,11 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Revert()
{
logger.Info("Stopping process monitoring...");
SplashScreen.UpdateText(Key.SplashScreen_StopProcessMonitoring);
SplashScreen.UpdateText(TextKey.SplashScreen_StopProcessMonitoring);
// TODO
SplashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup, true);
SplashScreen.UpdateText(TextKey.SplashScreen_WaitExplorerStartup, true);
processMonitor.StopMonitoringExplorer();
processMonitor.StartExplorerShell();

View file

@ -38,7 +38,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Perform()
{
logger.Info("Initializing taskbar...");
SplashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar);
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeTaskbar);
var aboutInfo = new AboutNotificationInfo(text);
var aboutNotification = uiFactory.CreateNotification(aboutInfo);

View file

@ -30,7 +30,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Perform()
{
logger.Info("Initializing window monitoring...");
SplashScreen.UpdateText(Key.SplashScreen_InitializeWindowMonitoring);
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeWindowMonitoring);
windowMonitor.HideAllWindows();
windowMonitor.StartMonitoringWindows();
@ -39,7 +39,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Revert()
{
logger.Info("Stopping window monitoring...");
SplashScreen.UpdateText(Key.SplashScreen_StopWindowMonitoring);
SplashScreen.UpdateText(TextKey.SplashScreen_StopWindowMonitoring);
windowMonitor.StopMonitoringWindows();
windowMonitor.RestoreHiddenWindows();

View file

@ -32,7 +32,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Perform()
{
logger.Info("Initializing working area...");
SplashScreen.UpdateText(Key.SplashScreen_InitializeWorkingArea);
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeWorkingArea);
// TODO
// - Emptying clipboard
@ -43,7 +43,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
public void Revert()
{
logger.Info("Restoring working area...");
SplashScreen.UpdateText(Key.SplashScreen_RestoreWorkingArea);
SplashScreen.UpdateText(TextKey.SplashScreen_RestoreWorkingArea);
// TODO
// - Emptying clipboard

View file

@ -63,14 +63,14 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetIndeterminate();
splashScreen.UpdateText(Key.SplashScreen_ShutdownProcedure);
splashScreen.UpdateText(TextKey.SplashScreen_ShutdownProcedure);
splashScreen.InvokeShow();
}
private void LogAndShowException(Exception e)
{
logger.Error($"Failed to finalize application!", e);
uiFactory.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error);
uiFactory.Show(text.Get(TextKey.MessageBox_ShutdownError), text.Get(TextKey.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error);
}
private void Finish(bool success = true)

View file

@ -92,14 +92,14 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(operationCount);
splashScreen.UpdateText(Key.SplashScreen_StartupProcedure);
splashScreen.UpdateText(TextKey.SplashScreen_StartupProcedure);
splashScreen.InvokeShow();
}
private void LogAndShowException(Exception e)
{
logger.Error($"Failed to initialize application!", e);
uiFactory.Show(text.Get(Key.MessageBox_StartupError), text.Get(Key.MessageBox_StartupErrorTitle), icon: MessageBoxIcon.Error);
uiFactory.Show(text.Get(TextKey.MessageBox_StartupError), text.Get(TextKey.MessageBox_StartupErrorTitle), icon: MessageBoxIcon.Error);
logger.Info("Reverting operations...");
}

View file

@ -14,7 +14,7 @@ namespace SafeExamBrowser.Core.I18n
{
public class Text : IText
{
private readonly IDictionary<Key, string> cache;
private readonly IDictionary<TextKey, string> cache;
public Text(ITextResource resource)
{
@ -23,10 +23,10 @@ namespace SafeExamBrowser.Core.I18n
throw new ArgumentNullException(nameof(resource));
}
cache = resource.LoadText() ?? new Dictionary<Key, string>();
cache = resource.LoadText() ?? new Dictionary<TextKey, string>();
}
public string Get(Key key)
public string Get(TextKey key)
{
return cache.ContainsKey(key) ? cache[key] : $"Could not find string for key '{key}'!";
}

View file

@ -17,16 +17,16 @@ namespace SafeExamBrowser.Core.I18n
{
public class XmlTextResource : ITextResource
{
public IDictionary<Key, string> LoadText()
public IDictionary<TextKey, string> LoadText()
{
var assembly = Assembly.GetAssembly(typeof(XmlTextResource)).Location;
var path = Path.GetDirectoryName(assembly) + $@"\{nameof(I18n)}\Text.xml";
var xml = XDocument.Load(path);
var text = new Dictionary<Key, string>();
var text = new Dictionary<TextKey, string>();
foreach (var definition in xml.Root.Descendants())
{
if (Enum.TryParse(definition.Name.LocalName, out Key key))
if (Enum.TryParse(definition.Name.LocalName, out TextKey key))
{
text[key] = definition.Value;
}

View file

@ -15,7 +15,7 @@ namespace SafeExamBrowser.Core.Notifications
{
private IText text;
public string Tooltip => text.Get(Key.Notification_AboutTooltip);
public string Tooltip => text.Get(TextKey.Notification_AboutTooltip);
public IIconResource IconResource { get; } = new AboutNotificationIconResource();
public AboutNotificationInfo(IText text)

View file

@ -43,7 +43,7 @@ namespace SafeExamBrowser.UserInterface
private void InitializeAboutWindow()
{
Closing += (o, args) => closing?.Invoke();
VersionInfo.Inlines.Add(new Run($"{text.Get(Key.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic });
VersionInfo.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic });
VersionInfo.Inlines.Add(new LineBreak());
VersionInfo.Inlines.Add(new LineBreak());
VersionInfo.Inlines.Add(new Run(settings.ProgramCopyright) { FontSize = 10 });

View file

@ -60,7 +60,7 @@ namespace SafeExamBrowser.UserInterface
model.MaxProgress = max;
}
public void UpdateText(Key key, bool showBusyIndication = false)
public void UpdateText(TextKey key, bool showBusyIndication = false)
{
model.StopBusyIndication();
model.Status = text.Get(key);
@ -73,7 +73,7 @@ namespace SafeExamBrowser.UserInterface
private void InitializeSplashScreen()
{
InfoTextBlock.Inlines.Add(new Run($"{text.Get(Key.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(settings.ProgramCopyright) { FontSize = 10 });

View file

@ -49,6 +49,22 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// </summary>
internal const int WM_COMMAND = 0x111;
/// <summary>
/// Posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when
/// the ALT key is not pressed.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646280(v=vs.85).aspx.
/// </summary>
internal const int WM_KEYDOWN = 0x100;
/// <summary>
/// Posted to the window with the keyboard focus when a nonsystem key is released. A nonsystem key is a key that is pressed when
/// the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646281(v=vs.85).aspx.
/// </summary>
internal const int WM_KEYUP = 0x101;
/// <summary>
/// A window receives this message when the user chooses a command from the Window menu (formerly known as the system or control
/// menu) or when the user chooses the maximize button, minimize button, restore button, or close button.
@ -56,5 +72,25 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx.
/// </summary>
internal const int WM_SYSCOMMAND = 0x112;
/// <summary>
/// Posted to the window with the keyboard focus when the user presses the F10 key (which activates the menu bar) or holds down
/// the ALT key and then presses another key. It also occurs when no window currently has the keyboard focus; in this case, the
/// WM_SYSKEYDOWN message is sent to the active window. The window that receives the message can distinguish between these two
/// contexts by checking the context code in the lParam parameter.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646286(v=vs.85).aspx.
/// </summary>
internal const int WM_SYSKEYDOWN = 0x104;
/// <summary>
/// Posted to the window with the keyboard focus when the user releases a key that was pressed while the ALT key was held down.
/// It also occurs when no window currently has the keyboard focus; in this case, the WM_SYSKEYUP message is sent to the active
/// window. The window that receives the message can distinguish between these two contexts by checking the context code in the
/// lParam parameter.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646287(v=vs.85).aspx
/// </summary>
internal const int WM_SYSKEYUP = 0x105;
}
}

View file

@ -0,0 +1,100 @@
/*
* 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.WindowsApi.Constants
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Enums/HookType.html.
/// </remarks>
internal enum HookType
{
/// <summary>
/// Installs a hook procedure that records input messages posted to the system message queue. This hook is useful for recording
/// macros. For more information, see the JournalRecordProc hook procedure.
/// </summary>
WH_JOURNALRECORD = 0,
/// <summary>
/// Installs a hook procedure that posts messages previously recorded by a WH_JOURNALRECORD hook procedure. For more information,
/// see the JournalPlaybackProc hook procedure.
/// </summary>
WH_JOURNALPLAYBACK = 1,
/// <summary>
/// Installs a hook procedure that monitors keystroke messages. For more information, see the KeyboardProc hook procedure.
/// </summary>
WH_KEYBOARD = 2,
/// <summary>
/// Installs a hook procedure that monitors messages posted to a message queue. For more information, see the GetMsgProc hook
/// procedure.
/// </summary>
WH_GETMESSAGE = 3,
/// <summary>
/// Installs a hook procedure that monitors messages before the system sends them to the destination window procedure. For more
/// information, see the CallWndProc hook procedure.
/// </summary>
WH_CALLWNDPROC = 4,
/// <summary>
/// Installs a hook procedure that receives notifications useful to a CBT application. For more information, see the CBTProc hook
/// procedure.
/// </summary>
WH_CBT = 5,
/// <summary>
/// Installs a hook procedure that monitors messages generated as a result of an input event in a dialog box, message box, menu,
/// or scroll bar. The hook procedure monitors these messages for all applications in the same desktop as the calling thread. For
/// more information, see the SysMsgProc hook procedure.
/// </summary>
WH_SYSMSGFILTER = 6,
/// <summary>
/// Installs a hook procedure that monitors mouse messages. For more information, see the MouseProc hook procedure.
/// </summary>
WH_MOUSE = 7,
WH_HARDWARE = 8,
/// <summary>
/// Installs a hook procedure useful for debugging other hook procedures. For more information, see the DebugProc hook procedure.
/// </summary>
WH_DEBUG = 9,
/// <summary>
/// Installs a hook procedure that receives notifications useful to shell applications. For more information, see the ShellProc
/// hook procedure.
/// </summary>
WH_SHELL = 10,
/// <summary>
/// Installs a hook procedure that will be called when the application's foreground thread is about to become idle. This hook is
/// useful for performing low priority tasks during idle time. For more information, see the ForegroundIdleProc hook procedure.
/// </summary>
WH_FOREGROUNDIDLE = 11,
/// <summary>
/// Installs a hook procedure that monitors messages after they have been processed by the destination window procedure. For more
/// information, see the CallWndRetProc hook procedure.
/// </summary>
WH_CALLWNDPROCRET = 12,
/// <summary>
/// Installs a hook procedure that monitors low-level keyboard input events. For more information, see the LowLevelKeyboardProc
/// hook procedure.
/// </summary>
WH_KEYBOARD_LL = 13,
/// <summary>
/// Installs a hook procedure that monitors low-level mouse input events. For more information, see the LowLevelMouseProc hook
/// procedure.
/// </summary>
WH_MOUSE_LL = 14
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Constants
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
/// <summary>
/// A virtual-key code. The code must be a value in the range 1 to 254.
/// </summary>
public uint KeyCode;
/// <summary>
/// A hardware scan code for the key.
/// </summary>
public uint ScanCode;
/// <summary>
/// The extended-key flag, event-injected flags, context code, and transition-state flag. This member is specified as follows. An
/// application can use the following values to test the keystroke flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the
/// event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected
/// from a process running at lower integrity level.
/// </summary>
public KBDLLHOOKSTRUCTFlags Flags;
/// <summary>
/// The time stamp for this message, equivalent to what <c>GetMessageTime</c> would return for this message.
/// </summary>
public uint Time;
/// <summary>
/// Additional information associated with the message.
/// </summary>
public IntPtr DwExtraInfo;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.WindowsApi.Constants
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html.
/// </remarks>
public enum KBDLLHOOKSTRUCTFlags
{
/// <summary>
/// Test the extended-key flag.
/// </summary>
LLKHF_EXTENDED = 0x01,
/// <summary>
/// Test the event-injected (from any process) flag.
/// </summary>
LLKHF_INJECTED = 0x10,
/// <summary>
/// Test the context code.
/// </summary>
LLKHF_ALTDOWN = 0x20,
/// <summary>
/// Test the transition-state flag.
/// </summary>
LLKHF_UP = 0x80
}
}

View file

@ -38,7 +38,7 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// <summary>
/// Activates the window and displays it as a maximized window.
/// </summary>
/// </summary>
ShowMaximized = 3,
/// <summary>

View file

@ -0,0 +1,22 @@
/*
* 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.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>kernel32.dll</c>.
/// </summary>
internal class Kernel32
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
}

View file

@ -0,0 +1,71 @@
/*
* 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.Runtime.InteropServices;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.WindowsApi.Constants;
namespace SafeExamBrowser.WindowsApi.Monitoring
{
internal class KeyboardHook
{
internal IntPtr Handle { get; private set; }
internal IKeyboardInterceptor Interceptor { get; private set; }
internal KeyboardHook(IKeyboardInterceptor interceptor)
{
Interceptor = interceptor;
}
internal void Attach()
{
var module = Kernel32.GetModuleHandle(null);
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, LowLevelKeyboardProc, module, 0);
}
internal bool Detach()
{
return User32.UnhookWindowsHookEx(Handle);
}
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
var modifier = GetModifiers(keyData);
if (Interceptor.Block(keyData.KeyCode, modifier))
{
return (IntPtr) 1;
}
}
return User32.CallNextHookEx(Handle, nCode, wParam, lParam);
}
private KeyModifier GetModifiers(KBDLLHOOKSTRUCT keyData)
{
var modifier = KeyModifier.None;
if ((keyData.Flags & KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN) == KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)
{
modifier |= KeyModifier.Alt;
}
if (keyData.Flags == 0)
{
modifier |= KeyModifier.Ctrl;
}
return modifier;
}
}
}

View file

@ -10,17 +10,37 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.Contracts.WindowsApi.Types;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Monitoring;
namespace SafeExamBrowser.WindowsApi
{
public class NativeMethods : INativeMethods
{
private ConcurrentDictionary<IntPtr, WinEventDelegate> EventDelegates = new ConcurrentDictionary<IntPtr, WinEventDelegate>();
private ConcurrentDictionary<IntPtr, EventProc> EventDelegates = new ConcurrentDictionary<IntPtr, EventProc>();
private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>();
/// <summary>
/// Upon finalization, unregister all system events and hooks...
/// </summary>
~NativeMethods()
{
foreach (var handle in EventDelegates.Keys)
{
User32.UnhookWinEvent(handle);
}
foreach (var handle in KeyboardHooks.Keys)
{
User32.UnhookWindowsHookEx(handle);
}
}
public IEnumerable<IntPtr> GetOpenWindows()
{
@ -118,9 +138,21 @@ namespace SafeExamBrowser.WindowsApi
}
}
public void RegisterKeyboardHook(IKeyboardInterceptor interceptor)
{
var hook = new KeyboardHook(interceptor);
hook.Attach();
// IMORTANT:
// Ensures that the hook 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!
KeyboardHooks[hook.Handle] = hook;
}
public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback)
{
WinEventDelegate eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
EventProc eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
{
callback(hwnd);
};
@ -128,7 +160,7 @@ namespace SafeExamBrowser.WindowsApi
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.
// 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] = eventProc;
@ -137,7 +169,7 @@ namespace SafeExamBrowser.WindowsApi
public IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback)
{
WinEventDelegate eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
EventProc eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
{
callback(hwnd);
};
@ -145,7 +177,7 @@ namespace SafeExamBrowser.WindowsApi
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.
// 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] = eventProc;
@ -172,6 +204,23 @@ namespace SafeExamBrowser.WindowsApi
}
}
public void UnregisterKeyboardHook(IKeyboardInterceptor interceptor)
{
var hook = KeyboardHooks.Values.FirstOrDefault(h => h.Interceptor == interceptor);
if (hook != null)
{
var success = hook.Detach();
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
KeyboardHooks.TryRemove(hook.Handle, out KeyboardHook h);
}
}
public void UnregisterSystemEvent(IntPtr handle)
{
var success = User32.UnhookWinEvent(handle);
@ -180,10 +229,8 @@ namespace SafeExamBrowser.WindowsApi
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else
{
EventDelegates.TryRemove(handle, out WinEventDelegate d);
}
EventDelegates.TryRemove(handle, out EventProc d);
}
}
}

View file

@ -59,8 +59,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Constants\Constant.cs" />
<Compile Include="Constants\HookType.cs" />
<Compile Include="Constants\KBDLLHOOKSTRUCT.cs" />
<Compile Include="Constants\KBDLLHOOKSTRUCTFlags.cs" />
<Compile Include="Constants\ShowWindowCommand.cs" />
<Compile Include="Constants\SystemCommand.cs" />
<Compile Include="Kernel32.cs" />
<Compile Include="Monitoring\KeyboardHook.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Constants\SPI.cs" />

View file

@ -15,13 +15,17 @@ using SafeExamBrowser.WindowsApi.Constants;
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);
internal delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
internal delegate void EventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
/// <summary>
/// Provides access to the native Windows API exposed by <c>user32.dll</c>.
/// </summary>
internal static class User32
{
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool EnumWindows(EnumWindowsDelegate enumProc, IntPtr lParam);
@ -50,7 +54,10 @@ namespace SafeExamBrowser.WindowsApi
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);
internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, EventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
@ -62,5 +69,9 @@ namespace SafeExamBrowser.WindowsApi
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
}
}