diff --git a/SafeExamBrowser.Configuration/Settings/BrowserSettings.cs b/SafeExamBrowser.Configuration/BrowserSettings.cs
similarity index 96%
rename from SafeExamBrowser.Configuration/Settings/BrowserSettings.cs
rename to SafeExamBrowser.Configuration/BrowserSettings.cs
index 7fe549b6..d8f88fd0 100644
--- a/SafeExamBrowser.Configuration/Settings/BrowserSettings.cs
+++ b/SafeExamBrowser.Configuration/BrowserSettings.cs
@@ -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
{
diff --git a/SafeExamBrowser.Configuration/Settings/KeyboardSettings.cs b/SafeExamBrowser.Configuration/KeyboardSettings.cs
similarity index 91%
rename from SafeExamBrowser.Configuration/Settings/KeyboardSettings.cs
rename to SafeExamBrowser.Configuration/KeyboardSettings.cs
index e468d724..6513c957 100644
--- a/SafeExamBrowser.Configuration/Settings/KeyboardSettings.cs
+++ b/SafeExamBrowser.Configuration/KeyboardSettings.cs
@@ -8,7 +8,7 @@
using SafeExamBrowser.Contracts.Configuration.Settings;
-namespace SafeExamBrowser.Configuration.Settings
+namespace SafeExamBrowser.Configuration
{
public class KeyboardSettings : IKeyboardSettings
{
diff --git a/SafeExamBrowser.Configuration/Settings/MouseSettings.cs b/SafeExamBrowser.Configuration/MouseSettings.cs
similarity index 83%
rename from SafeExamBrowser.Configuration/Settings/MouseSettings.cs
rename to SafeExamBrowser.Configuration/MouseSettings.cs
index f911db24..52fb0cde 100644
--- a/SafeExamBrowser.Configuration/Settings/MouseSettings.cs
+++ b/SafeExamBrowser.Configuration/MouseSettings.cs
@@ -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;
}
}
diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
index e263f7dc..2aaba73b 100644
--- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
+++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
@@ -60,12 +60,10 @@
-
-
-
-
-
-
+
+
+
+
diff --git a/SafeExamBrowser.Configuration/Settings/SettingsImpl.cs b/SafeExamBrowser.Configuration/Settings.cs
similarity index 95%
rename from SafeExamBrowser.Configuration/Settings/SettingsImpl.cs
rename to SafeExamBrowser.Configuration/Settings.cs
index f5ef343f..fdcb7c0e 100644
--- a/SafeExamBrowser.Configuration/Settings/SettingsImpl.cs
+++ b/SafeExamBrowser.Configuration/Settings.cs
@@ -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
///
/// TODO: Replace with proper implementation once configuration aspects are clear...
///
- 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();
diff --git a/SafeExamBrowser.Configuration/WorkingArea.cs b/SafeExamBrowser.Configuration/WorkingArea.cs
deleted file mode 100644
index 00f8d00c..00000000
--- a/SafeExamBrowser.Configuration/WorkingArea.cs
+++ /dev/null
@@ -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}.");
- }
- }
-}
diff --git a/SafeExamBrowser.Contracts/Configuration/IWorkingArea.cs b/SafeExamBrowser.Contracts/Configuration/IWorkingArea.cs
deleted file mode 100644
index 2b61ce86..00000000
--- a/SafeExamBrowser.Contracts/Configuration/IWorkingArea.cs
+++ /dev/null
@@ -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
- {
- ///
- /// Sets the Windows working area to accommodate to the taskbar's dimensions.
- ///
- void InitializeFor(ITaskbar taskbar);
-
- ///
- /// Resets the Windows working area to its previous (initial) state.
- ///
- void Reset();
- }
-}
diff --git a/SafeExamBrowser.Contracts/Monitoring/IDisplayMonitor.cs b/SafeExamBrowser.Contracts/Monitoring/IDisplayMonitor.cs
new file mode 100644
index 00000000..8bd33d26
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Monitoring/IDisplayMonitor.cs
@@ -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
+ {
+ ///
+ /// Event fired when the primary display or its settings have changed.
+ ///
+ event DisplayChangedEventHandler DisplayChanged;
+
+ ///
+ /// 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.
+ ///
+ void InitializePrimaryDisplay(int taskbarHeight);
+
+ ///
+ /// Resets the desktop working area and wallpaper to their previous (initial) state.
+ ///
+ void ResetPrimaryDisplay();
+
+ ///
+ /// Starts monitoring for display changes, i.e. display changes will trigger the DisplaySettingsChanged event.
+ ///
+ void StartMonitoringDisplayChanges();
+
+ ///
+ /// Stops monitoring for display changes.
+ ///
+ void StopMonitoringDisplayChanges();
+ }
+}
diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
index 2bbdd10e..29432ff7 100644
--- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
+++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
@@ -72,7 +72,6 @@
-
@@ -83,6 +82,7 @@
+
diff --git a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
index a3c88c6a..f25a07f1 100644
--- a/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
+++ b/SafeExamBrowser.Contracts/WindowsApi/INativeMethods.cs
@@ -38,6 +38,11 @@ namespace SafeExamBrowser.Contracts.WindowsApi
///
void DeregisterSystemEvent(IntPtr handle);
+ ///
+ /// Prevents Windows from entering sleep mode and keeps all displays powered on.
+ ///
+ void DisableSleep();
+
///
/// Empties the clipboard.
///
@@ -72,8 +77,15 @@ namespace SafeExamBrowser.Contracts.WindowsApi
uint GetShellProcessId();
///
- /// 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.
+ ///
+ ///
+ /// If the wallpaper path could not be retrieved.
+ ///
+ string GetWallpaperPath();
+
+ ///
+ /// Retrieves the title of the specified window, or an empty string, if the given window does not have a title.
///
string GetWindowTitle(IntPtr window);
@@ -125,6 +137,14 @@ namespace SafeExamBrowser.Contracts.WindowsApi
///
IntPtr RegisterSystemCaptureStartEvent(Action callback);
+ ///
+ /// Removes the currently configured desktop wallpaper.
+ ///
+ ///
+ /// If the wallpaper could not be removed.
+ ///
+ void RemoveWallpaper();
+
///
/// Restores the specified window to its original size and position.
///
@@ -135,6 +155,14 @@ namespace SafeExamBrowser.Contracts.WindowsApi
///
void SendCloseMessageTo(IntPtr window);
+ ///
+ /// Sets the wallpaper to the image located at the specified file path.
+ ///
+ ///
+ /// If the wallpaper could not be set.
+ ///
+ void SetWallpaper(string filePath);
+
///
/// Sets the working area of the primary screen according to the given dimensions.
///
diff --git a/SafeExamBrowser.Core.UnitTests/Behaviour/RuntimeControllerTests.cs b/SafeExamBrowser.Core.UnitTests/Behaviour/RuntimeControllerTests.cs
index 6aa694e3..60fdc12e 100644
--- a/SafeExamBrowser.Core.UnitTests/Behaviour/RuntimeControllerTests.cs
+++ b/SafeExamBrowser.Core.UnitTests/Behaviour/RuntimeControllerTests.cs
@@ -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 displayMonitorMock;
private Mock loggerMock;
private Mock processMonitorMock;
private Mock taskbarMock;
private Mock windowMonitorMock;
- private Mock workingAreaMock;
private IRuntimeController sut;
[TestInitialize]
public void Initialize()
{
+ displayMonitorMock = new Mock();
loggerMock = new Mock();
processMonitorMock = new Mock();
taskbarMock = new Mock();
windowMonitorMock= new Mock();
- workingAreaMock = new Mock();
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);
diff --git a/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/DisplayMonitorOperation.cs
similarity index 67%
rename from SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs
rename to SafeExamBrowser.Core/Behaviour/Operations/DisplayMonitorOperation.cs
index 851bf2e0..8084ced5 100644
--- a/SafeExamBrowser.Core/Behaviour/Operations/WorkingAreaOperation.cs
+++ b/SafeExamBrowser.Core/Behaviour/Operations/DisplayMonitorOperation.cs
@@ -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();
}
}
}
diff --git a/SafeExamBrowser.Core/Behaviour/RuntimeController.cs b/SafeExamBrowser.Core/Behaviour/RuntimeController.cs
index 6b2788ef..2b1abd78 100644
--- a/SafeExamBrowser.Core/Behaviour/RuntimeController.cs
+++ b/SafeExamBrowser.Core/Behaviour/RuntimeController.cs
@@ -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.");
diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj
index 1bb728e2..b564b361 100644
--- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj
+++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj
@@ -67,7 +67,7 @@
-
+
diff --git a/SafeExamBrowser.Configuration/Bounds.cs b/SafeExamBrowser.Monitoring/Display/Bounds.cs
similarity index 91%
rename from SafeExamBrowser.Configuration/Bounds.cs
rename to SafeExamBrowser.Monitoring/Display/Bounds.cs
index ce83b546..62237bae 100644
--- a/SafeExamBrowser.Configuration/Bounds.cs
+++ b/SafeExamBrowser.Monitoring/Display/Bounds.cs
@@ -8,7 +8,7 @@
using SafeExamBrowser.Contracts.WindowsApi;
-namespace SafeExamBrowser.Configuration
+namespace SafeExamBrowser.Monitoring.Display
{
internal class Bounds : IBounds
{
diff --git a/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs b/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs
new file mode 100644
index 00000000..8f88a11e
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs
@@ -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}.");
+ }
+ }
+}
diff --git a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
index 6f4187fe..881c82ae 100644
--- a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
+++ b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj
@@ -50,7 +50,9 @@
+
+
@@ -60,10 +62,13 @@
+
+
+
diff --git a/SafeExamBrowser.Monitoring/Windows/Window.cs b/SafeExamBrowser.Monitoring/Windows/Window.cs
new file mode 100644
index 00000000..27ea0cba
--- /dev/null
+++ b/SafeExamBrowser.Monitoring/Windows/Window.cs
@@ -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; }
+ }
+}
diff --git a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
index 84256968..73fe64f8 100644
--- a/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
+++ b/SafeExamBrowser.Monitoring/Windows/WindowMonitor.cs
@@ -117,11 +117,5 @@ namespace SafeExamBrowser.Monitoring.Windows
{
WindowChanged?.Invoke(window);
}
-
- private struct Window
- {
- internal IntPtr Handle { get; set; }
- internal string Title { get; set; }
- }
}
}
diff --git a/SafeExamBrowser.UserInterface/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface/Taskbar.xaml.cs
index 6a6af27f..e8d04c38 100644
--- a/SafeExamBrowser.UserInterface/Taskbar.xaml.cs
+++ b/SafeExamBrowser.UserInterface/Taskbar.xaml.cs
@@ -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));
+ }
}
}
diff --git a/SafeExamBrowser.WindowsApi/Constants/SPI.cs b/SafeExamBrowser.WindowsApi/Constants/SPI.cs
index a64bd0d1..319f865a 100644
--- a/SafeExamBrowser.WindowsApi/Constants/SPI.cs
+++ b/SafeExamBrowser.WindowsApi/Constants/SPI.cs
@@ -14,12 +14,11 @@ namespace SafeExamBrowser.WindowsApi.Constants
internal enum SPI : uint
{
///
- /// 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.
///
- SETWORKAREA = 0x002F,
+ GETDESKWALLPAPER = 0x73,
///
/// 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.
///
- GETWORKAREA = 0x0030,
+ GETWORKAREA = 0x30,
+
+ ///
+ /// 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.
+ ///
+ SETDESKWALLPAPER = 0x14,
+
+ ///
+ /// 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.
+ ///
+ SETWORKAREA = 0x2F,
}
}
diff --git a/SafeExamBrowser.WindowsApi/Kernel32.cs b/SafeExamBrowser.WindowsApi/Kernel32.cs
index 6fc2ec4c..1c092559 100644
--- a/SafeExamBrowser.WindowsApi/Kernel32.cs
+++ b/SafeExamBrowser.WindowsApi/Kernel32.cs
@@ -8,6 +8,7 @@
using System;
using System.Runtime.InteropServices;
+using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
@@ -16,7 +17,10 @@ namespace SafeExamBrowser.WindowsApi
///
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);
}
}
diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs
index fbaf5d19..a5b03fd2 100644
--- a/SafeExamBrowser.WindowsApi/NativeMethods.cs
+++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs
@@ -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 };
diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
index 900b809a..4bd2b3db 100644
--- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
+++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj
@@ -62,6 +62,7 @@
+
diff --git a/SafeExamBrowser.WindowsApi/Types/EXECUTION_STATE.cs b/SafeExamBrowser.WindowsApi/Types/EXECUTION_STATE.cs
new file mode 100644
index 00000000..4d1ea39c
--- /dev/null
+++ b/SafeExamBrowser.WindowsApi/Types/EXECUTION_STATE.cs
@@ -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
+{
+ ///
+ /// See http://www.pinvoke.net/default.aspx/kernel32/SetThreadExecutionState.html.
+ /// See https://msdn.microsoft.com/en-us/library/aa373208(v=vs.85).aspx.
+ ///
+ [Flags]
+ public enum EXECUTION_STATE : uint
+ {
+ AWAYMODE_REQUIRED = 0x00000040,
+ CONTINUOUS = 0x80000000,
+ DISPLAY_REQUIRED = 0x00000002,
+ SYSTEM_REQUIRED = 0x00000001
+ }
+}
diff --git a/SafeExamBrowser.WindowsApi/Types/RECT.cs b/SafeExamBrowser.WindowsApi/Types/RECT.cs
index c520e253..8e9ac824 100644
--- a/SafeExamBrowser.WindowsApi/Types/RECT.cs
+++ b/SafeExamBrowser.WindowsApi/Types/RECT.cs
@@ -11,9 +11,9 @@ using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.WindowsApi.Types
{
- ///
+ ///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx.
- ///
+ ///
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
diff --git a/SafeExamBrowser.WindowsApi/User32.cs b/SafeExamBrowser.WindowsApi/User32.cs
index f888d4f3..e31b8e41 100644
--- a/SafeExamBrowser.WindowsApi/User32.cs
+++ b/SafeExamBrowser.WindowsApi/User32.cs
@@ -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);
diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs
index 5403d6b9..4122cb8e 100644
--- a/SafeExamBrowser/CompositionRoot.cs
+++ b/SafeExamBrowser/CompositionRoot.cs
@@ -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));