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.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, 2));
displayMonitor.Setup(d => d.StartMonitoringDisplayChanges()).Callback(() => Assert.AreEqual(++order, 3));
displayMonitor.Setup(d => d.InitializePrimaryDisplay(It.IsAny<int>())).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<int>()), 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;

View file

@ -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<INotification> aboutNotification;
private Mock<IKeyboard> keyboard;
private Mock<INotification> logNotification;
private Mock<INativeMethods> nativeMethods;
private Mock<IPowerSupply> powerSupply;
private Mock<ISystemInfo> systemInfo;
private Mock<ITaskbar> taskbar;
@ -55,6 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
aboutNotification = new Mock<INotification>();
keyboard = new Mock<IKeyboard>();
logNotification = new Mock<INotification>();
nativeMethods = new Mock<INativeMethods>();
networkAdapter = new Mock<INetworkAdapter>();
powerSupply = new Mock<IPowerSupply>();
systemInfo = new Mock<ISystemInfo>();
@ -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<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]
public void Perform_MustInitializeClock()
{

View file

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

View file

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

View file

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

View file

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

View file

@ -20,17 +20,12 @@ namespace SafeExamBrowser.Monitoring.Contracts.Display
/// 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 and removes the configured wallpaper (if possible).
/// </summary>
void InitializePrimaryDisplay(int taskbarHeight);
/// <summary>
/// Prevents the computer from entering sleep mode and turning its display(s) off.
/// </summary>
void PreventSleepMode();
/// <summary>
/// Resets the desktop working area and wallpaper to their previous (initial) state.
/// </summary>

View file

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

View file

@ -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
/// </summary>
public SessionMode SessionMode { get; set; }
/// <summary>
/// All system-related settings.
/// </summary>
public SystemSettings System { get; set; }
/// <summary>
/// All taskbar-related settings.
/// </summary>
@ -119,6 +125,7 @@ namespace SafeExamBrowser.Settings
Security = new SecuritySettings();
Server = new ServerSettings();
Service = new ServiceSettings();
System = new SystemSettings();
Taskbar = new TaskbarSettings();
}
}

View file

@ -21,6 +21,12 @@ namespace SafeExamBrowser.Settings.Monitoring
/// </summary>
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>
/// Determines whether any display configuration may be allowed when the configuration can't be verified due to an error.
/// </summary>

View file

@ -92,6 +92,7 @@
<Compile Include="Service\ServiceSettings.cs" />
<Compile Include="AppSettings.cs" />
<Compile Include="SystemComponents\AudioSettings.cs" />
<Compile Include="System\SystemSettings.cs" />
<Compile Include="UserInterface\ActionCenterSettings.cs" />
<Compile Include="UserInterface\TaskbarSettings.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>
void PostCloseMessageToShell();
/// <summary>
/// Prevents Windows from entering sleep mode and keeps all displays powered on.
/// </summary>
void PreventSleepMode();
/// <summary>
/// Registers a keyboard hook for the given callback. Returns the identifier of the newly registered hook.
/// </summary>
@ -182,6 +177,12 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </summary>
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>
/// Sets the wallpaper to the image located at the specified file path.
/// </summary>

View file

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

View file

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