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);
}
}