From 5f316566498b13d25d6e15b7c197f21859f28cbe Mon Sep 17 00:00:00 2001 From: dbuechel Date: Thu, 14 Nov 2019 14:03:43 +0100 Subject: [PATCH] SEBWIN-312: Implemented scaffolding for task view and its keyboard activator. Finally consolidated keyboard and mouse hooks and resolved dependency from WindowsApi to UI. --- .../ClientControllerTests.cs | 20 ++- .../Operations/ShellOperationTests.cs | 72 +++++----- SafeExamBrowser.Client/ClientContext.cs | 6 +- SafeExamBrowser.Client/ClientController.cs | 42 ++++-- SafeExamBrowser.Client/CompositionRoot.cs | 25 +++- .../Operations/ShellOperation.cs | 48 ++++--- .../SafeExamBrowser.Client.csproj | 4 + .../Mouse/MouseInterceptor.cs | 2 +- ...ExamBrowser.UserInterface.Contracts.csproj | 4 + .../Shell/IActionCenter.cs | 2 +- .../Shell/IActionCenterActivator.cs | 24 +--- .../Shell/IActivator.cs | 13 +- .../Shell/ITaskView.cs | 28 ++++ .../Shell/ITaskViewActivator.cs | 28 ++++ .../Shell/ITerminationActivator.cs | 23 +++ ...feExamBrowser.UserInterface.Desktop.csproj | 7 + .../TaskView.xaml | 12 ++ .../TaskView.xaml.cs | 32 +++++ .../ActionCenterKeyboardActivator.cs | 72 ++++++++++ .../Activators/ActionCenterTouchActivator.cs | 78 ++++++++++ .../Activators/KeyboardActivator.cs | 53 +++++++ .../Activators/TaskViewKeyboardActivator.cs | 45 ++++++ .../Activators/TerminationActivator.cs | 75 ++++++++++ .../Activators/TouchActivator.cs | 52 +++++++ ...afeExamBrowser.UserInterface.Shared.csproj | 18 +++ .../Events/MouseButtonState.cs | 2 +- .../Events/MouseHookCallback.cs | 2 +- .../Events/MouseInformation.cs | 31 ++++ .../IBounds.cs | 2 +- .../INativeMethods.cs | 5 + ...afeExamBrowser.WindowsApi.Contracts.csproj | 2 +- SafeExamBrowser.WindowsApi/Hooks/MouseHook.cs | 15 +- .../KeyboardActivator.cs | 122 ---------------- SafeExamBrowser.WindowsApi/NativeMethods.cs | 9 ++ .../SafeExamBrowser.WindowsApi.csproj | 7 - .../TerminationActivator.cs | 125 ---------------- SafeExamBrowser.WindowsApi/TouchActivator.cs | 133 ------------------ 37 files changed, 734 insertions(+), 506 deletions(-) rename SafeExamBrowser.WindowsApi.Contracts/ITerminationActivator.cs => SafeExamBrowser.UserInterface.Contracts/Shell/IActivator.cs (64%) create mode 100644 SafeExamBrowser.UserInterface.Contracts/Shell/ITaskView.cs create mode 100644 SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Contracts/Shell/ITerminationActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Desktop/TaskView.xaml create mode 100644 SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs create mode 100644 SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterKeyboardActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterTouchActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Shared/Activators/KeyboardActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Shared/Activators/TerminationActivator.cs create mode 100644 SafeExamBrowser.UserInterface.Shared/Activators/TouchActivator.cs create mode 100644 SafeExamBrowser.WindowsApi.Contracts/Events/MouseInformation.cs delete mode 100644 SafeExamBrowser.WindowsApi/KeyboardActivator.cs delete mode 100644 SafeExamBrowser.WindowsApi/TerminationActivator.cs delete mode 100644 SafeExamBrowser.WindowsApi/TouchActivator.cs diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs index dab3a886..cbbe9da5 100644 --- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs +++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs @@ -100,11 +100,11 @@ namespace SafeExamBrowser.Client.UnitTests runtimeProxy.Object, shutdown.Object, taskbar.Object, - terminationActivator.Object, text.Object, uiFactory.Object); context.AppConfig = appConfig; + context.Activators.Add(terminationActivator.Object); context.Browser = browserController.Object; context.ClientHost = clientHost.Object; context.SessionId = sessionId; @@ -423,13 +423,29 @@ namespace SafeExamBrowser.Client.UnitTests } [TestMethod] - public void Shutdown_MustCloseActionCenterAndTaskbar() + public void Shutdown_MustCloseActionCenterAndTaskbarIfEnabled() { + settings.ActionCenter.EnableActionCenter = true; + settings.Taskbar.EnableTaskbar = true; + sut.Terminate(); + actionCenter.Verify(a => a.Close(), Times.Once); taskbar.Verify(o => o.Close(), Times.Once); } + [TestMethod] + public void Shutdown_MustNotCloseActionCenterAndTaskbarIfNotEnabled() + { + settings.ActionCenter.EnableActionCenter = false; + settings.Taskbar.EnableTaskbar = false; + + sut.Terminate(); + + actionCenter.Verify(a => a.Close(), Times.Never); + taskbar.Verify(o => o.Close(), Times.Never); + } + [TestMethod] public void Shutdown_MustShowErrorMessageOnCommunicationFailure() { diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs index 6efb788f..0a5b4878 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Client.Contracts; @@ -21,7 +20,6 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell; -using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Client.UnitTests.Operations { @@ -32,7 +30,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations private Mock audio; private ClientContext context; private Mock logger; - private Mock terminationActivator; private Mock aboutInfo; private Mock aboutController; private Mock keyboard; @@ -41,6 +38,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations private Mock powerSupply; private Mock systemInfo; private Mock taskbar; + private Mock taskView; private Mock text; private Mock uiFactory; private Mock wirelessAdapter; @@ -62,7 +60,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations powerSupply = new Mock(); systemInfo = new Mock(); taskbar = new Mock(); - terminationActivator = new Mock(); + taskView = new Mock(); text = new Mock(); uiFactory = new Mock(); wirelessAdapter = new Mock(); @@ -86,7 +84,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations powerSupply.Object, systemInfo.Object, taskbar.Object, - terminationActivator.Object, + taskView.Object, text.Object, uiFactory.Object, wirelessAdapter.Object); @@ -95,28 +93,20 @@ namespace SafeExamBrowser.Client.UnitTests.Operations [TestMethod] public void Perform_MustInitializeActivators() { - var activatorMocks = new List> - { - new Mock(), - new Mock(), - new Mock() - }; + var actionCenterActivator = new Mock(); + var taskViewActivator = new Mock(); + var terminationActivator = new Mock(); + context.Activators.Add(actionCenterActivator.Object); + context.Activators.Add(taskViewActivator.Object); + context.Activators.Add(terminationActivator.Object); context.Settings.ActionCenter.EnableActionCenter = true; - foreach (var activator in activatorMocks) - { - context.Activators.Add(activator.Object); - } - sut.Perform(); - terminationActivator.Verify(t => t.Start(), Times.Once); - - foreach (var activator in activatorMocks) - { - activator.Verify(a => a.Start(), Times.Once); - } + actionCenterActivator.Verify(a => a.Start(), Times.Once); + taskViewActivator.Verify(a => a.Start(), Times.Once); + terminationActivator.Verify(a => a.Start(), Times.Once); } [TestMethod] @@ -183,6 +173,12 @@ namespace SafeExamBrowser.Client.UnitTests.Operations taskbar.Verify(t => t.AddNotificationControl(It.Is(i => i == logControl.Object)), Times.Never); } + [TestMethod] + public void Perform_MustInitializeTaskView() + { + Assert.Fail("TODO"); + } + [TestMethod] public void Perform_MustInitializeSystemComponents() { @@ -242,9 +238,15 @@ namespace SafeExamBrowser.Client.UnitTests.Operations [TestMethod] public void Perform_MustNotInitializeActionCenterIfNotEnabled() { + var actionCenterActivator = new Mock(); + + context.Activators.Add(actionCenterActivator.Object); context.Settings.ActionCenter.EnableActionCenter = false; + sut.Perform(); + actionCenter.VerifyNoOtherCalls(); + actionCenterActivator.VerifyNoOtherCalls(); } [TestMethod] @@ -258,28 +260,20 @@ namespace SafeExamBrowser.Client.UnitTests.Operations [TestMethod] public void Revert_MustTerminateActivators() { - var activatorMocks = new List> - { - new Mock(), - new Mock(), - new Mock() - }; + var actionCenterActivator = new Mock(); + var taskViewActivator = new Mock(); + var terminationActivator = new Mock(); + context.Activators.Add(actionCenterActivator.Object); + context.Activators.Add(taskViewActivator.Object); + context.Activators.Add(terminationActivator.Object); context.Settings.ActionCenter.EnableActionCenter = true; - foreach (var activator in activatorMocks) - { - context.Activators.Add(activator.Object); - } - sut.Revert(); - terminationActivator.Verify(t => t.Stop(), Times.Once); - - foreach (var activator in activatorMocks) - { - activator.Verify(a => a.Stop(), Times.Once); - } + actionCenterActivator.Verify(a => a.Stop(), Times.Once); + taskViewActivator.Verify(a => a.Stop(), Times.Once); + terminationActivator.Verify(a => a.Stop(), Times.Once); } [TestMethod] diff --git a/SafeExamBrowser.Client/ClientContext.cs b/SafeExamBrowser.Client/ClientContext.cs index 0d29a8d7..af619dff 100644 --- a/SafeExamBrowser.Client/ClientContext.cs +++ b/SafeExamBrowser.Client/ClientContext.cs @@ -23,9 +23,9 @@ namespace SafeExamBrowser.Client internal class ClientContext { /// - /// All activators for the action center. + /// All activators for shell components. /// - internal IList Activators { get; } + internal IList Activators { get; } /// /// All applications allowed for the current session. @@ -59,7 +59,7 @@ namespace SafeExamBrowser.Client internal ClientContext() { - Activators = new List(); + Activators = new List(); Applications = new List(); } } diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index ed7aece6..cc41bec7 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -51,7 +51,6 @@ namespace SafeExamBrowser.Client private Action shutdown; private ISplashScreen splashScreen; private ITaskbar taskbar; - private ITerminationActivator terminationActivator; private IText text; private IUserInterfaceFactory uiFactory; @@ -72,7 +71,6 @@ namespace SafeExamBrowser.Client IRuntimeProxy runtime, Action shutdown, ITaskbar taskbar, - ITerminationActivator terminationActivator, IText text, IUserInterfaceFactory uiFactory) { @@ -88,7 +86,6 @@ namespace SafeExamBrowser.Client this.runtime = runtime; this.shutdown = shutdown; this.taskbar = taskbar; - this.terminationActivator = terminationActivator; this.text = text; this.uiFactory = uiFactory; } @@ -140,9 +137,8 @@ namespace SafeExamBrowser.Client logger.Info("Initiating shutdown procedure..."); splashScreen = uiFactory.CreateSplashScreen(context.AppConfig); - actionCenter.Close(); - taskbar.Close(); + CloseShell(); DeregisterEvents(); var success = operations.TryRevert() == OperationResult.Success; @@ -182,7 +178,11 @@ namespace SafeExamBrowser.Client displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged; runtime.ConnectionLost += Runtime_ConnectionLost; taskbar.QuitButtonClicked += Shell_QuitButtonClicked; - terminationActivator.Activated += TerminationActivator_Activated; + + foreach (var activator in context.Activators.OfType()) + { + activator.Activated += TerminationActivator_Activated; + } } private void DeregisterEvents() @@ -193,7 +193,6 @@ namespace SafeExamBrowser.Client displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged; runtime.ConnectionLost -= Runtime_ConnectionLost; taskbar.QuitButtonClicked -= Shell_QuitButtonClicked; - terminationActivator.Activated -= TerminationActivator_Activated; if (Browser != null) { @@ -207,10 +206,33 @@ namespace SafeExamBrowser.Client ClientHost.ReconfigurationDenied -= ClientHost_ReconfigurationDenied; ClientHost.Shutdown -= ClientHost_Shutdown; } + + foreach (var activator in context.Activators.OfType()) + { + activator.Activated -= TerminationActivator_Activated; + } + } + + private void CloseShell() + { + if (Settings.ActionCenter.EnableActionCenter) + { + actionCenter.Close(); + } + + if (Settings.Taskbar.EnableTaskbar) + { + taskbar.Close(); + } } private void ShowShell() { + if (Settings.ActionCenter.EnableActionCenter) + { + actionCenter.Show(); + } + if (Settings.Taskbar.EnableTaskbar) { taskbar.Show(); @@ -527,9 +549,6 @@ namespace SafeExamBrowser.Client private void PauseActivators() { - // TODO: Same for task view activator! - terminationActivator.Pause(); - foreach (var activator in context.Activators) { activator.Pause(); @@ -538,9 +557,6 @@ namespace SafeExamBrowser.Client private void ResumeActivators() { - // TODO: Same for task view activator! - terminationActivator.Resume(); - foreach (var activator in context.Activators) { activator.Resume(); diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 2d5325e4..7212d96c 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -44,6 +44,7 @@ using SafeExamBrowser.SystemComponents.WirelessNetwork; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.UserInterface.Shared.Activators; using SafeExamBrowser.WindowsApi; using SafeExamBrowser.WindowsApi.Contracts; using Desktop = SafeExamBrowser.UserInterface.Desktop; @@ -70,7 +71,6 @@ namespace SafeExamBrowser.Client private IRuntimeProxy runtimeProxy; private ISystemInfo systemInfo; private ITaskbar taskbar; - private ITerminationActivator terminationActivator; private IText text; private ITextResource textResource; private IUserInterfaceFactory uiFactory; @@ -94,7 +94,6 @@ namespace SafeExamBrowser.Client uiFactory = BuildUserInterfaceFactory(); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client); taskbar = BuildTaskbar(); - terminationActivator = new TerminationActivator(ModuleLogger(nameof(TerminationActivator))); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), processFactory); @@ -134,7 +133,6 @@ namespace SafeExamBrowser.Client runtimeProxy, shutdown, taskbar, - terminationActivator, text, uiFactory); } @@ -244,6 +242,7 @@ namespace SafeExamBrowser.Client var logInfo = new LogNotificationInfo(text); var logController = new LogNotificationController(logger, uiFactory); var powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply))); + var taskView = BuildTaskView(); var wirelessAdapter = new WirelessAdapter(ModuleLogger(nameof(WirelessAdapter))); var operation = new ShellOperation( actionCenter, @@ -258,13 +257,15 @@ namespace SafeExamBrowser.Client powerSupply, systemInfo, taskbar, - terminationActivator, + taskView, text, uiFactory, wirelessAdapter); - context.Activators.Add(new KeyboardActivator(ModuleLogger(nameof(KeyboardActivator)))); - context.Activators.Add(new TouchActivator(ModuleLogger(nameof(TouchActivator)))); + context.Activators.Add(new ActionCenterKeyboardActivator(ModuleLogger(nameof(ActionCenterKeyboardActivator)), nativeMethods)); + context.Activators.Add(new ActionCenterTouchActivator(ModuleLogger(nameof(ActionCenterTouchActivator)), nativeMethods)); + context.Activators.Add(new TaskViewKeyboardActivator(ModuleLogger(nameof(TaskViewKeyboardActivator)), nativeMethods)); + context.Activators.Add(new TerminationActivator(ModuleLogger(nameof(TerminationActivator)), nativeMethods)); return operation; } @@ -302,6 +303,18 @@ namespace SafeExamBrowser.Client } } + private ITaskView BuildTaskView() + { + switch (uiMode) + { + case UserInterfaceMode.Mobile: + // TODO + throw new NotImplementedException(); + default: + return new Desktop.TaskView(); + } + } + private IUserInterfaceFactory BuildUserInterfaceFactory() { switch (uiMode) diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs index 31d0fdae..f67a9ffc 100644 --- a/SafeExamBrowser.Client/Operations/ShellOperation.cs +++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs @@ -18,7 +18,6 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell; -using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Client.Operations { @@ -35,7 +34,7 @@ namespace SafeExamBrowser.Client.Operations private IPowerSupply powerSupply; private ISystemInfo systemInfo; private ITaskbar taskbar; - private ITerminationActivator terminationActivator; + private ITaskView taskView; private IText text; private IUserInterfaceFactory uiFactory; private IWirelessAdapter wirelessAdapter; @@ -56,7 +55,7 @@ namespace SafeExamBrowser.Client.Operations IPowerSupply powerSupply, ISystemInfo systemInfo, ITaskbar taskbar, - ITerminationActivator terminationActivator, + ITaskView taskView, IText text, IUserInterfaceFactory uiFactory, IWirelessAdapter wirelessAdapter) : base(context) @@ -71,9 +70,9 @@ namespace SafeExamBrowser.Client.Operations this.logController = logController; this.powerSupply = powerSupply; this.systemInfo = systemInfo; - this.terminationActivator = terminationActivator; this.text = text; this.taskbar = taskbar; + this.taskView = taskView; this.uiFactory = uiFactory; this.wirelessAdapter = wirelessAdapter; } @@ -86,6 +85,7 @@ namespace SafeExamBrowser.Client.Operations InitializeSystemComponents(); InitializeActionCenter(); InitializeTaskbar(); + InitializeTaskView(); InitializeActivators(); return OperationResult.Success; @@ -105,14 +105,23 @@ namespace SafeExamBrowser.Client.Operations private void InitializeActivators() { - terminationActivator.Start(); - - if (Context.Settings.ActionCenter.EnableActionCenter) + foreach (var activator in Context.Activators) { - foreach (var activator in Context.Activators) + if (Context.Settings.ActionCenter.EnableActionCenter && activator is IActionCenterActivator actionCenterActivator) { - actionCenter.Register(activator); - activator.Start(); + actionCenter.Register(actionCenterActivator); + actionCenterActivator.Start(); + } + + if (activator is ITaskViewActivator taskViewActivator) + { + taskView.Register(taskViewActivator); + taskViewActivator.Start(); + } + + if (activator is ITerminationActivator terminationActivator) + { + terminationActivator.Start(); } } } @@ -161,6 +170,16 @@ namespace SafeExamBrowser.Client.Operations } } + private void InitializeTaskView() + { + logger.Info("Initializing task view..."); + + foreach (var application in Context.Applications) + { + taskView.Add(application); + } + } + private void InitializeApplicationsFor(Location location) { foreach (var application in Context.Applications) @@ -295,14 +314,9 @@ namespace SafeExamBrowser.Client.Operations private void TerminateActivators() { - terminationActivator.Stop(); - - if (Context.Settings.ActionCenter.EnableActionCenter) + foreach (var activator in Context.Activators) { - foreach (var activator in Context.Activators) - { - activator.Stop(); - } + activator.Stop(); } } diff --git a/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj b/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj index 9af1ffaa..6a6010cc 100644 --- a/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj +++ b/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj @@ -216,6 +216,10 @@ {89bc24dd-ff31-496e-9816-a160b686a3d4} SafeExamBrowser.UserInterface.Mobile + + {38525928-87ba-4f8c-8010-4eb97bfaae13} + SafeExamBrowser.UserInterface.Shared + {7016f080-9aa5-41b2-a225-385ad877c171} SafeExamBrowser.WindowsApi.Contracts diff --git a/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs b/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs index fbd49c28..9604257f 100644 --- a/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs +++ b/SafeExamBrowser.Monitoring/Mouse/MouseInterceptor.cs @@ -42,7 +42,7 @@ namespace SafeExamBrowser.Monitoring.Mouse } } - private bool MouseHookCallback(MouseButton button, MouseButtonState state) + private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info) { var block = false; diff --git a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj index 9f21aaac..05e1c68a 100644 --- a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj +++ b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj @@ -71,10 +71,14 @@ + + + + diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs index 7a74b4a5..8d01932a 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs @@ -12,7 +12,7 @@ using SafeExamBrowser.UserInterface.Contracts.Shell.Events; namespace SafeExamBrowser.UserInterface.Contracts.Shell { /// - /// The action center is a user interface element via which the user can access and control various aspects of the application. + /// The action center is a user interface element via which the user can access and control various aspects of SEB. /// public interface IActionCenter { diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs index b956d08d..d622cd39 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs @@ -11,9 +11,9 @@ using SafeExamBrowser.UserInterface.Contracts.Shell.Events; namespace SafeExamBrowser.UserInterface.Contracts.Shell { /// - /// A module which can be used to control the visibility of the . + /// A module which can be used to control the . /// - public interface IActionCenterActivator + public interface IActionCenterActivator : IActivator { /// /// Fired when the action center should be made visible. @@ -29,25 +29,5 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell /// Fired when the action center visibility should be toggled. /// event ActivatorEventHandler Toggled; - - /// - /// Temporarily stops processing all user input. - /// - void Pause(); - - /// - /// Resumes processing user input. - /// - void Resume(); - - /// - /// Starts monitoring user input events. - /// - void Start(); - - /// - /// Stops monitoring user input events. - /// - void Stop(); } } diff --git a/SafeExamBrowser.WindowsApi.Contracts/ITerminationActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/IActivator.cs similarity index 64% rename from SafeExamBrowser.WindowsApi.Contracts/ITerminationActivator.cs rename to SafeExamBrowser.UserInterface.Contracts/Shell/IActivator.cs index 58546c13..3423f601 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/ITerminationActivator.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/IActivator.cs @@ -6,20 +6,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.WindowsApi.Contracts.Events; - -namespace SafeExamBrowser.WindowsApi.Contracts +namespace SafeExamBrowser.UserInterface.Contracts.Shell { /// - /// A module which observes user input and indicates when the user would like to terminate the application. + /// Defines an activator for a shell component. /// - public interface ITerminationActivator + public interface IActivator { - /// - /// Fired when a termination request has been detected. - /// - event TerminationActivatorEventHandler Activated; - /// /// Temporarily stops processing all user input. /// diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskView.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskView.cs new file mode 100644 index 00000000..ae6701de --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskView.cs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 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 SafeExamBrowser.Applications.Contracts; + +namespace SafeExamBrowser.UserInterface.Contracts.Shell +{ + /// + /// The task view provides an overview of all currently running application instances. + /// + public interface ITaskView + { + /// + /// Adds the given application to the task view. + /// + void Add(IApplication application); + + /// + /// Registers the specified activator for the task view. + /// + void Register(ITaskViewActivator activator); + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs new file mode 100644 index 00000000..8d2ed19e --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 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 SafeExamBrowser.UserInterface.Contracts.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Contracts.Shell +{ + /// + /// A module which can be used to control the . + /// + public interface ITaskViewActivator : IActivator + { + /// + /// Fired when the next application instance should be selected. + /// + event ActivatorEventHandler Next; + + /// + /// Fired when the previous application instance should be selected. + /// + event ActivatorEventHandler Previous; + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITerminationActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITerminationActivator.cs new file mode 100644 index 00000000..da1206ea --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITerminationActivator.cs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 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 SafeExamBrowser.UserInterface.Contracts.Shell.Events; + +namespace SafeExamBrowser.UserInterface.Contracts.Shell +{ + /// + /// A module which observes user input and indicates when the user would like to terminate SEB. + /// + public interface ITerminationActivator : IActivator + { + /// + /// Fired when a termination request has been detected. + /// + event ActivatorEventHandler Activated; + } +} diff --git a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj index 31fa2711..28d5cf22 100644 --- a/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj +++ b/SafeExamBrowser.UserInterface.Desktop/SafeExamBrowser.UserInterface.Desktop.csproj @@ -158,6 +158,9 @@ SplashScreen.xaml + + TaskView.xaml + @@ -315,6 +318,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml new file mode 100644 index 00000000..25723e03 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs new file mode 100644 index 00000000..f9e380f3 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 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.Windows; +using SafeExamBrowser.Applications.Contracts; +using SafeExamBrowser.UserInterface.Contracts.Shell; + +namespace SafeExamBrowser.UserInterface.Desktop +{ + public partial class TaskView : Window, ITaskView + { + public TaskView() + { + InitializeComponent(); + } + + public void Add(IApplication application) + { + + } + + public void Register(ITaskViewActivator activator) + { + + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterKeyboardActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterKeyboardActivator.cs new file mode 100644 index 00000000..3f76b5be --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterKeyboardActivator.cs @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 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.Windows.Input; +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.UserInterface.Contracts.Shell.Events; +using SafeExamBrowser.WindowsApi.Contracts; +using SafeExamBrowser.WindowsApi.Contracts.Events; + +namespace SafeExamBrowser.UserInterface.Shared.Activators +{ + public class ActionCenterKeyboardActivator : KeyboardActivator, IActionCenterActivator + { + private bool A, LeftWindows; + private ILogger logger; + + public event ActivatorEventHandler Activated { add { } remove { } } + public event ActivatorEventHandler Deactivated { add { } remove { } } + public event ActivatorEventHandler Toggled; + + public ActionCenterKeyboardActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods) + { + this.logger = logger; + } + + public void Pause() + { + Paused = true; + } + + public void Resume() + { + A = false; + LeftWindows = false; + Paused = false; + } + + protected override bool Process(Key key, KeyModifier modifier, KeyState state) + { + var changed = false; + var pressed = state == KeyState.Pressed; + + switch (key) + { + case Key.A: + changed = A != pressed; + A = pressed; + break; + case Key.LWin: + changed = LeftWindows != pressed; + LeftWindows = pressed; + break; + } + + if (A && LeftWindows && changed) + { + logger.Debug("Detected toggle sequence for action center."); + Toggled?.Invoke(); + + return true; + } + + return false; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterTouchActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterTouchActivator.cs new file mode 100644 index 00000000..8720c257 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Activators/ActionCenterTouchActivator.cs @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 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.Threading.Tasks; +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.UserInterface.Contracts.Shell.Events; +using SafeExamBrowser.WindowsApi.Contracts; +using SafeExamBrowser.WindowsApi.Contracts.Events; + +namespace SafeExamBrowser.UserInterface.Shared.Activators +{ + public class ActionCenterTouchActivator : TouchActivator, IActionCenterActivator + { + private bool isDown; + private ILogger logger; + private INativeMethods nativeMethods; + + public event ActivatorEventHandler Activated; + public event ActivatorEventHandler Deactivated { add { } remove { } } + public event ActivatorEventHandler Toggled { add { } remove { } } + + public ActionCenterTouchActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods) + { + this.logger = logger; + this.nativeMethods = nativeMethods; + } + + public void Pause() + { + Paused = true; + } + + public void Resume() + { + isDown = false; + Paused = false; + } + + protected override bool Process(MouseButton button, MouseButtonState state, MouseInformation info) + { + var inActivationArea = 0 < info.X && info.X < 100; + + if (button == MouseButton.Left) + { + if (state == MouseButtonState.Released) + { + isDown = false; + } + + if (state == MouseButtonState.Pressed && inActivationArea) + { + isDown = true; + Task.Delay(100).ContinueWith(_ => CheckPosition()); + } + } + + return false; + } + + private void CheckPosition() + { + var (x, y) = nativeMethods.GetCursorPosition(); + var hasMoved = x > 200; + + if (isDown && hasMoved) + { + logger.Debug("Detected activation gesture for action center."); + Activated?.Invoke(); + } + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/KeyboardActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/KeyboardActivator.cs new file mode 100644 index 00000000..602f6c47 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Activators/KeyboardActivator.cs @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 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.Windows.Input; +using SafeExamBrowser.WindowsApi.Contracts; +using SafeExamBrowser.WindowsApi.Contracts.Events; + +namespace SafeExamBrowser.UserInterface.Shared.Activators +{ + public abstract class KeyboardActivator + { + private INativeMethods nativeMethods; + private Guid? hookId; + + protected bool Paused { get; set; } + + protected KeyboardActivator(INativeMethods nativeMethods) + { + this.nativeMethods = nativeMethods; + } + + protected abstract bool Process(Key key, KeyModifier modifier, KeyState state); + + public void Start() + { + hookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback); + } + + public void Stop() + { + if (hookId.HasValue) + { + nativeMethods.DeregisterKeyboardHook(hookId.Value); + } + } + + private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state) + { + if (!Paused) + { + return Process(KeyInterop.KeyFromVirtualKey(keyCode), modifier, state); + } + + return false; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs new file mode 100644 index 00000000..afb2f513 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 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.Windows.Input; +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.UserInterface.Contracts.Shell.Events; +using SafeExamBrowser.WindowsApi.Contracts; +using SafeExamBrowser.WindowsApi.Contracts.Events; + +namespace SafeExamBrowser.UserInterface.Shared.Activators +{ + public class TaskViewKeyboardActivator : KeyboardActivator, ITaskViewActivator + { + private ILogger logger; + + public event ActivatorEventHandler Next; + public event ActivatorEventHandler Previous; + + public TaskViewKeyboardActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods) + { + this.logger = logger; + } + + public void Pause() + { + Paused = true; + } + + public void Resume() + { + Paused = false; + } + + protected override bool Process(Key key, KeyModifier modifier, KeyState state) + { + return false; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/TerminationActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/TerminationActivator.cs new file mode 100644 index 00000000..74b0a5d0 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Activators/TerminationActivator.cs @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 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.Windows.Input; +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.UserInterface.Contracts.Shell.Events; +using SafeExamBrowser.WindowsApi.Contracts; +using SafeExamBrowser.WindowsApi.Contracts.Events; + +namespace SafeExamBrowser.UserInterface.Shared.Activators +{ + public class TerminationActivator : KeyboardActivator, ITerminationActivator + { + private bool Q, LeftCtrl, RightCtrl; + private ILogger logger; + + public event ActivatorEventHandler Activated; + + public TerminationActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods) + { + this.logger = logger; + } + + public void Pause() + { + Paused = true; + } + + public void Resume() + { + Q = false; + LeftCtrl = false; + RightCtrl = false; + Paused = false; + } + + protected override bool Process(Key key, KeyModifier modifier, KeyState state) + { + var changed = false; + var pressed = state == KeyState.Pressed; + + switch (key) + { + case Key.Q: + changed = Q != pressed; + Q = pressed; + break; + case Key.LeftCtrl: + changed = LeftCtrl != pressed; + LeftCtrl = pressed; + break; + case Key.RightCtrl: + changed = RightCtrl != pressed; + RightCtrl = pressed; + break; + } + + if (Q && (LeftCtrl || RightCtrl) && changed) + { + logger.Debug("Detected termination sequence."); + Activated?.Invoke(); + + return true; + } + + return false; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/TouchActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/TouchActivator.cs new file mode 100644 index 00000000..4cb6226d --- /dev/null +++ b/SafeExamBrowser.UserInterface.Shared/Activators/TouchActivator.cs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 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 SafeExamBrowser.WindowsApi.Contracts; +using SafeExamBrowser.WindowsApi.Contracts.Events; + +namespace SafeExamBrowser.UserInterface.Shared.Activators +{ + public abstract class TouchActivator + { + private INativeMethods nativeMethods; + private Guid? hookId; + + protected bool Paused { get; set; } + + protected TouchActivator(INativeMethods nativeMethods) + { + this.nativeMethods = nativeMethods; + } + + protected abstract bool Process(MouseButton button, MouseButtonState state, MouseInformation info); + + public void Start() + { + hookId = nativeMethods.RegisterMouseHook(MouseHookCallback); + } + + public void Stop() + { + if (hookId.HasValue) + { + nativeMethods.DeregisterMouseHook(hookId.Value); + } + } + + private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info) + { + if (!Paused && info.IsTouch) + { + return Process(button, state, info); + } + + return false; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj index 6ad27680..04c90897 100644 --- a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj +++ b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj @@ -63,6 +63,12 @@ + + + + + + Code @@ -75,6 +81,18 @@ {fe0e1224-b447-4b14-81e7-ed7d84822aa0} SafeExamBrowser.Core.Contracts + + {64ea30fb-11d4-436a-9c2b-88566285363e} + SafeExamBrowser.Logging.Contracts + + + {c7889e97-6ff6-4a58-b7cb-521ed276b316} + SafeExamBrowser.UserInterface.Contracts + + + {7016f080-9aa5-41b2-a225-385ad877c171} + SafeExamBrowser.WindowsApi.Contracts + \ No newline at end of file diff --git a/SafeExamBrowser.WindowsApi.Contracts/Events/MouseButtonState.cs b/SafeExamBrowser.WindowsApi.Contracts/Events/MouseButtonState.cs index a4e9e4a1..74754167 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/Events/MouseButtonState.cs +++ b/SafeExamBrowser.WindowsApi.Contracts/Events/MouseButtonState.cs @@ -9,7 +9,7 @@ namespace SafeExamBrowser.WindowsApi.Contracts.Events { /// - /// The mouse button states which can be detected a mouse hook. + /// The mouse button states which can be detected by a mouse hook. /// public enum MouseButtonState { diff --git a/SafeExamBrowser.WindowsApi.Contracts/Events/MouseHookCallback.cs b/SafeExamBrowser.WindowsApi.Contracts/Events/MouseHookCallback.cs index 1a5c9f65..e56e983b 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/Events/MouseHookCallback.cs +++ b/SafeExamBrowser.WindowsApi.Contracts/Events/MouseHookCallback.cs @@ -11,5 +11,5 @@ namespace SafeExamBrowser.WindowsApi.Contracts.Events /// /// The callback for a mouse hook. Return true to consume (i.e. block) the user input, otherwise false. /// - public delegate bool MouseHookCallback(MouseButton button, MouseButtonState state); + public delegate bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info); } diff --git a/SafeExamBrowser.WindowsApi.Contracts/Events/MouseInformation.cs b/SafeExamBrowser.WindowsApi.Contracts/Events/MouseInformation.cs new file mode 100644 index 00000000..cabe1976 --- /dev/null +++ b/SafeExamBrowser.WindowsApi.Contracts/Events/MouseInformation.cs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 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.Contracts.Events +{ + /// + /// The mouse information which can be detected by a mouse hook. + /// + public class MouseInformation + { + /// + /// Indicates whether the mouse event originates from a touch input by the user. + /// + public bool IsTouch { get; set; } + + /// + /// The X coordinate of the current mouse position. + /// + public int X { get; set; } + + /// + /// The Y coordinate of the current mouse position. + /// + public int Y { get; set; } + } +} diff --git a/SafeExamBrowser.WindowsApi.Contracts/IBounds.cs b/SafeExamBrowser.WindowsApi.Contracts/IBounds.cs index 2dc824aa..5f20cc48 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/IBounds.cs +++ b/SafeExamBrowser.WindowsApi.Contracts/IBounds.cs @@ -9,7 +9,7 @@ namespace SafeExamBrowser.WindowsApi.Contracts { /// - /// Defines rectangular bounds, e.g. used for display-related operations (see ). + /// Defines rectangular bounds, e.g. used for display-related operations. /// public interface IBounds { diff --git a/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs b/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs index 255e3a56..c0769970 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs +++ b/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs @@ -49,6 +49,11 @@ namespace SafeExamBrowser.WindowsApi.Contracts /// void EmptyClipboard(); + /// + /// Retrieves the current position of the mouse cursor. + /// + (int x, int y) GetCursorPosition(); + /// /// Retrieves a collection of handles to all currently open (i.e. visible) windows. /// diff --git a/SafeExamBrowser.WindowsApi.Contracts/SafeExamBrowser.WindowsApi.Contracts.csproj b/SafeExamBrowser.WindowsApi.Contracts/SafeExamBrowser.WindowsApi.Contracts.csproj index e33dbce9..7e240162 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/SafeExamBrowser.WindowsApi.Contracts.csproj +++ b/SafeExamBrowser.WindowsApi.Contracts/SafeExamBrowser.WindowsApi.Contracts.csproj @@ -60,6 +60,7 @@ + @@ -70,7 +71,6 @@ - diff --git a/SafeExamBrowser.WindowsApi/Hooks/MouseHook.cs b/SafeExamBrowser.WindowsApi/Hooks/MouseHook.cs index 815bb8e0..e7dc0c24 100644 --- a/SafeExamBrowser.WindowsApi/Hooks/MouseHook.cs +++ b/SafeExamBrowser.WindowsApi/Hooks/MouseHook.cs @@ -54,8 +54,9 @@ namespace SafeExamBrowser.WindowsApi.Hooks var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); var button = GetButton(wParam.ToInt32()); var state = GetState(wParam.ToInt32()); + var info = GetInfo(mouseData); - if (callback(button, state)) + if (callback(button, state, info)) { return (IntPtr) 1; } @@ -91,6 +92,18 @@ namespace SafeExamBrowser.WindowsApi.Hooks } } + private MouseInformation GetInfo(MSLLHOOKSTRUCT mouseData) + { + var info = new MouseInformation(); + var extraInfo = mouseData.DwExtraInfo.ToUInt32(); + + info.IsTouch = (extraInfo & Constant.MOUSEEVENTF_MASK) == Constant.MOUSEEVENTF_FROMTOUCH; + info.X = mouseData.Point.X; + info.Y = mouseData.Point.Y; + + return info; + } + private MouseButtonState GetState(int wParam) { switch (wParam) diff --git a/SafeExamBrowser.WindowsApi/KeyboardActivator.cs b/SafeExamBrowser.WindowsApi/KeyboardActivator.cs deleted file mode 100644 index 7c15500b..00000000 --- a/SafeExamBrowser.WindowsApi/KeyboardActivator.cs +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2019 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 System.Threading; -using SafeExamBrowser.Logging.Contracts; -using SafeExamBrowser.UserInterface.Contracts.Shell; -using SafeExamBrowser.UserInterface.Contracts.Shell.Events; -using SafeExamBrowser.WindowsApi.Constants; -using SafeExamBrowser.WindowsApi.Delegates; -using SafeExamBrowser.WindowsApi.Types; - -namespace SafeExamBrowser.WindowsApi -{ - public class KeyboardActivator : IActionCenterActivator - { - private bool A, LeftWindows, paused; - private IntPtr handle; - private HookDelegate hookDelegate; - private ILogger logger; - - public event ActivatorEventHandler Activated { add { } remove { } } - public event ActivatorEventHandler Deactivated { add { } remove { } } - public event ActivatorEventHandler Toggled; - - public KeyboardActivator(ILogger logger) - { - this.logger = logger; - } - - public void Pause() - { - paused = true; - } - - public void Resume() - { - A = false; - LeftWindows = false; - paused = false; - } - - public void Start() - { - var hookReadyEvent = new AutoResetEvent(false); - var hookThread = new Thread(() => - { - var sleepEvent = new AutoResetEvent(false); - var process = System.Diagnostics.Process.GetCurrentProcess(); - var module = process.MainModule; - var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName); - - // IMPORTANT: - // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. - // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - hookDelegate = new HookDelegate(LowLevelKeyboardProc); - - handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookDelegate, moduleHandle, 0); - hookReadyEvent.Set(); - - while (true) - { - sleepEvent.WaitOne(); - } - }); - - hookThread.SetApartmentState(ApartmentState.STA); - hookThread.IsBackground = true; - hookThread.Start(); - - hookReadyEvent.WaitOne(); - } - - public void Stop() - { - User32.UnhookWindowsHookEx(handle); - } - - private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam) - { - if (nCode >= 0 && !paused) - { - var changed = false; - var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); - var pressed = IsPressed(wParam.ToInt32()); - - switch (keyData.KeyCode) - { - case (uint) VirtualKeyCode.A: - changed = A != pressed; - A = pressed; - break; - case (uint) VirtualKeyCode.LeftWindows: - changed = LeftWindows != pressed; - LeftWindows = pressed; - break; - } - - if (A && LeftWindows && changed) - { - logger.Debug("Detected toggle sequence for action center."); - Toggled?.Invoke(); - - return (IntPtr) 1; - } - } - - return User32.CallNextHookEx(handle, nCode, wParam, lParam); - } - - private bool IsPressed(int wParam) - { - return wParam == Constant.WM_KEYDOWN || wParam == Constant.WM_SYSKEYDOWN; - } - } -} diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index 70402fa7..d0393f0d 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -114,6 +114,15 @@ namespace SafeExamBrowser.WindowsApi } } + public (int x, int y) GetCursorPosition() + { + var position = new POINT(); + + User32.GetCursorPos(ref position); + + return (position.X, position.Y); + } + public IEnumerable GetOpenWindows() { var windows = new List(); diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj index e0597cea..7494b737 100644 --- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj +++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj @@ -66,14 +66,11 @@ - - - @@ -100,10 +97,6 @@ {64ea30fb-11d4-436a-9c2b-88566285363e} SafeExamBrowser.Logging.Contracts - - {c7889e97-6ff6-4a58-b7cb-521ed276b316} - SafeExamBrowser.UserInterface.Contracts - {7016f080-9aa5-41b2-a225-385ad877c171} SafeExamBrowser.WindowsApi.Contracts diff --git a/SafeExamBrowser.WindowsApi/TerminationActivator.cs b/SafeExamBrowser.WindowsApi/TerminationActivator.cs deleted file mode 100644 index e8b7f09a..00000000 --- a/SafeExamBrowser.WindowsApi/TerminationActivator.cs +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2019 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 System.Threading; -using SafeExamBrowser.Logging.Contracts; -using SafeExamBrowser.WindowsApi.Contracts; -using SafeExamBrowser.WindowsApi.Contracts.Events; -using SafeExamBrowser.WindowsApi.Constants; -using SafeExamBrowser.WindowsApi.Delegates; -using SafeExamBrowser.WindowsApi.Types; - -namespace SafeExamBrowser.WindowsApi -{ - public class TerminationActivator : ITerminationActivator - { - private bool Q, LeftCtrl, RightCtrl, paused; - private IntPtr handle; - private HookDelegate hookDelegate; - private ILogger logger; - - public event TerminationActivatorEventHandler Activated; - - public TerminationActivator(ILogger logger) - { - this.logger = logger; - } - - public void Pause() - { - paused = true; - } - - public void Resume() - { - Q = false; - LeftCtrl = false; - RightCtrl = false; - paused = false; - } - - public void Start() - { - var hookReadyEvent = new AutoResetEvent(false); - var hookThread = new Thread(() => - { - var sleepEvent = new AutoResetEvent(false); - var process = System.Diagnostics.Process.GetCurrentProcess(); - var module = process.MainModule; - var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName); - - // IMPORTANT: - // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. - // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - hookDelegate = new HookDelegate(LowLevelKeyboardProc); - - handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookDelegate, moduleHandle, 0); - hookReadyEvent.Set(); - - while (true) - { - sleepEvent.WaitOne(); - } - }); - - hookThread.SetApartmentState(ApartmentState.STA); - hookThread.IsBackground = true; - hookThread.Start(); - - hookReadyEvent.WaitOne(); - } - - public void Stop() - { - User32.UnhookWindowsHookEx(handle); - } - - private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam) - { - if (nCode >= 0 && !paused) - { - var changed = false; - var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); - var pressed = IsPressed(wParam.ToInt32()); - - switch (keyData.KeyCode) - { - case (uint) VirtualKeyCode.Q: - changed = Q != pressed; - Q = pressed; - break; - case (uint) VirtualKeyCode.LeftControl: - changed = LeftCtrl != pressed; - LeftCtrl = pressed; - break; - case (uint) VirtualKeyCode.RightControl: - changed = RightCtrl != pressed; - RightCtrl = pressed; - break; - } - - if (Q && (LeftCtrl || RightCtrl) && changed) - { - logger.Debug("Detected termination sequence."); - Activated?.Invoke(); - - return (IntPtr) 1; - } - } - - return User32.CallNextHookEx(handle, nCode, wParam, lParam); - } - - private bool IsPressed(int wParam) - { - return wParam == Constant.WM_KEYDOWN || wParam == Constant.WM_SYSKEYDOWN; - } - } -} diff --git a/SafeExamBrowser.WindowsApi/TouchActivator.cs b/SafeExamBrowser.WindowsApi/TouchActivator.cs deleted file mode 100644 index 85ee5e9a..00000000 --- a/SafeExamBrowser.WindowsApi/TouchActivator.cs +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2019 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 System.Threading; -using System.Threading.Tasks; -using SafeExamBrowser.Logging.Contracts; -using SafeExamBrowser.UserInterface.Contracts.Shell; -using SafeExamBrowser.UserInterface.Contracts.Shell.Events; -using SafeExamBrowser.WindowsApi.Constants; -using SafeExamBrowser.WindowsApi.Delegates; -using SafeExamBrowser.WindowsApi.Types; - -namespace SafeExamBrowser.WindowsApi -{ - public class TouchActivator : IActionCenterActivator - { - private HookDelegate hookDelegate; - private IntPtr handle; - private bool isDown, paused; - private ILogger logger; - - public event ActivatorEventHandler Activated; - public event ActivatorEventHandler Deactivated { add { } remove { } } - public event ActivatorEventHandler Toggled { add { } remove { } } - - public TouchActivator(ILogger logger) - { - this.logger = logger; - } - - public void Pause() - { - paused = true; - } - - public void Resume() - { - isDown = false; - paused = false; - } - - public void Start() - { - var hookReadyEvent = new AutoResetEvent(false); - var hookThread = new Thread(() => - { - var sleepEvent = new AutoResetEvent(false); - var process = System.Diagnostics.Process.GetCurrentProcess(); - var module = process.MainModule; - var moduleHandle = Kernel32.GetModuleHandle(module.ModuleName); - - // IMPORTANT: - // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. - // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - hookDelegate = new HookDelegate(LowLevelMouseProc); - - handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookDelegate, moduleHandle, 0); - hookReadyEvent.Set(); - - while (true) - { - sleepEvent.WaitOne(); - } - }); - - hookThread.SetApartmentState(ApartmentState.STA); - hookThread.IsBackground = true; - hookThread.Start(); - - hookReadyEvent.WaitOne(); - } - - public void Stop() - { - User32.UnhookWindowsHookEx(handle); - } - - private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam) - { - if (nCode >= 0 && !paused && !Ignore(wParam.ToInt32())) - { - var message = wParam.ToInt32(); - var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); - var position = $"{mouseData.Point.X}/{mouseData.Point.Y}"; - var extraInfo = mouseData.DwExtraInfo.ToUInt32(); - var isTouch = (extraInfo & Constant.MOUSEEVENTF_MASK) == Constant.MOUSEEVENTF_FROMTOUCH; - var inActivationArea = 0 < mouseData.Point.X && mouseData.Point.X < 100; - - if (isTouch) - { - if (message == Constant.WM_LBUTTONUP) - { - isDown = false; - } - - if (message == Constant.WM_LBUTTONDOWN && inActivationArea) - { - isDown = true; - Task.Delay(100).ContinueWith(_ => CheckPosition()); - } - } - } - - return User32.CallNextHookEx(handle, nCode, wParam, lParam); - } - - private void CheckPosition() - { - var position = new POINT(); - User32.GetCursorPos(ref position); - var hasMoved = position.X > 200; - - if (isDown && hasMoved) - { - logger.Debug("Detected activation gesture for action center."); - Activated?.Invoke(); - } - } - - private bool Ignore(int wParam) - { - // For performance reasons, ignore mouse movement and wheel rotation... - return wParam == Constant.WM_MOUSEMOVE || wParam == Constant.WM_MOUSEWHEEL; - } - } -}