SEBWIN-312: Implemented scaffolding for task view and its keyboard activator. Finally consolidated keyboard and mouse hooks and resolved dependency from WindowsApi to UI.

This commit is contained in:
dbuechel 2019-11-14 14:03:43 +01:00
parent 4f930a26d8
commit 5f31656649
37 changed files with 734 additions and 506 deletions

View file

@ -100,11 +100,11 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Object, runtimeProxy.Object,
shutdown.Object, shutdown.Object,
taskbar.Object, taskbar.Object,
terminationActivator.Object,
text.Object, text.Object,
uiFactory.Object); uiFactory.Object);
context.AppConfig = appConfig; context.AppConfig = appConfig;
context.Activators.Add(terminationActivator.Object);
context.Browser = browserController.Object; context.Browser = browserController.Object;
context.ClientHost = clientHost.Object; context.ClientHost = clientHost.Object;
context.SessionId = sessionId; context.SessionId = sessionId;
@ -423,13 +423,29 @@ namespace SafeExamBrowser.Client.UnitTests
} }
[TestMethod] [TestMethod]
public void Shutdown_MustCloseActionCenterAndTaskbar() public void Shutdown_MustCloseActionCenterAndTaskbarIfEnabled()
{ {
settings.ActionCenter.EnableActionCenter = true;
settings.Taskbar.EnableTaskbar = true;
sut.Terminate(); sut.Terminate();
actionCenter.Verify(a => a.Close(), Times.Once); actionCenter.Verify(a => a.Close(), Times.Once);
taskbar.Verify(o => o.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] [TestMethod]
public void Shutdown_MustShowErrorMessageOnCommunicationFailure() public void Shutdown_MustShowErrorMessageOnCommunicationFailure()
{ {

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Client.Contracts;
@ -21,7 +20,6 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Client.UnitTests.Operations namespace SafeExamBrowser.Client.UnitTests.Operations
{ {
@ -32,7 +30,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<IAudio> audio; private Mock<IAudio> audio;
private ClientContext context; private ClientContext context;
private Mock<ILogger> logger; private Mock<ILogger> logger;
private Mock<ITerminationActivator> terminationActivator;
private Mock<INotificationInfo> aboutInfo; private Mock<INotificationInfo> aboutInfo;
private Mock<INotificationController> aboutController; private Mock<INotificationController> aboutController;
private Mock<IKeyboard> keyboard; private Mock<IKeyboard> keyboard;
@ -41,6 +38,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<IPowerSupply> powerSupply; private Mock<IPowerSupply> powerSupply;
private Mock<ISystemInfo> systemInfo; private Mock<ISystemInfo> systemInfo;
private Mock<ITaskbar> taskbar; private Mock<ITaskbar> taskbar;
private Mock<ITaskView> taskView;
private Mock<IText> text; private Mock<IText> text;
private Mock<IUserInterfaceFactory> uiFactory; private Mock<IUserInterfaceFactory> uiFactory;
private Mock<IWirelessAdapter> wirelessAdapter; private Mock<IWirelessAdapter> wirelessAdapter;
@ -62,7 +60,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
powerSupply = new Mock<IPowerSupply>(); powerSupply = new Mock<IPowerSupply>();
systemInfo = new Mock<ISystemInfo>(); systemInfo = new Mock<ISystemInfo>();
taskbar = new Mock<ITaskbar>(); taskbar = new Mock<ITaskbar>();
terminationActivator = new Mock<ITerminationActivator>(); taskView = new Mock<ITaskView>();
text = new Mock<IText>(); text = new Mock<IText>();
uiFactory = new Mock<IUserInterfaceFactory>(); uiFactory = new Mock<IUserInterfaceFactory>();
wirelessAdapter = new Mock<IWirelessAdapter>(); wirelessAdapter = new Mock<IWirelessAdapter>();
@ -86,7 +84,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
powerSupply.Object, powerSupply.Object,
systemInfo.Object, systemInfo.Object,
taskbar.Object, taskbar.Object,
terminationActivator.Object, taskView.Object,
text.Object, text.Object,
uiFactory.Object, uiFactory.Object,
wirelessAdapter.Object); wirelessAdapter.Object);
@ -95,28 +93,20 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod] [TestMethod]
public void Perform_MustInitializeActivators() public void Perform_MustInitializeActivators()
{ {
var activatorMocks = new List<Mock<IActionCenterActivator>> var actionCenterActivator = new Mock<IActionCenterActivator>();
{ var taskViewActivator = new Mock<ITaskViewActivator>();
new Mock<IActionCenterActivator>(), var terminationActivator = new Mock<ITerminationActivator>();
new Mock<IActionCenterActivator>(),
new Mock<IActionCenterActivator>()
};
context.Activators.Add(actionCenterActivator.Object);
context.Activators.Add(taskViewActivator.Object);
context.Activators.Add(terminationActivator.Object);
context.Settings.ActionCenter.EnableActionCenter = true; context.Settings.ActionCenter.EnableActionCenter = true;
foreach (var activator in activatorMocks)
{
context.Activators.Add(activator.Object);
}
sut.Perform(); sut.Perform();
terminationActivator.Verify(t => t.Start(), Times.Once); actionCenterActivator.Verify(a => a.Start(), Times.Once);
taskViewActivator.Verify(a => a.Start(), Times.Once);
foreach (var activator in activatorMocks) terminationActivator.Verify(a => a.Start(), Times.Once);
{
activator.Verify(a => a.Start(), Times.Once);
}
} }
[TestMethod] [TestMethod]
@ -183,6 +173,12 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
taskbar.Verify(t => t.AddNotificationControl(It.Is<INotificationControl>(i => i == logControl.Object)), Times.Never); taskbar.Verify(t => t.AddNotificationControl(It.Is<INotificationControl>(i => i == logControl.Object)), Times.Never);
} }
[TestMethod]
public void Perform_MustInitializeTaskView()
{
Assert.Fail("TODO");
}
[TestMethod] [TestMethod]
public void Perform_MustInitializeSystemComponents() public void Perform_MustInitializeSystemComponents()
{ {
@ -242,9 +238,15 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod] [TestMethod]
public void Perform_MustNotInitializeActionCenterIfNotEnabled() public void Perform_MustNotInitializeActionCenterIfNotEnabled()
{ {
var actionCenterActivator = new Mock<IActionCenterActivator>();
context.Activators.Add(actionCenterActivator.Object);
context.Settings.ActionCenter.EnableActionCenter = false; context.Settings.ActionCenter.EnableActionCenter = false;
sut.Perform(); sut.Perform();
actionCenter.VerifyNoOtherCalls(); actionCenter.VerifyNoOtherCalls();
actionCenterActivator.VerifyNoOtherCalls();
} }
[TestMethod] [TestMethod]
@ -258,28 +260,20 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod] [TestMethod]
public void Revert_MustTerminateActivators() public void Revert_MustTerminateActivators()
{ {
var activatorMocks = new List<Mock<IActionCenterActivator>> var actionCenterActivator = new Mock<IActionCenterActivator>();
{ var taskViewActivator = new Mock<ITaskViewActivator>();
new Mock<IActionCenterActivator>(), var terminationActivator = new Mock<ITerminationActivator>();
new Mock<IActionCenterActivator>(),
new Mock<IActionCenterActivator>()
};
context.Activators.Add(actionCenterActivator.Object);
context.Activators.Add(taskViewActivator.Object);
context.Activators.Add(terminationActivator.Object);
context.Settings.ActionCenter.EnableActionCenter = true; context.Settings.ActionCenter.EnableActionCenter = true;
foreach (var activator in activatorMocks)
{
context.Activators.Add(activator.Object);
}
sut.Revert(); sut.Revert();
terminationActivator.Verify(t => t.Stop(), Times.Once); actionCenterActivator.Verify(a => a.Stop(), Times.Once);
taskViewActivator.Verify(a => a.Stop(), Times.Once);
foreach (var activator in activatorMocks) terminationActivator.Verify(a => a.Stop(), Times.Once);
{
activator.Verify(a => a.Stop(), Times.Once);
}
} }
[TestMethod] [TestMethod]

View file

@ -23,9 +23,9 @@ namespace SafeExamBrowser.Client
internal class ClientContext internal class ClientContext
{ {
/// <summary> /// <summary>
/// All activators for the action center. /// All activators for shell components.
/// </summary> /// </summary>
internal IList<IActionCenterActivator> Activators { get; } internal IList<IActivator> Activators { get; }
/// <summary> /// <summary>
/// All applications allowed for the current session. /// All applications allowed for the current session.
@ -59,7 +59,7 @@ namespace SafeExamBrowser.Client
internal ClientContext() internal ClientContext()
{ {
Activators = new List<IActionCenterActivator>(); Activators = new List<IActivator>();
Applications = new List<IApplication>(); Applications = new List<IApplication>();
} }
} }

View file

@ -51,7 +51,6 @@ namespace SafeExamBrowser.Client
private Action shutdown; private Action shutdown;
private ISplashScreen splashScreen; private ISplashScreen splashScreen;
private ITaskbar taskbar; private ITaskbar taskbar;
private ITerminationActivator terminationActivator;
private IText text; private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
@ -72,7 +71,6 @@ namespace SafeExamBrowser.Client
IRuntimeProxy runtime, IRuntimeProxy runtime,
Action shutdown, Action shutdown,
ITaskbar taskbar, ITaskbar taskbar,
ITerminationActivator terminationActivator,
IText text, IText text,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
{ {
@ -88,7 +86,6 @@ namespace SafeExamBrowser.Client
this.runtime = runtime; this.runtime = runtime;
this.shutdown = shutdown; this.shutdown = shutdown;
this.taskbar = taskbar; this.taskbar = taskbar;
this.terminationActivator = terminationActivator;
this.text = text; this.text = text;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
} }
@ -140,9 +137,8 @@ namespace SafeExamBrowser.Client
logger.Info("Initiating shutdown procedure..."); logger.Info("Initiating shutdown procedure...");
splashScreen = uiFactory.CreateSplashScreen(context.AppConfig); splashScreen = uiFactory.CreateSplashScreen(context.AppConfig);
actionCenter.Close();
taskbar.Close();
CloseShell();
DeregisterEvents(); DeregisterEvents();
var success = operations.TryRevert() == OperationResult.Success; var success = operations.TryRevert() == OperationResult.Success;
@ -182,7 +178,11 @@ namespace SafeExamBrowser.Client
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost += Runtime_ConnectionLost; runtime.ConnectionLost += Runtime_ConnectionLost;
taskbar.QuitButtonClicked += Shell_QuitButtonClicked; taskbar.QuitButtonClicked += Shell_QuitButtonClicked;
terminationActivator.Activated += TerminationActivator_Activated;
foreach (var activator in context.Activators.OfType<ITerminationActivator>())
{
activator.Activated += TerminationActivator_Activated;
}
} }
private void DeregisterEvents() private void DeregisterEvents()
@ -193,7 +193,6 @@ namespace SafeExamBrowser.Client
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost -= Runtime_ConnectionLost; runtime.ConnectionLost -= Runtime_ConnectionLost;
taskbar.QuitButtonClicked -= Shell_QuitButtonClicked; taskbar.QuitButtonClicked -= Shell_QuitButtonClicked;
terminationActivator.Activated -= TerminationActivator_Activated;
if (Browser != null) if (Browser != null)
{ {
@ -207,10 +206,33 @@ namespace SafeExamBrowser.Client
ClientHost.ReconfigurationDenied -= ClientHost_ReconfigurationDenied; ClientHost.ReconfigurationDenied -= ClientHost_ReconfigurationDenied;
ClientHost.Shutdown -= ClientHost_Shutdown; ClientHost.Shutdown -= ClientHost_Shutdown;
} }
foreach (var activator in context.Activators.OfType<ITerminationActivator>())
{
activator.Activated -= TerminationActivator_Activated;
}
}
private void CloseShell()
{
if (Settings.ActionCenter.EnableActionCenter)
{
actionCenter.Close();
}
if (Settings.Taskbar.EnableTaskbar)
{
taskbar.Close();
}
} }
private void ShowShell() private void ShowShell()
{ {
if (Settings.ActionCenter.EnableActionCenter)
{
actionCenter.Show();
}
if (Settings.Taskbar.EnableTaskbar) if (Settings.Taskbar.EnableTaskbar)
{ {
taskbar.Show(); taskbar.Show();
@ -527,9 +549,6 @@ namespace SafeExamBrowser.Client
private void PauseActivators() private void PauseActivators()
{ {
// TODO: Same for task view activator!
terminationActivator.Pause();
foreach (var activator in context.Activators) foreach (var activator in context.Activators)
{ {
activator.Pause(); activator.Pause();
@ -538,9 +557,6 @@ namespace SafeExamBrowser.Client
private void ResumeActivators() private void ResumeActivators()
{ {
// TODO: Same for task view activator!
terminationActivator.Resume();
foreach (var activator in context.Activators) foreach (var activator in context.Activators)
{ {
activator.Resume(); activator.Resume();

View file

@ -44,6 +44,7 @@ using SafeExamBrowser.SystemComponents.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Shared.Activators;
using SafeExamBrowser.WindowsApi; using SafeExamBrowser.WindowsApi;
using SafeExamBrowser.WindowsApi.Contracts; using SafeExamBrowser.WindowsApi.Contracts;
using Desktop = SafeExamBrowser.UserInterface.Desktop; using Desktop = SafeExamBrowser.UserInterface.Desktop;
@ -70,7 +71,6 @@ namespace SafeExamBrowser.Client
private IRuntimeProxy runtimeProxy; private IRuntimeProxy runtimeProxy;
private ISystemInfo systemInfo; private ISystemInfo systemInfo;
private ITaskbar taskbar; private ITaskbar taskbar;
private ITerminationActivator terminationActivator;
private IText text; private IText text;
private ITextResource textResource; private ITextResource textResource;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
@ -94,7 +94,6 @@ namespace SafeExamBrowser.Client
uiFactory = BuildUserInterfaceFactory(); uiFactory = BuildUserInterfaceFactory();
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
taskbar = BuildTaskbar(); taskbar = BuildTaskbar();
terminationActivator = new TerminationActivator(ModuleLogger(nameof(TerminationActivator)));
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), processFactory); var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), processFactory);
@ -134,7 +133,6 @@ namespace SafeExamBrowser.Client
runtimeProxy, runtimeProxy,
shutdown, shutdown,
taskbar, taskbar,
terminationActivator,
text, text,
uiFactory); uiFactory);
} }
@ -244,6 +242,7 @@ namespace SafeExamBrowser.Client
var logInfo = new LogNotificationInfo(text); var logInfo = new LogNotificationInfo(text);
var logController = new LogNotificationController(logger, uiFactory); var logController = new LogNotificationController(logger, uiFactory);
var powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply))); var powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply)));
var taskView = BuildTaskView();
var wirelessAdapter = new WirelessAdapter(ModuleLogger(nameof(WirelessAdapter))); var wirelessAdapter = new WirelessAdapter(ModuleLogger(nameof(WirelessAdapter)));
var operation = new ShellOperation( var operation = new ShellOperation(
actionCenter, actionCenter,
@ -258,13 +257,15 @@ namespace SafeExamBrowser.Client
powerSupply, powerSupply,
systemInfo, systemInfo,
taskbar, taskbar,
terminationActivator, taskView,
text, text,
uiFactory, uiFactory,
wirelessAdapter); wirelessAdapter);
context.Activators.Add(new KeyboardActivator(ModuleLogger(nameof(KeyboardActivator)))); context.Activators.Add(new ActionCenterKeyboardActivator(ModuleLogger(nameof(ActionCenterKeyboardActivator)), nativeMethods));
context.Activators.Add(new TouchActivator(ModuleLogger(nameof(TouchActivator)))); 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; 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() private IUserInterfaceFactory BuildUserInterfaceFactory()
{ {
switch (uiMode) switch (uiMode)

View file

@ -18,7 +18,6 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Client.Operations namespace SafeExamBrowser.Client.Operations
{ {
@ -35,7 +34,7 @@ namespace SafeExamBrowser.Client.Operations
private IPowerSupply powerSupply; private IPowerSupply powerSupply;
private ISystemInfo systemInfo; private ISystemInfo systemInfo;
private ITaskbar taskbar; private ITaskbar taskbar;
private ITerminationActivator terminationActivator; private ITaskView taskView;
private IText text; private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
private IWirelessAdapter wirelessAdapter; private IWirelessAdapter wirelessAdapter;
@ -56,7 +55,7 @@ namespace SafeExamBrowser.Client.Operations
IPowerSupply powerSupply, IPowerSupply powerSupply,
ISystemInfo systemInfo, ISystemInfo systemInfo,
ITaskbar taskbar, ITaskbar taskbar,
ITerminationActivator terminationActivator, ITaskView taskView,
IText text, IText text,
IUserInterfaceFactory uiFactory, IUserInterfaceFactory uiFactory,
IWirelessAdapter wirelessAdapter) : base(context) IWirelessAdapter wirelessAdapter) : base(context)
@ -71,9 +70,9 @@ namespace SafeExamBrowser.Client.Operations
this.logController = logController; this.logController = logController;
this.powerSupply = powerSupply; this.powerSupply = powerSupply;
this.systemInfo = systemInfo; this.systemInfo = systemInfo;
this.terminationActivator = terminationActivator;
this.text = text; this.text = text;
this.taskbar = taskbar; this.taskbar = taskbar;
this.taskView = taskView;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
this.wirelessAdapter = wirelessAdapter; this.wirelessAdapter = wirelessAdapter;
} }
@ -86,6 +85,7 @@ namespace SafeExamBrowser.Client.Operations
InitializeSystemComponents(); InitializeSystemComponents();
InitializeActionCenter(); InitializeActionCenter();
InitializeTaskbar(); InitializeTaskbar();
InitializeTaskView();
InitializeActivators(); InitializeActivators();
return OperationResult.Success; return OperationResult.Success;
@ -105,14 +105,23 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeActivators() private void InitializeActivators()
{ {
terminationActivator.Start(); foreach (var activator in Context.Activators)
if (Context.Settings.ActionCenter.EnableActionCenter)
{ {
foreach (var activator in Context.Activators) if (Context.Settings.ActionCenter.EnableActionCenter && activator is IActionCenterActivator actionCenterActivator)
{ {
actionCenter.Register(activator); actionCenter.Register(actionCenterActivator);
activator.Start(); 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) private void InitializeApplicationsFor(Location location)
{ {
foreach (var application in Context.Applications) foreach (var application in Context.Applications)
@ -295,14 +314,9 @@ namespace SafeExamBrowser.Client.Operations
private void TerminateActivators() private void TerminateActivators()
{ {
terminationActivator.Stop(); foreach (var activator in Context.Activators)
if (Context.Settings.ActionCenter.EnableActionCenter)
{ {
foreach (var activator in Context.Activators) activator.Stop();
{
activator.Stop();
}
} }
} }

View file

@ -216,6 +216,10 @@
<Project>{89bc24dd-ff31-496e-9816-a160b686a3d4}</Project> <Project>{89bc24dd-ff31-496e-9816-a160b686a3d4}</Project>
<Name>SafeExamBrowser.UserInterface.Mobile</Name> <Name>SafeExamBrowser.UserInterface.Mobile</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Shared\SafeExamBrowser.UserInterface.Shared.csproj">
<Project>{38525928-87ba-4f8c-8010-4eb97bfaae13}</Project>
<Name>SafeExamBrowser.UserInterface.Shared</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project> <Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name> <Name>SafeExamBrowser.WindowsApi.Contracts</Name>

View file

@ -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; var block = false;

View file

@ -71,10 +71,14 @@
<Compile Include="Shell\Events\QuitButtonClickedEventHandler.cs" /> <Compile Include="Shell\Events\QuitButtonClickedEventHandler.cs" />
<Compile Include="Shell\IActionCenter.cs" /> <Compile Include="Shell\IActionCenter.cs" />
<Compile Include="Shell\IActionCenterActivator.cs" /> <Compile Include="Shell\IActionCenterActivator.cs" />
<Compile Include="Shell\IActivator.cs" />
<Compile Include="Shell\IApplicationControl.cs" /> <Compile Include="Shell\IApplicationControl.cs" />
<Compile Include="Shell\INotificationControl.cs" /> <Compile Include="Shell\INotificationControl.cs" />
<Compile Include="Shell\ISystemControl.cs" /> <Compile Include="Shell\ISystemControl.cs" />
<Compile Include="Shell\ITaskbar.cs" /> <Compile Include="Shell\ITaskbar.cs" />
<Compile Include="Shell\ITaskView.cs" />
<Compile Include="Shell\ITaskViewActivator.cs" />
<Compile Include="Shell\ITerminationActivator.cs" />
<Compile Include="Shell\Location.cs" /> <Compile Include="Shell\Location.cs" />
<Compile Include="Windows\Data\LockScreenOption.cs" /> <Compile Include="Windows\Data\LockScreenOption.cs" />
<Compile Include="Windows\Data\LockScreenResult.cs" /> <Compile Include="Windows\Data\LockScreenResult.cs" />

View file

@ -12,7 +12,7 @@ using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Shell namespace SafeExamBrowser.UserInterface.Contracts.Shell
{ {
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public interface IActionCenter public interface IActionCenter
{ {

View file

@ -11,9 +11,9 @@ using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Shell namespace SafeExamBrowser.UserInterface.Contracts.Shell
{ {
/// <summary> /// <summary>
/// A module which can be used to control the visibility of the <see cref="IActionCenter"/>. /// A module which can be used to control the <see cref="IActionCenter"/>.
/// </summary> /// </summary>
public interface IActionCenterActivator public interface IActionCenterActivator : IActivator
{ {
/// <summary> /// <summary>
/// Fired when the action center should be made visible. /// 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. /// Fired when the action center visibility should be toggled.
/// </summary> /// </summary>
event ActivatorEventHandler Toggled; event ActivatorEventHandler Toggled;
/// <summary>
/// Temporarily stops processing all user input.
/// </summary>
void Pause();
/// <summary>
/// Resumes processing user input.
/// </summary>
void Resume();
/// <summary>
/// Starts monitoring user input events.
/// </summary>
void Start();
/// <summary>
/// Stops monitoring user input events.
/// </summary>
void Stop();
} }
} }

View file

@ -6,20 +6,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.WindowsApi.Contracts.Events; namespace SafeExamBrowser.UserInterface.Contracts.Shell
namespace SafeExamBrowser.WindowsApi.Contracts
{ {
/// <summary> /// <summary>
/// A module which observes user input and indicates when the user would like to terminate the application. /// Defines an activator for a shell component.
/// </summary> /// </summary>
public interface ITerminationActivator public interface IActivator
{ {
/// <summary>
/// Fired when a termination request has been detected.
/// </summary>
event TerminationActivatorEventHandler Activated;
/// <summary> /// <summary>
/// Temporarily stops processing all user input. /// Temporarily stops processing all user input.
/// </summary> /// </summary>

View file

@ -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
{
/// <summary>
/// The task view provides an overview of all currently running application instances.
/// </summary>
public interface ITaskView
{
/// <summary>
/// Adds the given application to the task view.
/// </summary>
void Add(IApplication application);
/// <summary>
/// Registers the specified activator for the task view.
/// </summary>
void Register(ITaskViewActivator activator);
}
}

View file

@ -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
{
/// <summary>
/// A module which can be used to control the <see cref="ITaskView"/>.
/// </summary>
public interface ITaskViewActivator : IActivator
{
/// <summary>
/// Fired when the next application instance should be selected.
/// </summary>
event ActivatorEventHandler Next;
/// <summary>
/// Fired when the previous application instance should be selected.
/// </summary>
event ActivatorEventHandler Previous;
}
}

View file

@ -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
{
/// <summary>
/// A module which observes user input and indicates when the user would like to terminate SEB.
/// </summary>
public interface ITerminationActivator : IActivator
{
/// <summary>
/// Fired when a termination request has been detected.
/// </summary>
event ActivatorEventHandler Activated;
}
}

View file

@ -158,6 +158,9 @@
<Compile Include="SplashScreen.xaml.cs"> <Compile Include="SplashScreen.xaml.cs">
<DependentUpon>SplashScreen.xaml</DependentUpon> <DependentUpon>SplashScreen.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="TaskView.xaml.cs">
<DependentUpon>TaskView.xaml</DependentUpon>
</Compile>
<Compile Include="UserInterfaceFactory.cs" /> <Compile Include="UserInterfaceFactory.cs" />
<Compile Include="ViewModels\DateTimeViewModel.cs" /> <Compile Include="ViewModels\DateTimeViewModel.cs" />
<Compile Include="ViewModels\LogViewModel.cs" /> <Compile Include="ViewModels\LogViewModel.cs" />
@ -315,6 +318,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="TaskView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Templates\ScrollViewers.xaml"> <Page Include="Templates\ScrollViewers.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View file

@ -0,0 +1,12 @@
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
mc:Ignorable="d" AllowsTransparency="True" Background="#74000000" BorderBrush="DodgerBlue" BorderThickness="1" Title="TaskView"
Height="450" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid>
</Grid>
</Window>

View file

@ -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)
{
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -63,6 +63,12 @@
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Activators\ActionCenterKeyboardActivator.cs" />
<Compile Include="Activators\ActionCenterTouchActivator.cs" />
<Compile Include="Activators\KeyboardActivator.cs" />
<Compile Include="Activators\TaskViewKeyboardActivator.cs" />
<Compile Include="Activators\TerminationActivator.cs" />
<Compile Include="Activators\TouchActivator.cs" />
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
@ -75,6 +81,18 @@
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project> <Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
<Name>SafeExamBrowser.Core.Contracts</Name> <Name>SafeExamBrowser.Core.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.WindowsApi.Contracts.Events namespace SafeExamBrowser.WindowsApi.Contracts.Events
{ {
/// <summary> /// <summary>
/// The mouse button states which can be detected a mouse hook. /// The mouse button states which can be detected by a mouse hook.
/// </summary> /// </summary>
public enum MouseButtonState public enum MouseButtonState
{ {

View file

@ -11,5 +11,5 @@ namespace SafeExamBrowser.WindowsApi.Contracts.Events
/// <summary> /// <summary>
/// The callback for a mouse hook. Return <c>true</c> to consume (i.e. block) the user input, otherwise <c>false</c>. /// The callback for a mouse hook. Return <c>true</c> to consume (i.e. block) the user input, otherwise <c>false</c>.
/// </summary> /// </summary>
public delegate bool MouseHookCallback(MouseButton button, MouseButtonState state); public delegate bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info);
} }

View file

@ -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
{
/// <summary>
/// The mouse information which can be detected by a mouse hook.
/// </summary>
public class MouseInformation
{
/// <summary>
/// Indicates whether the mouse event originates from a touch input by the user.
/// </summary>
public bool IsTouch { get; set; }
/// <summary>
/// The X coordinate of the current mouse position.
/// </summary>
public int X { get; set; }
/// <summary>
/// The Y coordinate of the current mouse position.
/// </summary>
public int Y { get; set; }
}
}

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.WindowsApi.Contracts namespace SafeExamBrowser.WindowsApi.Contracts
{ {
/// <summary> /// <summary>
/// Defines rectangular bounds, e.g. used for display-related operations (see <see cref="Monitoring.IDisplayMonitor"/>). /// Defines rectangular bounds, e.g. used for display-related operations.
/// </summary> /// </summary>
public interface IBounds public interface IBounds
{ {

View file

@ -49,6 +49,11 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </exception> /// </exception>
void EmptyClipboard(); void EmptyClipboard();
/// <summary>
/// Retrieves the current position of the mouse cursor.
/// </summary>
(int x, int y) GetCursorPosition();
/// <summary> /// <summary>
/// Retrieves a collection of handles to all currently open (i.e. visible) windows. /// Retrieves a collection of handles to all currently open (i.e. visible) windows.
/// </summary> /// </summary>

View file

@ -60,6 +60,7 @@
<Compile Include="Events\MouseButton.cs" /> <Compile Include="Events\MouseButton.cs" />
<Compile Include="Events\MouseButtonState.cs" /> <Compile Include="Events\MouseButtonState.cs" />
<Compile Include="Events\MouseHookCallback.cs" /> <Compile Include="Events\MouseHookCallback.cs" />
<Compile Include="Events\MouseInformation.cs" />
<Compile Include="Events\ProcessTerminatedEventHandler.cs" /> <Compile Include="Events\ProcessTerminatedEventHandler.cs" />
<Compile Include="Events\SystemEventCallback.cs" /> <Compile Include="Events\SystemEventCallback.cs" />
<Compile Include="Events\TerminationActivatorEventHandler.cs" /> <Compile Include="Events\TerminationActivatorEventHandler.cs" />
@ -70,7 +71,6 @@
<Compile Include="INativeMethods.cs" /> <Compile Include="INativeMethods.cs" />
<Compile Include="IProcess.cs" /> <Compile Include="IProcess.cs" />
<Compile Include="IProcessFactory.cs" /> <Compile Include="IProcessFactory.cs" />
<Compile Include="ITerminationActivator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View file

@ -54,8 +54,9 @@ namespace SafeExamBrowser.WindowsApi.Hooks
var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
var button = GetButton(wParam.ToInt32()); var button = GetButton(wParam.ToInt32());
var state = GetState(wParam.ToInt32()); var state = GetState(wParam.ToInt32());
var info = GetInfo(mouseData);
if (callback(button, state)) if (callback(button, state, info))
{ {
return (IntPtr) 1; 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) private MouseButtonState GetState(int wParam)
{ {
switch (wParam) switch (wParam)

View file

@ -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 <c>CallbackOnCollectedDelegate</c> 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;
}
}
}

View file

@ -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<IntPtr> GetOpenWindows() public IEnumerable<IntPtr> GetOpenWindows()
{ {
var windows = new List<IntPtr>(); var windows = new List<IntPtr>();

View file

@ -66,14 +66,11 @@
<Compile Include="Desktop.cs" /> <Compile Include="Desktop.cs" />
<Compile Include="DesktopFactory.cs" /> <Compile Include="DesktopFactory.cs" />
<Compile Include="ExplorerShell.cs" /> <Compile Include="ExplorerShell.cs" />
<Compile Include="KeyboardActivator.cs" />
<Compile Include="Hooks\MouseHook.cs" /> <Compile Include="Hooks\MouseHook.cs" />
<Compile Include="Hooks\SystemHook.cs" /> <Compile Include="Hooks\SystemHook.cs" />
<Compile Include="TerminationActivator.cs" />
<Compile Include="Process.cs" /> <Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" /> <Compile Include="ProcessFactory.cs" />
<Compile Include="Constants\AccessMask.cs" /> <Compile Include="Constants\AccessMask.cs" />
<Compile Include="TouchActivator.cs" />
<Compile Include="Types\Bounds.cs" /> <Compile Include="Types\Bounds.cs" />
<Compile Include="Types\EXECUTION_STATE.cs" /> <Compile Include="Types\EXECUTION_STATE.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCT.cs" /> <Compile Include="Types\KBDLLHOOKSTRUCT.cs" />
@ -100,10 +97,6 @@
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project> <Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name> <Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project> <Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name> <Name>SafeExamBrowser.WindowsApi.Contracts</Name>

View file

@ -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 <c>CallbackOnCollectedDelegate</c> 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;
}
}
}

View file

@ -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 <c>CallbackOnCollectedDelegate</c> 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;
}
}
}