From c9a53397a4d7ea354d3b41ffddb445d9ff2f14a7 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Thu, 3 Aug 2017 15:35:22 +0200 Subject: [PATCH] Renamed Key to TextKey and started implementing keyboard monitoring. --- .../Handlers/BrowserContextMenuHandler.cs | 2 +- SafeExamBrowser.Contracts/I18n/IText.cs | 2 +- .../I18n/ITextResource.cs | 2 +- .../I18n/{Key.cs => TextKey.cs} | 2 +- .../Monitoring/IKeyboardInterceptor.cs | 4 + .../Monitoring/KeyModifier.cs | 20 ++++ .../SafeExamBrowser.Contracts.csproj | 3 +- .../UserInterface/ISplashScreen.cs | 2 +- .../WindowsApi/INativeMethods.cs | 14 +++ .../I18n/TextTests.cs | 4 +- .../Behaviour/Operations/BrowserOperation.cs | 4 +- .../Operations/EventControllerOperation.cs | 4 +- .../Operations/ProcessMonitorOperation.cs | 8 +- .../Behaviour/Operations/TaskbarOperation.cs | 2 +- .../Operations/WindowMonitorOperation.cs | 4 +- .../Operations/WorkingAreaOperation.cs | 4 +- .../Behaviour/ShutdownController.cs | 4 +- .../Behaviour/StartupController.cs | 4 +- SafeExamBrowser.Core/I18n/Text.cs | 6 +- SafeExamBrowser.Core/I18n/XmlTextResource.cs | 6 +- .../Notifications/AboutNotificationInfo.cs | 2 +- .../AboutWindow.xaml.cs | 2 +- .../SplashScreen.xaml.cs | 4 +- .../Constants/Constant.cs | 36 +++++++ .../Constants/HookType.cs | 100 ++++++++++++++++++ .../Constants/KBDLLHOOKSTRUCT.cs | 48 +++++++++ .../Constants/KBDLLHOOKSTRUCTFlags.cs | 36 +++++++ .../Constants/ShowWindowCommand.cs | 2 +- SafeExamBrowser.WindowsApi/Kernel32.cs | 22 ++++ .../Monitoring/KeyboardHook.cs | 71 +++++++++++++ SafeExamBrowser.WindowsApi/NativeMethods.cs | 65 ++++++++++-- .../SafeExamBrowser.WindowsApi.csproj | 5 + SafeExamBrowser.WindowsApi/User32.cs | 15 ++- 33 files changed, 462 insertions(+), 47 deletions(-) rename SafeExamBrowser.Contracts/I18n/{Key.cs => TextKey.cs} (98%) create mode 100644 SafeExamBrowser.Contracts/Monitoring/KeyModifier.cs create mode 100644 SafeExamBrowser.WindowsApi/Constants/HookType.cs create mode 100644 SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCT.cs create mode 100644 SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCTFlags.cs create mode 100644 SafeExamBrowser.WindowsApi/Kernel32.cs create mode 100644 SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs diff --git a/SafeExamBrowser.Browser/Handlers/BrowserContextMenuHandler.cs b/SafeExamBrowser.Browser/Handlers/BrowserContextMenuHandler.cs index 339c59c1..c13d99bb 100644 --- a/SafeExamBrowser.Browser/Handlers/BrowserContextMenuHandler.cs +++ b/SafeExamBrowser.Browser/Handlers/BrowserContextMenuHandler.cs @@ -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)); } } diff --git a/SafeExamBrowser.Contracts/I18n/IText.cs b/SafeExamBrowser.Contracts/I18n/IText.cs index 02d8671f..47549c0c 100644 --- a/SafeExamBrowser.Contracts/I18n/IText.cs +++ b/SafeExamBrowser.Contracts/I18n/IText.cs @@ -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. /// - string Get(Key key); + string Get(TextKey key); } } diff --git a/SafeExamBrowser.Contracts/I18n/ITextResource.cs b/SafeExamBrowser.Contracts/I18n/ITextResource.cs index 5b43ee0c..e8f9e098 100644 --- a/SafeExamBrowser.Contracts/I18n/ITextResource.cs +++ b/SafeExamBrowser.Contracts/I18n/ITextResource.cs @@ -15,6 +15,6 @@ namespace SafeExamBrowser.Contracts.I18n /// /// Loads all text data from a resource. /// - IDictionary LoadText(); + IDictionary LoadText(); } } diff --git a/SafeExamBrowser.Contracts/I18n/Key.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs similarity index 98% rename from SafeExamBrowser.Contracts/I18n/Key.cs rename to SafeExamBrowser.Contracts/I18n/TextKey.cs index f90221a5..7cca4c62 100644 --- a/SafeExamBrowser.Contracts/I18n/Key.cs +++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs @@ -11,7 +11,7 @@ namespace SafeExamBrowser.Contracts.I18n /// /// Defines all text components of the user interface. /// - public enum Key + public enum TextKey { Browser_ShowDeveloperConsole, MessageBox_ShutdownError, diff --git a/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs b/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs index 77f717ca..8274e586 100644 --- a/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs +++ b/SafeExamBrowser.Contracts/Monitoring/IKeyboardInterceptor.cs @@ -10,5 +10,9 @@ namespace SafeExamBrowser.Contracts.Monitoring { public interface IKeyboardInterceptor { + /// + /// Returns true if the given key should be blocked, otherwise false. + /// + bool Block(uint keyCode, KeyModifier modifier); } } diff --git a/SafeExamBrowser.Contracts/Monitoring/KeyModifier.cs b/SafeExamBrowser.Contracts/Monitoring/KeyModifier.cs new file mode 100644 index 00000000..2892d849 --- /dev/null +++ b/SafeExamBrowser.Contracts/Monitoring/KeyModifier.cs @@ -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 + } +} diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index 1b0e5c21..1d43922c 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -72,7 +72,7 @@ - + @@ -84,6 +84,7 @@ + diff --git a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs index 1a5c2c7c..6c973618 100644 --- a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs +++ b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs @@ -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. /// - void UpdateText(Key key, bool showBusyIndication = false); + void UpdateText(TextKey key, bool showBusyIndication = false); } } diff --git a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs index baeb550d..a139d49b 100644 --- a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs +++ b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs @@ -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 /// void PostCloseMessageToShell(); + /// + /// Registers a system hook for the given keyboard interceptor. + /// + void RegisterKeyboardHook(IKeyboardInterceptor interceptor); + /// /// 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 /// void SetWorkingArea(RECT bounds); + /// + /// Unregisters the system hook for the given keyboard interceptor. + /// + /// + /// If the hook for the given interceptor could not be successfully removed. + /// + void UnregisterKeyboardHook(IKeyboardInterceptor interceptor); + /// /// Unregisters a previously registered system event. /// diff --git a/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs b/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs index 34572749..8958036d 100644 --- a/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs +++ b/SafeExamBrowser.Core.UnitTests/I18n/TextTests.cs @@ -24,9 +24,9 @@ namespace SafeExamBrowser.Core.UnitTests.I18n var resource = new Mock(); var sut = new Text(resource.Object); - resource.Setup(r => r.LoadText()).Returns>(null); + resource.Setup(r => r.LoadText()).Returns>(null); - var text = sut.Get((Key)(-1)); + var text = sut.Get((TextKey)(-1)); Assert.IsNotNull(text); } diff --git a/SafeExamBrowser.Core/Behaviour/Operations/BrowserOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/BrowserOperation.cs index f6030979..af87d87e 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/BrowserOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/BrowserOperation.cs @@ -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(); } diff --git a/SafeExamBrowser.Core/Behaviour/Operations/EventControllerOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/EventControllerOperation.cs index de7f1e0f..c5bfeea3 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/EventControllerOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/EventControllerOperation.cs @@ -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(); } diff --git a/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitorOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitorOperation.cs index 7214df81..06b19219 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitorOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/ProcessMonitorOperation.cs @@ -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(); diff --git a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs index 56eaf358..9dae6d3c 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs @@ -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); diff --git a/SafeExamBrowser.Core/Behaviour/Operations/WindowMonitorOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/WindowMonitorOperation.cs index a33ffd5e..5ffa407e 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/WindowMonitorOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/WindowMonitorOperation.cs @@ -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(); diff --git a/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs index bdc642a4..851bf2e0 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs @@ -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 diff --git a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs index f9e3e664..8a3881b5 100644 --- a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs +++ b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs @@ -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) diff --git a/SafeExamBrowser.Core/Behaviour/StartupController.cs b/SafeExamBrowser.Core/Behaviour/StartupController.cs index ace9aa86..c4cd3729 100644 --- a/SafeExamBrowser.Core/Behaviour/StartupController.cs +++ b/SafeExamBrowser.Core/Behaviour/StartupController.cs @@ -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..."); } diff --git a/SafeExamBrowser.Core/I18n/Text.cs b/SafeExamBrowser.Core/I18n/Text.cs index 236fd3ad..aa88869d 100644 --- a/SafeExamBrowser.Core/I18n/Text.cs +++ b/SafeExamBrowser.Core/I18n/Text.cs @@ -14,7 +14,7 @@ namespace SafeExamBrowser.Core.I18n { public class Text : IText { - private readonly IDictionary cache; + private readonly IDictionary cache; public Text(ITextResource resource) { @@ -23,10 +23,10 @@ namespace SafeExamBrowser.Core.I18n throw new ArgumentNullException(nameof(resource)); } - cache = resource.LoadText() ?? new Dictionary(); + cache = resource.LoadText() ?? new Dictionary(); } - public string Get(Key key) + public string Get(TextKey key) { return cache.ContainsKey(key) ? cache[key] : $"Could not find string for key '{key}'!"; } diff --git a/SafeExamBrowser.Core/I18n/XmlTextResource.cs b/SafeExamBrowser.Core/I18n/XmlTextResource.cs index 9f21b672..236aa54b 100644 --- a/SafeExamBrowser.Core/I18n/XmlTextResource.cs +++ b/SafeExamBrowser.Core/I18n/XmlTextResource.cs @@ -17,16 +17,16 @@ namespace SafeExamBrowser.Core.I18n { public class XmlTextResource : ITextResource { - public IDictionary LoadText() + public IDictionary 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(); + var text = new Dictionary(); 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; } diff --git a/SafeExamBrowser.Core/Notifications/AboutNotificationInfo.cs b/SafeExamBrowser.Core/Notifications/AboutNotificationInfo.cs index 88aeeadb..a1be68e0 100644 --- a/SafeExamBrowser.Core/Notifications/AboutNotificationInfo.cs +++ b/SafeExamBrowser.Core/Notifications/AboutNotificationInfo.cs @@ -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) diff --git a/SafeExamBrowser.UserInterface/AboutWindow.xaml.cs b/SafeExamBrowser.UserInterface/AboutWindow.xaml.cs index 1e51a2ba..f88fa625 100644 --- a/SafeExamBrowser.UserInterface/AboutWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface/AboutWindow.xaml.cs @@ -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 }); diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs index 71c8d631..52ef86d5 100644 --- a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs @@ -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 }); diff --git a/SafeExamBrowser.WindowsApi/Constants/Constant.cs b/SafeExamBrowser.WindowsApi/Constants/Constant.cs index 32760a7b..d30f7d46 100644 --- a/SafeExamBrowser.WindowsApi/Constants/Constant.cs +++ b/SafeExamBrowser.WindowsApi/Constants/Constant.cs @@ -49,6 +49,22 @@ namespace SafeExamBrowser.WindowsApi.Constants /// internal const int WM_COMMAND = 0x111; + /// + /// 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. + /// + internal const int WM_KEYDOWN = 0x100; + + /// + /// 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. + /// + internal const int WM_KEYUP = 0x101; + /// /// 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. /// internal const int WM_SYSCOMMAND = 0x112; + + /// + /// 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. + /// + internal const int WM_SYSKEYDOWN = 0x104; + + /// + /// 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 + /// + internal const int WM_SYSKEYUP = 0x105; } } diff --git a/SafeExamBrowser.WindowsApi/Constants/HookType.cs b/SafeExamBrowser.WindowsApi/Constants/HookType.cs new file mode 100644 index 00000000..07364da7 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Constants/HookType.cs @@ -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 +{ + /// + /// See http://www.pinvoke.net/default.aspx/Enums/HookType.html. + /// + internal enum HookType + { + /// + /// 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. + /// + WH_JOURNALRECORD = 0, + + /// + /// Installs a hook procedure that posts messages previously recorded by a WH_JOURNALRECORD hook procedure. For more information, + /// see the JournalPlaybackProc hook procedure. + /// + WH_JOURNALPLAYBACK = 1, + + /// + /// Installs a hook procedure that monitors keystroke messages. For more information, see the KeyboardProc hook procedure. + /// + WH_KEYBOARD = 2, + + /// + /// Installs a hook procedure that monitors messages posted to a message queue. For more information, see the GetMsgProc hook + /// procedure. + /// + WH_GETMESSAGE = 3, + + /// + /// 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. + /// + WH_CALLWNDPROC = 4, + + /// + /// Installs a hook procedure that receives notifications useful to a CBT application. For more information, see the CBTProc hook + /// procedure. + /// + WH_CBT = 5, + + /// + /// 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. + /// + WH_SYSMSGFILTER = 6, + + /// + /// Installs a hook procedure that monitors mouse messages. For more information, see the MouseProc hook procedure. + /// + WH_MOUSE = 7, + + WH_HARDWARE = 8, + + /// + /// Installs a hook procedure useful for debugging other hook procedures. For more information, see the DebugProc hook procedure. + /// + WH_DEBUG = 9, + + /// + /// Installs a hook procedure that receives notifications useful to shell applications. For more information, see the ShellProc + /// hook procedure. + /// + WH_SHELL = 10, + + /// + /// 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. + /// + WH_FOREGROUNDIDLE = 11, + + /// + /// 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. + /// + WH_CALLWNDPROCRET = 12, + + /// + /// Installs a hook procedure that monitors low-level keyboard input events. For more information, see the LowLevelKeyboardProc + /// hook procedure. + /// + WH_KEYBOARD_LL = 13, + + /// + /// Installs a hook procedure that monitors low-level mouse input events. For more information, see the LowLevelMouseProc hook + /// procedure. + /// + WH_MOUSE_LL = 14 + } +} diff --git a/SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCT.cs b/SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCT.cs new file mode 100644 index 00000000..4568b295 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCT.cs @@ -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 +{ + /// + /// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html. + /// + [StructLayout(LayoutKind.Sequential)] + public struct KBDLLHOOKSTRUCT + { + /// + /// A virtual-key code. The code must be a value in the range 1 to 254. + /// + public uint KeyCode; + + /// + /// A hardware scan code for the key. + /// + public uint ScanCode; + + /// + /// 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. + /// + public KBDLLHOOKSTRUCTFlags Flags; + + /// + /// The time stamp for this message, equivalent to what GetMessageTime would return for this message. + /// + public uint Time; + + /// + /// Additional information associated with the message. + /// + public IntPtr DwExtraInfo; + } +} diff --git a/SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCTFlags.cs b/SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCTFlags.cs new file mode 100644 index 00000000..a4e07b0b --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Constants/KBDLLHOOKSTRUCTFlags.cs @@ -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 +{ + /// + /// See http://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html. + /// + public enum KBDLLHOOKSTRUCTFlags + { + /// + /// Test the extended-key flag. + /// + LLKHF_EXTENDED = 0x01, + + /// + /// Test the event-injected (from any process) flag. + /// + LLKHF_INJECTED = 0x10, + + /// + /// Test the context code. + /// + LLKHF_ALTDOWN = 0x20, + + /// + /// Test the transition-state flag. + /// + LLKHF_UP = 0x80 + } +} diff --git a/SafeExamBrowser.WindowsApi/Constants/ShowWindowCommand.cs b/SafeExamBrowser.WindowsApi/Constants/ShowWindowCommand.cs index 7d236995..bb770ae5 100644 --- a/SafeExamBrowser.WindowsApi/Constants/ShowWindowCommand.cs +++ b/SafeExamBrowser.WindowsApi/Constants/ShowWindowCommand.cs @@ -38,7 +38,7 @@ namespace SafeExamBrowser.WindowsApi.Constants /// /// Activates the window and displays it as a maximized window. - /// + /// ShowMaximized = 3, /// diff --git a/SafeExamBrowser.WindowsApi/Kernel32.cs b/SafeExamBrowser.WindowsApi/Kernel32.cs new file mode 100644 index 00000000..6fc2ec4c --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Kernel32.cs @@ -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 +{ + /// + /// Provides access to the native Windows API exposed by kernel32.dll. + /// + internal class Kernel32 + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr GetModuleHandle(string lpModuleName); + } +} diff --git a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs new file mode 100644 index 00000000..bc6a5312 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs @@ -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; + } + } +} diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index 10f16e22..4c942b3f 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -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 EventDelegates = new ConcurrentDictionary(); + private ConcurrentDictionary EventDelegates = new ConcurrentDictionary(); + private ConcurrentDictionary KeyboardHooks = new ConcurrentDictionary(); + + /// + /// Upon finalization, unregister all system events and hooks... + /// + ~NativeMethods() + { + foreach (var handle in EventDelegates.Keys) + { + User32.UnhookWinEvent(handle); + } + + foreach (var handle in KeyboardHooks.Keys) + { + User32.UnhookWindowsHookEx(handle); + } + } public IEnumerable 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 CallbackOnCollectedDelegate error and subsequent application crash! + KeyboardHooks[hook.Handle] = hook; + } + public IntPtr RegisterSystemForegroundEvent(Action 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 CallbackOnCollectedDelegate error and subsequent application crash! EventDelegates[handle] = eventProc; @@ -137,7 +169,7 @@ namespace SafeExamBrowser.WindowsApi public IntPtr RegisterSystemCaptureStartEvent(Action 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 CallbackOnCollectedDelegate 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); } } } diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj index a1c46fd1..05670949 100644 --- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj +++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj @@ -59,8 +59,13 @@ + + + + + diff --git a/SafeExamBrowser.WindowsApi/User32.cs b/SafeExamBrowser.WindowsApi/User32.cs index 625001f5..4a37ed18 100644 --- a/SafeExamBrowser.WindowsApi/User32.cs +++ b/SafeExamBrowser.WindowsApi/User32.cs @@ -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); /// /// Provides access to the native Windows API exposed by user32.dll. /// 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); } }