SEBWIN-821: Implemented always on configuration for display and system.

This commit is contained in:
Damian Büchel 2024-01-11 12:02:01 +01:00
parent 622df39fca
commit 181346b810
15 changed files with 116 additions and 37 deletions

View file

@ -45,13 +45,11 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
context.Settings = new AppSettings(); context.Settings = new AppSettings();
context.Settings.Taskbar.EnableTaskbar = true; context.Settings.Taskbar.EnableTaskbar = true;
displayMonitor.Setup(d => d.PreventSleepMode()).Callback(() => Assert.AreEqual(++order, 1)); displayMonitor.Setup(d => d.InitializePrimaryDisplay(It.IsAny<int>())).Callback(() => Assert.AreEqual(++order, 1));
displayMonitor.Setup(d => d.InitializePrimaryDisplay(It.IsAny<int>())).Callback(() => Assert.AreEqual(++order, 2)); displayMonitor.Setup(d => d.StartMonitoringDisplayChanges()).Callback(() => Assert.AreEqual(++order, 2));
displayMonitor.Setup(d => d.StartMonitoringDisplayChanges()).Callback(() => Assert.AreEqual(++order, 3));
sut.Perform(); sut.Perform();
displayMonitor.Verify(d => d.PreventSleepMode(), Times.Once);
displayMonitor.Verify(d => d.InitializePrimaryDisplay(It.IsAny<int>()), Times.Once); displayMonitor.Verify(d => d.InitializePrimaryDisplay(It.IsAny<int>()), Times.Once);
displayMonitor.Verify(d => d.StartMonitoringDisplayChanges(), Times.Once); displayMonitor.Verify(d => d.StartMonitoringDisplayChanges(), Times.Once);
} }
@ -59,7 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod] [TestMethod]
public void Perform_MustCorrectlyInitializeDisplayWithTaskbar() public void Perform_MustCorrectlyInitializeDisplayWithTaskbar()
{ {
int height = 25; var height = 25;
context.Settings = new AppSettings(); context.Settings = new AppSettings();
context.Settings.Taskbar.EnableTaskbar = true; context.Settings.Taskbar.EnableTaskbar = true;
@ -74,7 +72,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod] [TestMethod]
public void Perform_MustCorrectlyInitializeDisplayWithoutTaskbar() public void Perform_MustCorrectlyInitializeDisplayWithoutTaskbar()
{ {
int height = 25; var height = 25;
context.Settings = new AppSettings(); context.Settings = new AppSettings();
context.Settings.Taskbar.EnableTaskbar = false; context.Settings.Taskbar.EnableTaskbar = false;

View file

@ -22,6 +22,7 @@ using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
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
{ {
@ -35,6 +36,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<INotification> aboutNotification; private Mock<INotification> aboutNotification;
private Mock<IKeyboard> keyboard; private Mock<IKeyboard> keyboard;
private Mock<INotification> logNotification; private Mock<INotification> logNotification;
private Mock<INativeMethods> nativeMethods;
private Mock<IPowerSupply> powerSupply; private Mock<IPowerSupply> powerSupply;
private Mock<ISystemInfo> systemInfo; private Mock<ISystemInfo> systemInfo;
private Mock<ITaskbar> taskbar; private Mock<ITaskbar> taskbar;
@ -55,6 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
aboutNotification = new Mock<INotification>(); aboutNotification = new Mock<INotification>();
keyboard = new Mock<IKeyboard>(); keyboard = new Mock<IKeyboard>();
logNotification = new Mock<INotification>(); logNotification = new Mock<INotification>();
nativeMethods = new Mock<INativeMethods>();
networkAdapter = new Mock<INetworkAdapter>(); networkAdapter = new Mock<INetworkAdapter>();
powerSupply = new Mock<IPowerSupply>(); powerSupply = new Mock<IPowerSupply>();
systemInfo = new Mock<ISystemInfo>(); systemInfo = new Mock<ISystemInfo>();
@ -77,6 +80,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
keyboard.Object, keyboard.Object,
logger.Object, logger.Object,
logNotification.Object, logNotification.Object,
nativeMethods.Object,
networkAdapter.Object, networkAdapter.Object,
powerSupply.Object, powerSupply.Object,
systemInfo.Object, systemInfo.Object,
@ -185,6 +189,25 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application3.Object), Location.Taskbar), Times.Never); uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(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] [TestMethod]
public void Perform_MustInitializeClock() public void Perform_MustInitializeClock()
{ {

View file

@ -314,6 +314,7 @@ namespace SafeExamBrowser.Client
keyboard, keyboard,
logger, logger,
logNotification, logNotification,
nativeMethods,
networkAdapter, networkAdapter,
powerSupply, powerSupply,
systemInfo, systemInfo,

View file

@ -17,9 +17,9 @@ namespace SafeExamBrowser.Client.Operations
{ {
internal class DisplayMonitorOperation : ClientOperation internal class DisplayMonitorOperation : ClientOperation
{ {
private IDisplayMonitor displayMonitor; private readonly IDisplayMonitor displayMonitor;
private ILogger logger; private readonly ILogger logger;
private ITaskbar taskbar; private readonly ITaskbar taskbar;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged; public override event StatusChangedEventHandler StatusChanged;
@ -36,7 +36,6 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing working area..."); logger.Info("Initializing working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeWorkingArea); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeWorkingArea);
displayMonitor.PreventSleepMode();
displayMonitor.InitializePrimaryDisplay(Context.Settings.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0); displayMonitor.InitializePrimaryDisplay(Context.Settings.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.StartMonitoringDisplayChanges(); displayMonitor.StartMonitoringDisplayChanges();

View file

@ -19,6 +19,7 @@ using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
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
{ {
@ -30,6 +31,7 @@ namespace SafeExamBrowser.Client.Operations
private readonly IKeyboard keyboard; private readonly IKeyboard keyboard;
private readonly ILogger logger; private readonly ILogger logger;
private readonly INotification logNotification; private readonly INotification logNotification;
private readonly INativeMethods nativeMethods;
private readonly INetworkAdapter networkAdapter; private readonly INetworkAdapter networkAdapter;
private readonly IPowerSupply powerSupply; private readonly IPowerSupply powerSupply;
private readonly ISystemInfo systemInfo; private readonly ISystemInfo systemInfo;
@ -49,6 +51,7 @@ namespace SafeExamBrowser.Client.Operations
IKeyboard keyboard, IKeyboard keyboard,
ILogger logger, ILogger logger,
INotification logNotification, INotification logNotification,
INativeMethods nativeMethods,
INetworkAdapter networkAdapter, INetworkAdapter networkAdapter,
IPowerSupply powerSupply, IPowerSupply powerSupply,
ISystemInfo systemInfo, ISystemInfo systemInfo,
@ -63,6 +66,7 @@ namespace SafeExamBrowser.Client.Operations
this.keyboard = keyboard; this.keyboard = keyboard;
this.logger = logger; this.logger = logger;
this.logNotification = logNotification; this.logNotification = logNotification;
this.nativeMethods = nativeMethods;
this.networkAdapter = networkAdapter; this.networkAdapter = networkAdapter;
this.powerSupply = powerSupply; this.powerSupply = powerSupply;
this.systemInfo = systemInfo; this.systemInfo = systemInfo;
@ -82,6 +86,7 @@ namespace SafeExamBrowser.Client.Operations
InitializeTaskbar(); InitializeTaskbar();
InitializeTaskview(); InitializeTaskview();
InitializeActivators(); InitializeActivators();
InitializeAlwaysOnState();
return OperationResult.Success; 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() private void InitializeTaskbar()
{ {
if (Context.Settings.Taskbar.EnableTaskbar) if (Context.Settings.Taskbar.EnableTaskbar)

View file

@ -206,6 +206,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.ConfigurationMode = ConfigurationMode.Exam; settings.ConfigurationMode = ConfigurationMode.Exam;
settings.Display.AllowedDisplays = 1; settings.Display.AllowedDisplays = 1;
settings.Display.AlwaysOn = true;
settings.Display.IgnoreError = false; settings.Display.IgnoreError = false;
settings.Display.InternalDisplayOnly = false; settings.Display.InternalDisplayOnly = false;
@ -296,6 +297,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.SessionMode = SessionMode.Normal; settings.SessionMode = SessionMode.Normal;
settings.System.AlwaysOn = true;
settings.Taskbar.EnableTaskbar = true; settings.Taskbar.EnableTaskbar = true;
settings.Taskbar.ShowApplicationInfo = false; settings.Taskbar.ShowApplicationInfo = false;
settings.Taskbar.ShowApplicationLog = false; settings.Taskbar.ShowApplicationLog = false;

View file

@ -20,17 +20,12 @@ namespace SafeExamBrowser.Monitoring.Contracts.Display
/// Event fired when the primary display or its settings have changed. /// Event fired when the primary display or its settings have changed.
/// </summary> /// </summary>
event DisplayChangedEventHandler DisplayChanged; event DisplayChangedEventHandler DisplayChanged;
/// <summary> /// <summary>
/// Sets the desktop working area to accommodate to the taskbar's height and removes the configured wallpaper (if possible). /// Sets the desktop working area to accommodate to the taskbar's height and removes the configured wallpaper (if possible).
/// </summary> /// </summary>
void InitializePrimaryDisplay(int taskbarHeight); void InitializePrimaryDisplay(int taskbarHeight);
/// <summary>
/// Prevents the computer from entering sleep mode and turning its display(s) off.
/// </summary>
void PreventSleepMode();
/// <summary> /// <summary>
/// Resets the desktop working area and wallpaper to their previous (initial) state. /// Resets the desktop working area and wallpaper to their previous (initial) state.
/// </summary> /// </summary>

View file

@ -26,9 +26,9 @@ namespace SafeExamBrowser.Monitoring.Display
public class DisplayMonitor : IDisplayMonitor public class DisplayMonitor : IDisplayMonitor
{ {
private IBounds originalWorkingArea; private IBounds originalWorkingArea;
private ILogger logger; private readonly ILogger logger;
private INativeMethods nativeMethods; private readonly INativeMethods nativeMethods;
private ISystemInfo systemInfo; private readonly ISystemInfo systemInfo;
private string wallpaper; private string wallpaper;
public event DisplayChangedEventHandler DisplayChanged; public event DisplayChangedEventHandler DisplayChanged;
@ -46,12 +46,6 @@ namespace SafeExamBrowser.Monitoring.Display
InitializeWallpaper(); InitializeWallpaper();
} }
public void PreventSleepMode()
{
nativeMethods.PreventSleepMode();
logger.Info("Disabled sleep mode and display timeout.");
}
public void ResetPrimaryDisplay() public void ResetPrimaryDisplay()
{ {
ResetWorkingArea(); ResetWorkingArea();

View file

@ -15,6 +15,7 @@ using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.Settings.Security; using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.Settings.Server; using SafeExamBrowser.Settings.Server;
using SafeExamBrowser.Settings.Service; using SafeExamBrowser.Settings.Service;
using SafeExamBrowser.Settings.System;
using SafeExamBrowser.Settings.SystemComponents; using SafeExamBrowser.Settings.SystemComponents;
using SafeExamBrowser.Settings.UserInterface; using SafeExamBrowser.Settings.UserInterface;
@ -96,6 +97,11 @@ namespace SafeExamBrowser.Settings
/// </summary> /// </summary>
public SessionMode SessionMode { get; set; } public SessionMode SessionMode { get; set; }
/// <summary>
/// All system-related settings.
/// </summary>
public SystemSettings System { get; set; }
/// <summary> /// <summary>
/// All taskbar-related settings. /// All taskbar-related settings.
/// </summary> /// </summary>
@ -119,6 +125,7 @@ namespace SafeExamBrowser.Settings
Security = new SecuritySettings(); Security = new SecuritySettings();
Server = new ServerSettings(); Server = new ServerSettings();
Service = new ServiceSettings(); Service = new ServiceSettings();
System = new SystemSettings();
Taskbar = new TaskbarSettings(); Taskbar = new TaskbarSettings();
} }
} }

View file

@ -21,6 +21,12 @@ namespace SafeExamBrowser.Settings.Monitoring
/// </summary> /// </summary>
public int AllowedDisplays { get; set; } public int AllowedDisplays { get; set; }
/// <summary>
/// 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 <see cref="System.SystemSettings.AlwaysOn"/>.
/// </summary>
public bool AlwaysOn { get; set; }
/// <summary> /// <summary>
/// Determines whether any display configuration may be allowed when the configuration can't be verified due to an error. /// Determines whether any display configuration may be allowed when the configuration can't be verified due to an error.
/// </summary> /// </summary>

View file

@ -92,6 +92,7 @@
<Compile Include="Service\ServiceSettings.cs" /> <Compile Include="Service\ServiceSettings.cs" />
<Compile Include="AppSettings.cs" /> <Compile Include="AppSettings.cs" />
<Compile Include="SystemComponents\AudioSettings.cs" /> <Compile Include="SystemComponents\AudioSettings.cs" />
<Compile Include="System\SystemSettings.cs" />
<Compile Include="UserInterface\ActionCenterSettings.cs" /> <Compile Include="UserInterface\ActionCenterSettings.cs" />
<Compile Include="UserInterface\TaskbarSettings.cs" /> <Compile Include="UserInterface\TaskbarSettings.cs" />
<Compile Include="UserInterface\UserInterfaceMode.cs" /> <Compile Include="UserInterface\UserInterfaceMode.cs" />

View file

@ -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
{
/// <summary>
/// Defines all settings related to functionality of the operating system.
/// </summary>
[Serializable]
public class SystemSettings
{
/// <summary>
/// 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 <see cref="Monitoring.DisplaySettings.AlwaysOn"/>.
/// </summary>
public bool AlwaysOn { get; set; }
}
}

View file

@ -131,11 +131,6 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </exception> /// </exception>
void PostCloseMessageToShell(); void PostCloseMessageToShell();
/// <summary>
/// Prevents Windows from entering sleep mode and keeps all displays powered on.
/// </summary>
void PreventSleepMode();
/// <summary> /// <summary>
/// Registers a keyboard hook for the given callback. Returns the identifier of the newly registered hook. /// Registers a keyboard hook for the given callback. Returns the identifier of the newly registered hook.
/// </summary> /// </summary>
@ -182,6 +177,12 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </summary> /// </summary>
void SendCloseMessageTo(IntPtr window); void SendCloseMessageTo(IntPtr window);
/// <summary>
/// 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 <c>true</c> respectively.
/// </summary>
void SetAlwaysOnState(bool display = true, bool system = true);
/// <summary> /// <summary>
/// Sets the wallpaper to the image located at the specified file path. /// Sets the wallpaper to the image located at the specified file path.
/// </summary> /// </summary>

View file

@ -68,6 +68,8 @@ namespace SafeExamBrowser.WindowsApi.Desktops
if (name?.Equals(desktop.Name, StringComparison.OrdinalIgnoreCase) != true) if (name?.Equals(desktop.Name, StringComparison.OrdinalIgnoreCase) != true)
{ {
logger.Warn($"Detected desktop switch to '{name}' [{handle}], trying to reactivate {desktop}..."); logger.Warn($"Detected desktop switch to '{name}' [{handle}], trying to reactivate {desktop}...");
// TODO: SEBWIN-827
desktop.Activate(); desktop.Activate();
} }
} }

View file

@ -165,7 +165,7 @@ namespace SafeExamBrowser.WindowsApi
public uint GetProcessIdFor(IntPtr window) public uint GetProcessIdFor(IntPtr window)
{ {
User32.GetWindowThreadProcessId(window, out uint processId); User32.GetWindowThreadProcessId(window, out var processId);
return processId; return processId;
} }
@ -178,7 +178,7 @@ namespace SafeExamBrowser.WindowsApi
public uint GetShellProcessId() public uint GetShellProcessId()
{ {
var handle = GetShellWindowHandle(); var handle = GetShellWindowHandle();
var threadId = User32.GetWindowThreadProcessId(handle, out uint processId); var threadId = User32.GetWindowThreadProcessId(handle, out var processId);
return 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) public Guid RegisterKeyboardHook(KeyboardHookCallback callback)
{ {
var hookId = default(Guid); var hookId = default(Guid);
@ -411,6 +406,19 @@ namespace SafeExamBrowser.WindowsApi
User32.SendMessage(window, Constant.WM_SYSCOMMAND, (IntPtr) SystemCommand.CLOSE, IntPtr.Zero); 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) public void SetWallpaper(string filePath)
{ {
var success = User32.SystemParametersInfo(SPI.SETDESKWALLPAPER, 0, filePath, SPIF.NONE); var success = User32.SystemParametersInfo(SPI.SETDESKWALLPAPER, 0, filePath, SPIF.NONE);