From 181346b8101cf6950a595459f8c1be9675d31704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= Date: Thu, 11 Jan 2024 12:02:01 +0100 Subject: [PATCH] SEBWIN-821: Implemented always on configuration for display and system. --- .../DisplayMonitorOperationTests.cs | 10 +++----- .../Operations/ShellOperationTests.cs | 23 +++++++++++++++++ SafeExamBrowser.Client/CompositionRoot.cs | 1 + .../Operations/DisplayMonitorOperation.cs | 7 +++--- .../Operations/ShellOperation.cs | 16 ++++++++++++ .../ConfigurationData/DataValues.cs | 3 +++ .../Display/IDisplayMonitor.cs | 7 +----- .../Display/DisplayMonitor.cs | 12 +++------ SafeExamBrowser.Settings/AppSettings.cs | 7 ++++++ .../Monitoring/DisplaySettings.cs | 6 +++++ .../SafeExamBrowser.Settings.csproj | 1 + .../System/SystemSettings.cs | 25 +++++++++++++++++++ .../INativeMethods.cs | 11 ++++---- .../Desktops/DesktopMonitor.cs | 2 ++ SafeExamBrowser.WindowsApi/NativeMethods.cs | 22 ++++++++++------ 15 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 SafeExamBrowser.Settings/System/SystemSettings.cs diff --git a/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs index d3abae32..7e15f257 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/DisplayMonitorOperationTests.cs @@ -45,13 +45,11 @@ namespace SafeExamBrowser.Client.UnitTests.Operations context.Settings = new AppSettings(); context.Settings.Taskbar.EnableTaskbar = true; - displayMonitor.Setup(d => d.PreventSleepMode()).Callback(() => Assert.AreEqual(++order, 1)); - displayMonitor.Setup(d => d.InitializePrimaryDisplay(It.IsAny())).Callback(() => Assert.AreEqual(++order, 2)); - displayMonitor.Setup(d => d.StartMonitoringDisplayChanges()).Callback(() => Assert.AreEqual(++order, 3)); + displayMonitor.Setup(d => d.InitializePrimaryDisplay(It.IsAny())).Callback(() => Assert.AreEqual(++order, 1)); + displayMonitor.Setup(d => d.StartMonitoringDisplayChanges()).Callback(() => Assert.AreEqual(++order, 2)); sut.Perform(); - displayMonitor.Verify(d => d.PreventSleepMode(), Times.Once); displayMonitor.Verify(d => d.InitializePrimaryDisplay(It.IsAny()), Times.Once); displayMonitor.Verify(d => d.StartMonitoringDisplayChanges(), Times.Once); } @@ -59,7 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations [TestMethod] public void Perform_MustCorrectlyInitializeDisplayWithTaskbar() { - int height = 25; + var height = 25; context.Settings = new AppSettings(); context.Settings.Taskbar.EnableTaskbar = true; @@ -74,7 +72,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations [TestMethod] public void Perform_MustCorrectlyInitializeDisplayWithoutTaskbar() { - int height = 25; + var height = 25; context.Settings = new AppSettings(); context.Settings.Taskbar.EnableTaskbar = false; diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs index 8dcc23a0..0a5bd9f8 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs @@ -22,6 +22,7 @@ using SafeExamBrowser.SystemComponents.Contracts.Network; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Client.UnitTests.Operations { @@ -35,6 +36,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations private Mock aboutNotification; private Mock keyboard; private Mock logNotification; + private Mock nativeMethods; private Mock powerSupply; private Mock systemInfo; private Mock taskbar; @@ -55,6 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations aboutNotification = new Mock(); keyboard = new Mock(); logNotification = new Mock(); + nativeMethods = new Mock(); networkAdapter = new Mock(); powerSupply = new Mock(); systemInfo = new Mock(); @@ -77,6 +80,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations keyboard.Object, logger.Object, logNotification.Object, + nativeMethods.Object, networkAdapter.Object, powerSupply.Object, systemInfo.Object, @@ -185,6 +189,25 @@ namespace SafeExamBrowser.Client.UnitTests.Operations uiFactory.Verify(f => f.CreateApplicationControl(It.Is(a => a == application3.Object), Location.Taskbar), Times.Never); } + [TestMethod] + public void Perform_MustInitializeAlwaysOnState() + { + context.Settings.Display.AlwaysOn = true; + context.Settings.System.AlwaysOn = false; + + sut.Perform(); + + nativeMethods.Verify(n => n.SetAlwaysOnState(true, false), Times.Once); + nativeMethods.Reset(); + + context.Settings.Display.AlwaysOn = false; + context.Settings.System.AlwaysOn = true; + + sut.Perform(); + + nativeMethods.Verify(n => n.SetAlwaysOnState(false, true), Times.Once); + } + [TestMethod] public void Perform_MustInitializeClock() { diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 29c0ea09..23636d42 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -314,6 +314,7 @@ namespace SafeExamBrowser.Client keyboard, logger, logNotification, + nativeMethods, networkAdapter, powerSupply, systemInfo, diff --git a/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs b/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs index df98c067..7c652998 100644 --- a/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs +++ b/SafeExamBrowser.Client/Operations/DisplayMonitorOperation.cs @@ -17,9 +17,9 @@ namespace SafeExamBrowser.Client.Operations { internal class DisplayMonitorOperation : ClientOperation { - private IDisplayMonitor displayMonitor; - private ILogger logger; - private ITaskbar taskbar; + private readonly IDisplayMonitor displayMonitor; + private readonly ILogger logger; + private readonly ITaskbar taskbar; public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } public override event StatusChangedEventHandler StatusChanged; @@ -36,7 +36,6 @@ namespace SafeExamBrowser.Client.Operations logger.Info("Initializing working area..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeWorkingArea); - displayMonitor.PreventSleepMode(); displayMonitor.InitializePrimaryDisplay(Context.Settings.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0); displayMonitor.StartMonitoringDisplayChanges(); diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs index 22837c23..b9dfb98c 100644 --- a/SafeExamBrowser.Client/Operations/ShellOperation.cs +++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs @@ -19,6 +19,7 @@ using SafeExamBrowser.SystemComponents.Contracts.Network; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Client.Operations { @@ -30,6 +31,7 @@ namespace SafeExamBrowser.Client.Operations private readonly IKeyboard keyboard; private readonly ILogger logger; private readonly INotification logNotification; + private readonly INativeMethods nativeMethods; private readonly INetworkAdapter networkAdapter; private readonly IPowerSupply powerSupply; private readonly ISystemInfo systemInfo; @@ -49,6 +51,7 @@ namespace SafeExamBrowser.Client.Operations IKeyboard keyboard, ILogger logger, INotification logNotification, + INativeMethods nativeMethods, INetworkAdapter networkAdapter, IPowerSupply powerSupply, ISystemInfo systemInfo, @@ -63,6 +66,7 @@ namespace SafeExamBrowser.Client.Operations this.keyboard = keyboard; this.logger = logger; this.logNotification = logNotification; + this.nativeMethods = nativeMethods; this.networkAdapter = networkAdapter; this.powerSupply = powerSupply; this.systemInfo = systemInfo; @@ -82,6 +86,7 @@ namespace SafeExamBrowser.Client.Operations InitializeTaskbar(); InitializeTaskview(); InitializeActivators(); + InitializeAlwaysOnState(); return OperationResult.Success; } @@ -150,6 +155,17 @@ namespace SafeExamBrowser.Client.Operations } } + private void InitializeAlwaysOnState() + { + var display = Context.Settings.Display.AlwaysOn; + var system = Context.Settings.System.AlwaysOn; + + nativeMethods.SetAlwaysOnState(display, system); + + logger.Info($"Display(s) will {(display ? "be always on" : "use the operating system configuration and may turn off")}."); + logger.Info($"System will {(system ? "be always on" : "use the operating system configuration and may enter sleep mode or standby")}."); + } + private void InitializeTaskbar() { if (Context.Settings.Taskbar.EnableTaskbar) diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs index e033f0b2..67d0df2b 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs @@ -206,6 +206,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData settings.ConfigurationMode = ConfigurationMode.Exam; settings.Display.AllowedDisplays = 1; + settings.Display.AlwaysOn = true; settings.Display.IgnoreError = false; settings.Display.InternalDisplayOnly = false; @@ -296,6 +297,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData settings.SessionMode = SessionMode.Normal; + settings.System.AlwaysOn = true; + settings.Taskbar.EnableTaskbar = true; settings.Taskbar.ShowApplicationInfo = false; settings.Taskbar.ShowApplicationLog = false; diff --git a/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs b/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs index c3512a10..e5796f58 100644 --- a/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs +++ b/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs @@ -20,17 +20,12 @@ namespace SafeExamBrowser.Monitoring.Contracts.Display /// 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 and removes the configured wallpaper (if possible). /// void InitializePrimaryDisplay(int taskbarHeight); - /// - /// Prevents the computer from entering sleep mode and turning its display(s) off. - /// - void PreventSleepMode(); - /// /// Resets the desktop working area and wallpaper to their previous (initial) state. /// diff --git a/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs b/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs index 806500ed..c8501437 100644 --- a/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs +++ b/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs @@ -26,9 +26,9 @@ namespace SafeExamBrowser.Monitoring.Display public class DisplayMonitor : IDisplayMonitor { private IBounds originalWorkingArea; - private ILogger logger; - private INativeMethods nativeMethods; - private ISystemInfo systemInfo; + private readonly ILogger logger; + private readonly INativeMethods nativeMethods; + private readonly ISystemInfo systemInfo; private string wallpaper; public event DisplayChangedEventHandler DisplayChanged; @@ -46,12 +46,6 @@ namespace SafeExamBrowser.Monitoring.Display InitializeWallpaper(); } - public void PreventSleepMode() - { - nativeMethods.PreventSleepMode(); - logger.Info("Disabled sleep mode and display timeout."); - } - public void ResetPrimaryDisplay() { ResetWorkingArea(); diff --git a/SafeExamBrowser.Settings/AppSettings.cs b/SafeExamBrowser.Settings/AppSettings.cs index df33edcf..5a4a2632 100644 --- a/SafeExamBrowser.Settings/AppSettings.cs +++ b/SafeExamBrowser.Settings/AppSettings.cs @@ -15,6 +15,7 @@ using SafeExamBrowser.Settings.Proctoring; using SafeExamBrowser.Settings.Security; using SafeExamBrowser.Settings.Server; using SafeExamBrowser.Settings.Service; +using SafeExamBrowser.Settings.System; using SafeExamBrowser.Settings.SystemComponents; using SafeExamBrowser.Settings.UserInterface; @@ -96,6 +97,11 @@ namespace SafeExamBrowser.Settings /// public SessionMode SessionMode { get; set; } + /// + /// All system-related settings. + /// + public SystemSettings System { get; set; } + /// /// All taskbar-related settings. /// @@ -119,6 +125,7 @@ namespace SafeExamBrowser.Settings Security = new SecuritySettings(); Server = new ServerSettings(); Service = new ServiceSettings(); + System = new SystemSettings(); Taskbar = new TaskbarSettings(); } } diff --git a/SafeExamBrowser.Settings/Monitoring/DisplaySettings.cs b/SafeExamBrowser.Settings/Monitoring/DisplaySettings.cs index 8ac76a50..62ff901b 100644 --- a/SafeExamBrowser.Settings/Monitoring/DisplaySettings.cs +++ b/SafeExamBrowser.Settings/Monitoring/DisplaySettings.cs @@ -21,6 +21,12 @@ namespace SafeExamBrowser.Settings.Monitoring /// public int AllowedDisplays { get; set; } + /// + /// Determines whether the display(s) will remain always on or not. This does not prevent the operating system from entering sleep mode or + /// standby, see . + /// + public bool AlwaysOn { get; set; } + /// /// Determines whether any display configuration may be allowed when the configuration can't be verified due to an error. /// diff --git a/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj b/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj index a3d852e9..a85f9b3c 100644 --- a/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj +++ b/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj @@ -92,6 +92,7 @@ + diff --git a/SafeExamBrowser.Settings/System/SystemSettings.cs b/SafeExamBrowser.Settings/System/SystemSettings.cs new file mode 100644 index 00000000..f31b54cb --- /dev/null +++ b/SafeExamBrowser.Settings/System/SystemSettings.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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.Settings.System +{ + /// + /// Defines all settings related to functionality of the operating system. + /// + [Serializable] + public class SystemSettings + { + /// + /// Determines whether the system will remain always on or not (i.e. potentially entering sleep mode or standby). This does not prevent the + /// display(s) from turning off, see . + /// + public bool AlwaysOn { get; set; } + } +} diff --git a/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs b/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs index 95b0c720..345aa143 100644 --- a/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs +++ b/SafeExamBrowser.WindowsApi.Contracts/INativeMethods.cs @@ -131,11 +131,6 @@ namespace SafeExamBrowser.WindowsApi.Contracts /// void PostCloseMessageToShell(); - /// - /// Prevents Windows from entering sleep mode and keeps all displays powered on. - /// - void PreventSleepMode(); - /// /// Registers a keyboard hook for the given callback. Returns the identifier of the newly registered hook. /// @@ -182,6 +177,12 @@ namespace SafeExamBrowser.WindowsApi.Contracts /// void SendCloseMessageTo(IntPtr window); + /// + /// Sets the always on state for the display and the operating system according to the given parameters, i.e. keeps all displays powered on + /// and/or prevents Windows from entering sleep mode or standby when set to true respectively. + /// + void SetAlwaysOnState(bool display = true, bool system = true); + /// /// Sets the wallpaper to the image located at the specified file path. /// diff --git a/SafeExamBrowser.WindowsApi/Desktops/DesktopMonitor.cs b/SafeExamBrowser.WindowsApi/Desktops/DesktopMonitor.cs index 0bade340..5322ad53 100644 --- a/SafeExamBrowser.WindowsApi/Desktops/DesktopMonitor.cs +++ b/SafeExamBrowser.WindowsApi/Desktops/DesktopMonitor.cs @@ -68,6 +68,8 @@ namespace SafeExamBrowser.WindowsApi.Desktops if (name?.Equals(desktop.Name, StringComparison.OrdinalIgnoreCase) != true) { logger.Warn($"Detected desktop switch to '{name}' [{handle}], trying to reactivate {desktop}..."); + + // TODO: SEBWIN-827 desktop.Activate(); } } diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index 8b5bba9a..3370935c 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -165,7 +165,7 @@ namespace SafeExamBrowser.WindowsApi public uint GetProcessIdFor(IntPtr window) { - User32.GetWindowThreadProcessId(window, out uint processId); + User32.GetWindowThreadProcessId(window, out var processId); return processId; } @@ -178,7 +178,7 @@ namespace SafeExamBrowser.WindowsApi public uint GetShellProcessId() { var handle = GetShellWindowHandle(); - var threadId = User32.GetWindowThreadProcessId(handle, out uint processId); + var threadId = User32.GetWindowThreadProcessId(handle, out var processId); return processId; } @@ -276,11 +276,6 @@ namespace SafeExamBrowser.WindowsApi } } - public void PreventSleepMode() - { - Kernel32.SetThreadExecutionState(EXECUTION_STATE.CONTINUOUS | EXECUTION_STATE.DISPLAY_REQUIRED | EXECUTION_STATE.SYSTEM_REQUIRED); - } - public Guid RegisterKeyboardHook(KeyboardHookCallback callback) { var hookId = default(Guid); @@ -411,6 +406,19 @@ namespace SafeExamBrowser.WindowsApi User32.SendMessage(window, Constant.WM_SYSCOMMAND, (IntPtr) SystemCommand.CLOSE, IntPtr.Zero); } + public void SetAlwaysOnState(bool display = true, bool system = true) + { + if (display || system) + { + var state = EXECUTION_STATE.CONTINUOUS; + + state |= display ? EXECUTION_STATE.DISPLAY_REQUIRED : 0x0; + state |= system ? EXECUTION_STATE.SYSTEM_REQUIRED : 0x0; + + Kernel32.SetThreadExecutionState(state); + } + } + public void SetWallpaper(string filePath) { var success = User32.SystemParametersInfo(SPI.SETDESKWALLPAPER, 0, filePath, SPIF.NONE);