Implemented basic display / sleep mode handling and added logging to taskbar.

This commit is contained in:
dbuechel 2017-08-11 08:28:17 +02:00
parent 49540af39d
commit 939bc7f79a
28 changed files with 436 additions and 174 deletions

View file

@ -10,7 +10,7 @@ using System;
using System.IO;
using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Configuration.Settings
namespace SafeExamBrowser.Configuration
{
public class BrowserSettings : IBrowserSettings
{

View file

@ -8,7 +8,7 @@
using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Configuration.Settings
namespace SafeExamBrowser.Configuration
{
public class KeyboardSettings : IKeyboardSettings
{

View file

@ -8,12 +8,12 @@
using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Configuration.Settings
namespace SafeExamBrowser.Configuration
{
public class MouseSettings : IMouseSettings
{
public bool AllowMiddleButton => false;
public bool AllowRightButton => false;
public bool AllowRightButton => true;
}
}

View file

@ -60,12 +60,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Bounds.cs" />
<Compile Include="Settings\BrowserSettings.cs" />
<Compile Include="Settings\KeyboardSettings.cs" />
<Compile Include="Settings\MouseSettings.cs" />
<Compile Include="Settings\SettingsImpl.cs" />
<Compile Include="WorkingArea.cs" />
<Compile Include="BrowserSettings.cs" />
<Compile Include="KeyboardSettings.cs" />
<Compile Include="MouseSettings.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -9,7 +9,6 @@
using System;
using System.IO;
using System.Reflection;
using SafeExamBrowser.Configuration.Settings;
using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Configuration
@ -17,11 +16,11 @@ namespace SafeExamBrowser.Configuration
/// <remarks>
/// TODO: Replace with proper implementation once configuration aspects are clear...
/// </remarks>
public class SettingsImpl : ISettings
public class Settings : ISettings
{
private static readonly string LogFileDate = DateTime.Now.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
public SettingsImpl()
public Settings()
{
Browser = new BrowserSettings(this);
Keyboard = new KeyboardSettings();

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Windows.Forms;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Configuration
{
public class WorkingArea : IWorkingArea
{
private ILogger logger;
private INativeMethods nativeMethods;
private IBounds originalWorkingArea;
public WorkingArea(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
}
public void InitializeFor(ITaskbar taskbar)
{
originalWorkingArea = nativeMethods.GetWorkingArea();
LogWorkingArea("Saved original working area", originalWorkingArea);
var area = new Bounds
{
Left = 0,
Top = 0,
Right = Screen.PrimaryScreen.Bounds.Width,
Bottom = Screen.PrimaryScreen.Bounds.Height - taskbar.GetAbsoluteHeight()
};
LogWorkingArea("Trying to set new working area", area);
nativeMethods.SetWorkingArea(area);
LogWorkingArea("Working area is now set to", nativeMethods.GetWorkingArea());
}
public void Reset()
{
if (originalWorkingArea != null)
{
nativeMethods.SetWorkingArea(originalWorkingArea);
LogWorkingArea("Restored original working area", originalWorkingArea);
}
}
private void LogWorkingArea(string message, IBounds area)
{
logger.Info($"{message}: Left = {area.Left}, Top = {area.Top}, Right = {area.Right}, Bottom = {area.Bottom}.");
}
}
}

View file

@ -1,25 +0,0 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Contracts.Configuration
{
public interface IWorkingArea
{
/// <summary>
/// Sets the Windows working area to accommodate to the taskbar's dimensions.
/// </summary>
void InitializeFor(ITaskbar taskbar);
/// <summary>
/// Resets the Windows working area to its previous (initial) state.
/// </summary>
void Reset();
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Monitoring
{
public delegate void DisplayChangedEventHandler();
public interface IDisplayMonitor
{
/// <summary>
/// Event fired when the primary display or its settings have changed.
/// </summary>
event DisplayChangedEventHandler DisplayChanged;
/// <summary>
/// Sets the desktop working area to accommodate to the taskbar's height, removes the configured wallpaper (if possible) and
/// prevents the computer from entering sleep mode or turning its display(s) off.
/// </summary>
void InitializePrimaryDisplay(int taskbarHeight);
/// <summary>
/// Resets the desktop working area and wallpaper to their previous (initial) state.
/// </summary>
void ResetPrimaryDisplay();
/// <summary>
/// Starts monitoring for display changes, i.e. display changes will trigger the <c>DisplaySettingsChanged</c> event.
/// </summary>
void StartMonitoringDisplayChanges();
/// <summary>
/// Stops monitoring for display changes.
/// </summary>
void StopMonitoringDisplayChanges();
}
}

View file

@ -72,7 +72,6 @@
<Compile Include="Configuration\Settings\ISettings.cs" />
<Compile Include="Behaviour\IShutdownController.cs" />
<Compile Include="Behaviour\IStartupController.cs" />
<Compile Include="Configuration\IWorkingArea.cs" />
<Compile Include="I18n\IText.cs" />
<Compile Include="I18n\TextKey.cs" />
<Compile Include="Logging\ILogContent.cs" />
@ -83,6 +82,7 @@
<Compile Include="Logging\ILogText.cs" />
<Compile Include="Logging\IThreadInfo.cs" />
<Compile Include="Logging\LogLevel.cs" />
<Compile Include="Monitoring\IDisplayMonitor.cs" />
<Compile Include="Monitoring\IKeyboardInterceptor.cs" />
<Compile Include="Monitoring\IMouseInterceptor.cs" />
<Compile Include="Monitoring\IProcessMonitor.cs" />

View file

@ -38,6 +38,11 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// </exception>
void DeregisterSystemEvent(IntPtr handle);
/// <summary>
/// Prevents Windows from entering sleep mode and keeps all displays powered on.
/// </summary>
void DisableSleep();
/// <summary>
/// Empties the clipboard.
/// </summary>
@ -72,8 +77,15 @@ namespace SafeExamBrowser.Contracts.WindowsApi
uint GetShellProcessId();
/// <summary>
/// Retrieves the title of the specified window, or an empty string, if the
/// given window does not have a title.
/// Retrieves the path of the currently configured wallpaper image, or an empty string, if there is no wallpaper set.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the wallpaper path could not be retrieved.
/// </exception>
string GetWallpaperPath();
/// <summary>
/// Retrieves the title of the specified window, or an empty string, if the given window does not have a title.
/// </summary>
string GetWindowTitle(IntPtr window);
@ -125,6 +137,14 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// </summary>
IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback);
/// <summary>
/// Removes the currently configured desktop wallpaper.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the wallpaper could not be removed.
/// </exception>
void RemoveWallpaper();
/// <summary>
/// Restores the specified window to its original size and position.
/// </summary>
@ -135,6 +155,14 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// </summary>
void SendCloseMessageTo(IntPtr window);
/// <summary>
/// Sets the wallpaper to the image located at the specified file path.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the wallpaper could not be set.
/// </exception>
void SetWallpaper(string filePath);
/// <summary>
/// Sets the working area of the primary screen according to the given dimensions.
/// </summary>

View file

@ -10,7 +10,6 @@ using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
@ -21,33 +20,52 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
[TestClass]
public class RuntimeControllerTests
{
private Mock<IDisplayMonitor> displayMonitorMock;
private Mock<ILogger> loggerMock;
private Mock<IProcessMonitor> processMonitorMock;
private Mock<ITaskbar> taskbarMock;
private Mock<IWindowMonitor> windowMonitorMock;
private Mock<IWorkingArea> workingAreaMock;
private IRuntimeController sut;
[TestInitialize]
public void Initialize()
{
displayMonitorMock = new Mock<IDisplayMonitor>();
loggerMock = new Mock<ILogger>();
processMonitorMock = new Mock<IProcessMonitor>();
taskbarMock = new Mock<ITaskbar>();
windowMonitorMock= new Mock<IWindowMonitor>();
workingAreaMock = new Mock<IWorkingArea>();
sut = new RuntimeController(
displayMonitorMock.Object,
loggerMock.Object,
processMonitorMock.Object,
taskbarMock.Object,
windowMonitorMock.Object,
workingAreaMock.Object);
windowMonitorMock.Object);
sut.Start();
}
[TestMethod]
public void MustHandleDisplayChangeCorrectly()
{
var order = 0;
var workingArea = 0;
var taskbar = 0;
displayMonitorMock.Setup(w => w.InitializePrimaryDisplay(taskbarMock.Object.GetAbsoluteHeight())).Callback(() => workingArea = ++order);
taskbarMock.Setup(t => t.InitializeBounds()).Callback(() => taskbar = ++order);
displayMonitorMock.Raise(d => d.DisplayChanged += null);
displayMonitorMock.Verify(w => w.InitializePrimaryDisplay(taskbarMock.Object.GetAbsoluteHeight()), Times.Once);
taskbarMock.Verify(t => t.InitializeBounds(), Times.Once);
Assert.IsTrue(workingArea == 1);
Assert.IsTrue(taskbar == 2);
}
[TestMethod]
public void MustHandleExplorerStartCorrectly()
{
@ -57,13 +75,13 @@ namespace SafeExamBrowser.Core.UnitTests.Behaviour
var taskbar = 0;
processMonitorMock.Setup(p => p.CloseExplorerShell()).Callback(() => processManager = ++order);
workingAreaMock.Setup(w => w.InitializeFor(taskbarMock.Object)).Callback(() => workingArea = ++order);
displayMonitorMock.Setup(w => w.InitializePrimaryDisplay(taskbarMock.Object.GetAbsoluteHeight())).Callback(() => workingArea = ++order);
taskbarMock.Setup(t => t.InitializeBounds()).Callback(() => taskbar = ++order);
processMonitorMock.Raise(p => p.ExplorerStarted += null);
processMonitorMock.Verify(p => p.CloseExplorerShell(), Times.Once);
workingAreaMock.Verify(w => w.InitializeFor(taskbarMock.Object), Times.Once);
displayMonitorMock.Verify(w => w.InitializePrimaryDisplay(taskbarMock.Object.GetAbsoluteHeight()), Times.Once);
taskbarMock.Verify(t => t.InitializeBounds(), Times.Once);
Assert.IsTrue(processManager == 1);

View file

@ -7,26 +7,26 @@
*/
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
public class WorkingAreaOperation : IOperation
public class DisplayMonitorOperation : IOperation
{
private IDisplayMonitor displayMonitor;
private ILogger logger;
private ITaskbar taskbar;
private IWorkingArea workingArea;
public ISplashScreen SplashScreen { private get; set; }
public WorkingAreaOperation(ILogger logger, ITaskbar taskbar, IWorkingArea workingArea)
public DisplayMonitorOperation(IDisplayMonitor displayMonitor, ILogger logger, ITaskbar taskbar)
{
this.displayMonitor = displayMonitor;
this.logger = logger;
this.taskbar = taskbar;
this.workingArea = workingArea;
}
public void Perform()
@ -34,10 +34,8 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
logger.Info("Initializing working area...");
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeWorkingArea);
// TODO
// - Emptying clipboard
workingArea.InitializeFor(taskbar);
displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight());
displayMonitor.StartMonitoringDisplayChanges();
}
public void Revert()
@ -45,10 +43,8 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
logger.Info("Restoring working area...");
SplashScreen.UpdateText(TextKey.SplashScreen_RestoreWorkingArea);
// TODO
// - Emptying clipboard
workingArea.Reset();
displayMonitor.StopMonitoringDisplayChanges();
displayMonitor.ResetPrimaryDisplay();
}
}
}

View file

@ -8,7 +8,6 @@
using System;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
@ -17,28 +16,29 @@ namespace SafeExamBrowser.Core.Behaviour
{
public class RuntimeController : IRuntimeController
{
private IDisplayMonitor displayMonitor;
private ILogger logger;
private IProcessMonitor processMonitor;
private ITaskbar taskbar;
private IWindowMonitor windowMonitor;
private IWorkingArea workingArea;
public RuntimeController(
IDisplayMonitor displayMonitor,
ILogger logger,
IProcessMonitor processMonitor,
ITaskbar taskbar,
IWindowMonitor windowMonitor,
IWorkingArea workingArea)
IWindowMonitor windowMonitor)
{
this.displayMonitor = displayMonitor;
this.logger = logger;
this.processMonitor = processMonitor;
this.taskbar = taskbar;
this.windowMonitor = windowMonitor;
this.workingArea = workingArea;
}
public void Start()
{
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
windowMonitor.WindowChanged += WindowMonitor_WindowChanged;
}
@ -49,12 +49,21 @@ namespace SafeExamBrowser.Core.Behaviour
windowMonitor.WindowChanged -= WindowMonitor_WindowChanged;
}
private void DisplayMonitor_DisplaySettingsChanged()
{
logger.Info("Reinitializing working area...");
displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight());
logger.Info("Reinitializing taskbar bounds...");
taskbar.InitializeBounds();
logger.Info("Desktop successfully restored.");
}
private void ProcessMonitor_ExplorerStarted()
{
logger.Info("Trying to shut down explorer...");
processMonitor.CloseExplorerShell();
logger.Info("Reinitializing working area...");
workingArea.InitializeFor(taskbar);
displayMonitor.InitializePrimaryDisplay(taskbar.GetAbsoluteHeight());
logger.Info("Reinitializing taskbar bounds...");
taskbar.InitializeBounds();
logger.Info("Desktop successfully restored.");

View file

@ -67,7 +67,7 @@
<Compile Include="Behaviour\Operations\ProcessMonitorOperation.cs" />
<Compile Include="Behaviour\Operations\TaskbarOperation.cs" />
<Compile Include="Behaviour\Operations\WindowMonitorOperation.cs" />
<Compile Include="Behaviour\Operations\WorkingAreaOperation.cs" />
<Compile Include="Behaviour\Operations\DisplayMonitorOperation.cs" />
<Compile Include="Behaviour\ShutdownController.cs" />
<Compile Include="Behaviour\StartupController.cs" />
<Compile Include="Logging\DefaultLogFormatter.cs" />

View file

@ -8,7 +8,7 @@
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Configuration
namespace SafeExamBrowser.Monitoring.Display
{
internal class Bounds : IBounds
{

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Windows.Forms;
using Microsoft.Win32;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Monitoring.Display
{
public class DisplayMonitor : IDisplayMonitor
{
private IBounds originalWorkingArea;
private ILogger logger;
private INativeMethods nativeMethods;
private string wallpaper;
public event DisplayChangedEventHandler DisplayChanged;
public DisplayMonitor(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
}
public void InitializePrimaryDisplay(int taskbarHeight)
{
InitializeWorkingArea(taskbarHeight);
InitializeWallpaper();
PreventSleepMode();
}
public void ResetPrimaryDisplay()
{
ResetWorkingArea();
ResetWallpaper();
}
public void StartMonitoringDisplayChanges()
{
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
logger.Info("Started monitoring display changes.");
}
public void StopMonitoringDisplayChanges()
{
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
logger.Info("Stopped monitoring display changes.");
}
private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
logger.Info("Display change detected!");
DisplayChanged?.Invoke();
}
private void InitializeWorkingArea(int taskbarHeight)
{
var identifier = GetIdentifierForPrimaryDisplay();
originalWorkingArea = nativeMethods.GetWorkingArea();
LogWorkingArea($"Saved original working area for {identifier}", originalWorkingArea);
var area = new Bounds
{
Left = 0,
Top = 0,
Right = Screen.PrimaryScreen.Bounds.Width,
Bottom = Screen.PrimaryScreen.Bounds.Height - taskbarHeight
};
LogWorkingArea($"Trying to set new working area for {identifier}", area);
nativeMethods.SetWorkingArea(area);
LogWorkingArea($"Working area of {identifier} is now set to", nativeMethods.GetWorkingArea());
}
private void InitializeWallpaper()
{
var path = nativeMethods.GetWallpaperPath();
if (!String.IsNullOrEmpty(path))
{
wallpaper = path;
logger.Info($"Saved wallpaper image: {wallpaper}.");
nativeMethods.RemoveWallpaper();
logger.Info("Removed current wallpaper.");
}
}
private void PreventSleepMode()
{
nativeMethods.DisableSleep();
logger.Info("Disabled sleep mode and display timeout.");
}
private void ResetWorkingArea()
{
var identifier = GetIdentifierForPrimaryDisplay();
if (originalWorkingArea != null)
{
nativeMethods.SetWorkingArea(originalWorkingArea);
LogWorkingArea($"Restored original working area for {identifier}", originalWorkingArea);
}
else
{
logger.Warn($"Could not restore original working area for {identifier}!");
}
}
private void ResetWallpaper()
{
if (!String.IsNullOrEmpty(wallpaper))
{
nativeMethods.SetWallpaper(wallpaper);
logger.Info($"Restored wallpaper image: {wallpaper}.");
}
}
private string GetIdentifierForPrimaryDisplay()
{
var display = Screen.PrimaryScreen.DeviceName?.Replace(@"\\.\", string.Empty);
return $"{display} ({Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height})";
}
private void LogWorkingArea(string message, IBounds area)
{
logger.Info($"{message}: Left = {area.Left}, Top = {area.Top}, Right = {area.Right}, Bottom = {area.Bottom}.");
}
}
}

View file

@ -50,7 +50,9 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -60,10 +62,13 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Display\Bounds.cs" />
<Compile Include="Display\DisplayMonitor.cs" />
<Compile Include="Keyboard\KeyboardInterceptor.cs" />
<Compile Include="Mouse\MouseInterceptor.cs" />
<Compile Include="Processes\ProcessMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Windows\Window.cs" />
<Compile Include="Windows\WindowMonitor.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Monitoring.Windows
{
internal struct Window
{
internal IntPtr Handle { get; set; }
internal string Title { get; set; }
}
}

View file

@ -117,11 +117,5 @@ namespace SafeExamBrowser.Monitoring.Windows
{
WindowChanged?.Invoke(window);
}
private struct Window
{
internal IntPtr Handle { get; set; }
internal string Title { get; set; }
}
}
}

View file

@ -10,14 +10,19 @@ using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.UserInterface
{
public partial class Taskbar : Window, ITaskbar
{
public Taskbar()
private ILogger logger;
public Taskbar(ILogger logger)
{
this.logger = logger;
InitializeComponent();
Loaded += (o, args) => InitializeBounds();
@ -41,28 +46,13 @@ namespace SafeExamBrowser.UserInterface
public int GetAbsoluteHeight()
{
// WPF works with device-independent pixels. The following code is required
// to get the real height of the taskbar (in absolute, device-specific pixels).
// Source: https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels
return Dispatcher.Invoke(() =>
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(this);
var height = (int) TransformToPhysical(Width, Height).Y;
if (source != null)
{
transformToDevice = source.CompositionTarget.TransformToDevice;
}
else
{
using (var newSource = new HwndSource(new HwndSourceParameters()))
{
transformToDevice = newSource.CompositionTarget.TransformToDevice;
}
}
logger.Info($"Calculated absolute taskbar height is {height}px.");
return (int)transformToDevice.Transform((Vector)new Size(Width, Height)).Y;
return height;
});
}
@ -73,6 +63,11 @@ namespace SafeExamBrowser.UserInterface
Width = SystemParameters.WorkArea.Right;
Left = SystemParameters.WorkArea.Right - Width;
Top = SystemParameters.WorkArea.Bottom;
var position = TransformToPhysical(Left, Top);
var size = TransformToPhysical(Width, Height);
logger.Info($"Set taskbar bounds to {Width}x{Height} at ({Left}/{Top}), in physical pixels: {size.X}x{size.Y} at ({position.X}/{position.Y}).");
});
}
@ -106,5 +101,29 @@ namespace SafeExamBrowser.UserInterface
}
}
}
private Vector TransformToPhysical(double x, double y)
{
// WPF works with device-independent pixels. The following code is required
// to transform those values to their absolute, device-specific pixel value.
// Source: https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels
Matrix transformToDevice;
var source = PresentationSource.FromVisual(this);
if (source != null)
{
transformToDevice = source.CompositionTarget.TransformToDevice;
}
else
{
using (var newSource = new HwndSource(new HwndSourceParameters()))
{
transformToDevice = newSource.CompositionTarget.TransformToDevice;
}
}
return transformToDevice.Transform(new Vector(x, y));
}
}
}

View file

@ -14,12 +14,11 @@ namespace SafeExamBrowser.WindowsApi.Constants
internal enum SPI : uint
{
/// <summary>
/// Sets the size of the work area. The work area is the portion of the screen not obscured by the system taskbar
/// or by application desktop toolbars. The pvParam parameter is a pointer to a RECT structure that specifies the
/// new work area rectangle, expressed in virtual screen coordinates. In a system with multiple display monitors,
/// the function sets the work area of the monitor that contains the specified rectangle.
/// Retrieves the full path of the bitmap file for the desktop wallpaper. The pvParam parameter must point to a buffer
/// that receives a null-terminated path string. Set the uiParam parameter to the size, in characters, of the pvParam buffer.
/// The returned string will not exceed MAX_PATH characters. If there is no desktop wallpaper, the returned string is empty.
/// </summary>
SETWORKAREA = 0x002F,
GETDESKWALLPAPER = 0x73,
/// <summary>
/// Retrieves the size of the work area on the primary display monitor. The work area is the portion of the screen
@ -27,6 +26,21 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// RECT structure that receives the coordinates of the work area, expressed in virtual screen coordinates. To get
/// the work area of a monitor other than the primary display monitor, call the GetMonitorInfo function.
/// </summary>
GETWORKAREA = 0x0030,
GETWORKAREA = 0x30,
/// <summary>
/// Sets the desktop wallpaper. The value of the pvParam parameter determines the new wallpaper. To specify a wallpaper bitmap,
/// set pvParam to point to a null-terminated string containing the name of a bitmap file. Setting pvParam to "" removes the
/// wallpaper. Setting pvParam to SETWALLPAPER_DEFAULT or null reverts to the default wallpaper.
/// </summary>
SETDESKWALLPAPER = 0x14,
/// <summary>
/// Sets the size of the work area. The work area is the portion of the screen not obscured by the system taskbar
/// or by application desktop toolbars. The pvParam parameter is a pointer to a RECT structure that specifies the
/// new work area rectangle, expressed in virtual screen coordinates. In a system with multiple display monitors,
/// the function sets the work area of the monitor that contains the specified rectangle.
/// </summary>
SETWORKAREA = 0x2F,
}
}

View file

@ -8,6 +8,7 @@
using System;
using System.Runtime.InteropServices;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
@ -16,7 +17,10 @@ namespace SafeExamBrowser.WindowsApi
/// </summary>
internal class Kernel32
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
}
}

View file

@ -94,6 +94,11 @@ namespace SafeExamBrowser.WindowsApi
EventDelegates.TryRemove(handle, out EventProc d);
}
public void DisableSleep()
{
Kernel32.SetThreadExecutionState(EXECUTION_STATE.CONTINUOUS | EXECUTION_STATE.DISPLAY_REQUIRED | EXECUTION_STATE.SYSTEM_REQUIRED);
}
public void EmptyClipboard()
{
var success = true;
@ -149,6 +154,22 @@ namespace SafeExamBrowser.WindowsApi
return processId;
}
public string GetWallpaperPath()
{
const int MAX_PATH = 260;
var buffer = new String('\0', MAX_PATH);
var success = User32.SystemParametersInfo(SPI.GETDESKWALLPAPER, buffer.Length, buffer, 0);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var path = buffer.Substring(0, buffer.IndexOf('\0'));
return path;
}
public string GetWindowTitle(IntPtr window)
{
var length = User32.GetWindowTextLength(window);
@ -256,6 +277,11 @@ namespace SafeExamBrowser.WindowsApi
return handle;
}
public void RemoveWallpaper()
{
SetWallpaper(string.Empty);
}
public void RestoreWindow(IntPtr window)
{
User32.ShowWindow(window, (int)ShowWindowCommand.Restore);
@ -266,6 +292,16 @@ namespace SafeExamBrowser.WindowsApi
User32.SendMessage(window, Constant.WM_SYSCOMMAND, (IntPtr) SystemCommand.CLOSE, IntPtr.Zero);
}
public void SetWallpaper(string filePath)
{
var success = User32.SystemParametersInfo(SPI.SETDESKWALLPAPER, 0, filePath, SPIF.UPDATEANDCHANGE);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
public void SetWorkingArea(IBounds bounds)
{
var workingArea = new RECT { Left = bounds.Left, Top = bounds.Top, Right = bounds.Right, Bottom = bounds.Bottom };

View file

@ -62,6 +62,7 @@
<Compile Include="Constants\HookType.cs" />
<Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Types\Bounds.cs" />
<Compile Include="Types\EXECUTION_STATE.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCT.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCTFlags.cs" />
<Compile Include="Constants\ShowWindowCommand.cs" />

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/kernel32/SetThreadExecutionState.html.
/// See https://msdn.microsoft.com/en-us/library/aa373208(v=vs.85).aspx.
/// </remarks>
[Flags]
public enum EXECUTION_STATE : uint
{
AWAYMODE_REQUIRED = 0x00000040,
CONTINUOUS = 0x80000000,
DISPLAY_REQUIRED = 0x00000002,
SYSTEM_REQUIRED = 0x00000001
}
}

View file

@ -11,9 +11,9 @@ using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <summary>
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx.
/// </summary>
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{

View file

@ -38,10 +38,10 @@ namespace SafeExamBrowser.WindowsApi
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool EnumWindows(EnumWindowsDelegate enumProc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", SetLastError = true)]
@ -79,6 +79,10 @@ namespace SafeExamBrowser.WindowsApi
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref RECT pvParam, SPIF fWinIni);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SystemParametersInfo(SPI uiAction, int uiParam, string pvParam, SPIF fWinIni);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);

View file

@ -21,6 +21,7 @@ using SafeExamBrowser.Core.Behaviour;
using SafeExamBrowser.Core.Behaviour.Operations;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
using SafeExamBrowser.Monitoring.Display;
using SafeExamBrowser.Monitoring.Keyboard;
using SafeExamBrowser.Monitoring.Mouse;
using SafeExamBrowser.Monitoring.Processes;
@ -34,6 +35,7 @@ namespace SafeExamBrowser
{
private IApplicationController browserController;
private IApplicationInfo browserInfo;
private IDisplayMonitor displayMonitor;
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
private ILogContentFormatter logFormatter;
@ -46,7 +48,6 @@ namespace SafeExamBrowser
private ITextResource textResource;
private IUserInterfaceFactory uiFactory;
private IWindowMonitor windowMonitor;
private IWorkingArea workingArea;
public IShutdownController ShutdownController { get; private set; }
public IStartupController StartupController { get; private set; }
@ -59,22 +60,22 @@ namespace SafeExamBrowser
logger = new Logger();
logFormatter = new DefaultLogFormatter();
nativeMethods = new NativeMethods();
settings = new SettingsImpl();
Taskbar = new Taskbar();
settings = new Settings();
textResource = new XmlTextResource();
uiFactory = new UserInterfaceFactory();
logger.Subscribe(new LogFileWriter(logFormatter, settings));
text = new Text(textResource);
Taskbar = new Taskbar(new ModuleLogger(logger, typeof(Taskbar)));
browserController = new BrowserApplicationController(settings, text, uiFactory);
displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
keyboardInterceptor = new KeyboardInterceptor(settings.Keyboard, new ModuleLogger(logger, typeof(KeyboardInterceptor)));
mouseInterceptor = new MouseInterceptor(new ModuleLogger(logger, typeof(MouseInterceptor)), settings.Mouse);
processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea)), nativeMethods);
runtimeController = new RuntimeController(new ModuleLogger(logger, typeof(RuntimeController)), processMonitor, Taskbar, windowMonitor, workingArea);
runtimeController = new RuntimeController(displayMonitor, new ModuleLogger(logger, typeof(RuntimeController)), processMonitor, Taskbar, windowMonitor);
ShutdownController = new ShutdownController(logger, settings, text, uiFactory);
StartupController = new StartupController(logger, settings, text, uiFactory);
@ -82,7 +83,7 @@ namespace SafeExamBrowser
StartupOperations.Enqueue(new KeyboardInterceptorOperation(keyboardInterceptor, logger, nativeMethods));
StartupOperations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
StartupOperations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
StartupOperations.Enqueue(new WorkingAreaOperation(logger, Taskbar, workingArea));
StartupOperations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar));
StartupOperations.Enqueue(new TaskbarOperation(logger, logFormatter, settings, Taskbar, text, uiFactory));
StartupOperations.Enqueue(new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory));
StartupOperations.Enqueue(new RuntimeControllerOperation(runtimeController, logger));