diff --git a/SafeExamBrowser.Client/Behaviour/ClientController.cs b/SafeExamBrowser.Client/Behaviour/ClientController.cs index c17bcbe6..78b3cff2 100644 --- a/SafeExamBrowser.Client/Behaviour/ClientController.cs +++ b/SafeExamBrowser.Client/Behaviour/ClientController.cs @@ -90,7 +90,7 @@ namespace SafeExamBrowser.Client.Behaviour public bool TryStart() { - logger.Info("--- Initiating startup procedure ---"); + logger.Info("Initiating startup procedure..."); splashScreen = uiFactory.CreateSplashScreen(); operations.ProgressIndicator = splashScreen; @@ -107,7 +107,7 @@ namespace SafeExamBrowser.Client.Behaviour { splashScreen.Hide(); - logger.Info("--- Application successfully initialized ---"); + logger.Info("Application successfully initialized."); logger.Log(string.Empty); } else @@ -118,7 +118,7 @@ namespace SafeExamBrowser.Client.Behaviour } else { - logger.Info("--- Application startup aborted! ---"); + logger.Info("Application startup aborted!"); logger.Log(string.Empty); } @@ -128,7 +128,7 @@ namespace SafeExamBrowser.Client.Behaviour public void Terminate() { logger.Log(string.Empty); - logger.Info("--- Initiating shutdown procedure ---"); + logger.Info("Initiating shutdown procedure..."); splashScreen.Show(); splashScreen.BringToForeground(); @@ -139,12 +139,12 @@ namespace SafeExamBrowser.Client.Behaviour if (success) { - logger.Info("--- Application successfully finalized ---"); + logger.Info("Application successfully finalized."); logger.Log(string.Empty); } else { - logger.Info("--- Shutdown procedure failed! ---"); + logger.Info("Shutdown procedure failed!"); logger.Log(string.Empty); } diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index 87c36702..f3f025f6 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -81,6 +81,7 @@ namespace SafeExamBrowser.Configuration CurrentSettings = new Settings(); + CurrentSettings.KioskMode = KioskMode.CreateNewDesktop; CurrentSettings.ServicePolicy = ServicePolicy.Optional; CurrentSettings.Browser.StartUrl = "https://www.safeexambrowser.org/testing"; @@ -108,11 +109,11 @@ namespace SafeExamBrowser.Configuration appConfig.ApplicationStartTime = startTime; appConfig.AppDataFolder = appDataFolder; appConfig.BrowserCachePath = Path.Combine(appDataFolder, "Cache"); - appConfig.BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.txt"); + appConfig.BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.log"); appConfig.ClientId = Guid.NewGuid(); appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; appConfig.ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executable.Location), $"{nameof(SafeExamBrowser)}.Client.exe"); - appConfig.ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.txt"); + appConfig.ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.log"); appConfig.ConfigurationFileExtension = ".seb"; appConfig.DefaultSettingsFileName = "SebClientSettings.seb"; appConfig.DownloadDirectory = Path.Combine(appDataFolder, "Downloads"); @@ -122,7 +123,7 @@ namespace SafeExamBrowser.Configuration appConfig.ProgramVersion = executable.GetCustomAttribute().InformationalVersion; appConfig.RuntimeId = Guid.NewGuid(); appConfig.RuntimeAddress = $"{BASE_ADDRESS}/runtime/{Guid.NewGuid()}"; - appConfig.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.txt"); + appConfig.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.log"); appConfig.SebUriScheme = "seb"; appConfig.SebUriSchemeSecure = "sebs"; appConfig.ServiceAddress = $"{BASE_ADDRESS}/service"; diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index 623bed9f..aab52f7e 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -181,6 +181,7 @@ + diff --git a/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs b/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs index 0ec80045..f8dbbc6c 100644 --- a/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs +++ b/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs @@ -6,16 +6,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; + namespace SafeExamBrowser.Contracts.WindowsApi { /// - /// Defines the API to retrieve information about desktops and perform desktop-related operations. + /// Represents a desktop and defines its functionality. /// public interface IDesktop { /// - /// Retrieves the name of the currently active desktop. + /// The handle identifying the desktop. /// - string CurrentName { get; } + IntPtr Handle { get; } + + /// + /// The name of the desktop. + /// + string Name { get; } + + /// + /// Activates the desktop. + /// + /// If the desktop could not be activated. + void Activate(); + + /// + /// Closes the desktop. + /// + /// If the desktop could not be closed. + void Close(); } } diff --git a/SafeExamBrowser.Contracts/WindowsApi/IDesktopFactory.cs b/SafeExamBrowser.Contracts/WindowsApi/IDesktopFactory.cs new file mode 100644 index 00000000..cff9ff32 --- /dev/null +++ b/SafeExamBrowser.Contracts/WindowsApi/IDesktopFactory.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 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.WindowsApi +{ + /// + /// The factory for desktops of the operating system. + /// + public interface IDesktopFactory + { + /// + /// Creates a new desktop with the given name. + /// + /// If the desktop could not be created. + IDesktop CreateNew(string name); + + /// + /// Retrieves the currently active desktop. + /// + /// If the current desktop could not be retrieved. + IDesktop GetCurrent(); + } +} diff --git a/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs b/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs index eba52a10..1c978dcd 100644 --- a/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs +++ b/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs @@ -14,7 +14,13 @@ namespace SafeExamBrowser.Contracts.WindowsApi public interface IProcessFactory { /// - /// Starts a new process on the currently active desktop. + /// Allows to define the desktop on which new processes should be started. If no startup desktop is specified, processes will be + /// started on the same desktop which was active when the application itself was started. + /// + IDesktop StartupDesktop { set; } + + /// + /// Starts a new process with the given command-line arguments. /// /// If the process could not be started. IProcess StartNew(string path, params string[] args); diff --git a/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs b/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs index 982c4e61..e547d38a 100644 --- a/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs +++ b/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs @@ -89,7 +89,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies var response = proxy.Disconnect(message); var success = response.ConnectionTerminated; - Logger.Debug($"{(success ? "Disconnected" : "Failed to disconnect")} from {address}."); + Logger.Debug($"{(success ? "Disconnected" : "Failed to disconnect")} from '{address}'."); if (success) { diff --git a/SafeExamBrowser.Core/Logging/Logger.cs b/SafeExamBrowser.Core/Logging/Logger.cs index 15c316ab..63de193a 100644 --- a/SafeExamBrowser.Core/Logging/Logger.cs +++ b/SafeExamBrowser.Core/Logging/Logger.cs @@ -81,16 +81,24 @@ namespace SafeExamBrowser.Core.Logging details.AppendLine(); details.AppendLine($" Exception Message: {exception.Message}"); details.AppendLine($" Exception Type: {exception.GetType()}"); - details.AppendLine(); - details.AppendLine(exception.StackTrace); + + if (exception.StackTrace != null) + { + details.AppendLine(); + details.AppendLine(exception.StackTrace); + } for (var inner = exception.InnerException; inner != null; inner = inner.InnerException) { details.AppendLine(); details.AppendLine($" Inner Exception Message: {inner.Message}"); details.AppendLine($" Inner Exception Type: {inner.GetType()}"); - details.AppendLine(); - details.AppendLine(inner.StackTrace); + + if (inner.StackTrace != null) + { + details.AppendLine(); + details.AppendLine(inner.StackTrace); + } } Add(LogLevel.Error, message); diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs index 0e9d8036..27ada7e1 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs @@ -12,21 +12,32 @@ using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Runtime.Behaviour.Operations { internal class KioskModeOperation : IOperation { - private ILogger logger; private IConfigurationRepository configuration; + private IDesktopFactory desktopFactory; private KioskMode kioskMode; + private ILogger logger; + private IProcessFactory processFactory; + private IDesktop newDesktop; + private IDesktop originalDesktop; public IProgressIndicator ProgressIndicator { private get; set; } - public KioskModeOperation(ILogger logger, IConfigurationRepository configuration) + public KioskModeOperation( + IConfigurationRepository configuration, + IDesktopFactory desktopFactory, + ILogger logger, + IProcessFactory processFactory) { - this.logger = logger; this.configuration = configuration; + this.desktopFactory = desktopFactory; + this.logger = logger; + this.processFactory = processFactory; } public OperationResult Perform() @@ -74,12 +85,37 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations private void CreateNewDesktop() { - // TODO + originalDesktop = desktopFactory.GetCurrent(); + logger.Info($"Current desktop is {ToString(originalDesktop)}."); + newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser)); + logger.Info($"Created new desktop {ToString(newDesktop)}."); + newDesktop.Activate(); + logger.Info("Successfully activated new desktop."); + processFactory.StartupDesktop = newDesktop; } private void CloseNewDesktop() { - // TODO + if (originalDesktop != null) + { + originalDesktop.Activate(); + processFactory.StartupDesktop = originalDesktop; + logger.Info($"Switched back to original desktop {ToString(originalDesktop)}."); + } + else + { + logger.Warn($"No original desktop found when attempting to revert kiosk mode '{kioskMode}'!"); + } + + if (newDesktop != null) + { + newDesktop.Close(); + logger.Info($"Closed new desktop {ToString(newDesktop)}."); + } + else + { + logger.Warn($"No new desktop found when attempting to revert kiosk mode '{kioskMode}'!"); + } } private void DisableExplorerShell() @@ -91,5 +127,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations { // TODO } + + private string ToString(IDesktop desktop) + { + return $"'{desktop.Name}' [{desktop.Handle}]"; + } } } diff --git a/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs b/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs index cc30cbf5..14fd0fd7 100644 --- a/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs +++ b/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs @@ -65,7 +65,7 @@ namespace SafeExamBrowser.Runtime.Behaviour public bool TryStart() { - logger.Info("--- Initiating startup procedure ---"); + logger.Info("Initiating startup procedure..."); runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig); splashScreen = uiFactory.CreateSplashScreen(appConfig); @@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.Behaviour { RegisterEvents(); - logger.Info("--- Application successfully initialized ---"); + logger.Info("Application successfully initialized."); logger.Log(string.Empty); logger.Subscribe(runtimeWindow); splashScreen.Hide(); @@ -90,7 +90,7 @@ namespace SafeExamBrowser.Runtime.Behaviour } else { - logger.Info("--- Application startup aborted! ---"); + logger.Info("Application startup aborted!"); logger.Log(string.Empty); messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error); @@ -114,18 +114,18 @@ namespace SafeExamBrowser.Runtime.Behaviour splashScreen?.BringToForeground(); logger.Log(string.Empty); - logger.Info("--- Initiating shutdown procedure ---"); + logger.Info("Initiating shutdown procedure..."); var success = bootstrapSequence.TryRevert(); if (success) { - logger.Info("--- Application successfully finalized ---"); + logger.Info("Application successfully finalized."); logger.Log(string.Empty); } else { - logger.Info("--- Shutdown procedure failed! ---"); + logger.Info("Shutdown procedure failed!"); logger.Log(string.Empty); messageBox.Show(TextKey.MessageBox_ShutdownError, TextKey.MessageBox_ShutdownErrorTitle, icon: MessageBoxIcon.Error); @@ -139,7 +139,7 @@ namespace SafeExamBrowser.Runtime.Behaviour runtimeWindow.Show(); runtimeWindow.BringToForeground(); runtimeWindow.ShowProgressBar(); - logger.Info(">>>--- Initiating session procedure ---<<<"); + logger.Info("Initiating session procedure..."); if (sessionRunning) { @@ -152,7 +152,7 @@ namespace SafeExamBrowser.Runtime.Behaviour { RegisterSessionEvents(); - logger.Info(">>>--- Session is running ---<<<"); + logger.Info("Session is running."); runtimeWindow.HideProgressBar(); runtimeWindow.UpdateText(TextKey.RuntimeWindow_ApplicationRunning); runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None; @@ -166,7 +166,7 @@ namespace SafeExamBrowser.Runtime.Behaviour } else { - logger.Info($">>>--- Session procedure {(result == OperationResult.Aborted ? "was aborted." : "has failed!")} ---<<<"); + logger.Info($"Session procedure {(result == OperationResult.Aborted ? "was aborted." : "has failed!")}"); if (result == OperationResult.Failed) { @@ -187,7 +187,7 @@ namespace SafeExamBrowser.Runtime.Behaviour runtimeWindow.Show(); runtimeWindow.BringToForeground(); runtimeWindow.ShowProgressBar(); - logger.Info(">>>--- Reverting session operations ---<<<"); + logger.Info("Reverting session operations..."); DeregisterSessionEvents(); @@ -195,12 +195,12 @@ namespace SafeExamBrowser.Runtime.Behaviour if (success) { - logger.Info(">>>--- Session is terminated ---<<<"); + logger.Info("Session is terminated."); sessionRunning = false; } else { - logger.Info(">>>--- Session reversion was erroneous! ---<<<"); + logger.Info("Session reversion was erroneous!"); messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error); } } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 31f50298..51448ca7 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -50,13 +50,13 @@ namespace SafeExamBrowser.Runtime var text = new Text(logger); var messageBox = new MessageBox(text); - var uiFactory = new UserInterfaceFactory(text); - var desktop = new Desktop(new ModuleLogger(logger, typeof(Desktop))); - var processFactory = new ProcessFactory(desktop, new ModuleLogger(logger, typeof(ProcessFactory))); + var desktopFactory = new DesktopFactory(new ModuleLogger(logger, typeof(DesktopFactory))); + var processFactory = new ProcessFactory(new ModuleLogger(logger, typeof(ProcessFactory))); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); var resourceLoader = new ResourceLoader(); var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, configuration, new HostObjectFactory(), new ModuleLogger(logger, typeof(RuntimeHost))); var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, typeof(ServiceProxy))); + var uiFactory = new UserInterfaceFactory(text); var bootstrapOperations = new Queue(); var sessionOperations = new Queue(); @@ -68,7 +68,7 @@ namespace SafeExamBrowser.Runtime sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost)); sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text)); sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS)); - sessionOperations.Enqueue(new KioskModeOperation(logger, configuration)); + sessionOperations.Enqueue(new KioskModeOperation(configuration, desktopFactory, logger, processFactory)); sessionOperations.Enqueue(new ClientOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS)); var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); diff --git a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml index bb221a21..e757a836 100644 --- a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml +++ b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml @@ -5,7 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic" xmlns:s="clr-namespace:System;assembly=mscorlib" - mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="850" WindowStyle="None" WindowStartupLocation="CenterScreen" + mc:Ignorable="d" Background="White" Foreground="Black" Height="500" Width="850" WindowStyle="None" WindowStartupLocation="CenterScreen" Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True"> @@ -44,7 +44,7 @@ - @@ -52,7 +52,7 @@ 5 5 - + diff --git a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs index 5f7a7965..9e813a21 100644 --- a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; using System.Windows; using System.Windows.Documents; using SafeExamBrowser.Contracts.Configuration; @@ -22,7 +21,6 @@ namespace SafeExamBrowser.UserInterface.Classic { private bool allowClose; private AppConfig appConfig; - private ILogContentFormatter formatter; private IText text; private RuntimeWindowViewModel model; private WindowClosingEventHandler closing; @@ -39,10 +37,9 @@ namespace SafeExamBrowser.UserInterface.Classic remove { closing -= value; } } - public RuntimeWindow(AppConfig appConfig, ILogContentFormatter formatter, IText text) + public RuntimeWindow(AppConfig appConfig, IText text) { this.appConfig = appConfig; - this.formatter = formatter; this.text = text; InitializeComponent(); @@ -77,7 +74,7 @@ namespace SafeExamBrowser.UserInterface.Classic { Dispatcher.Invoke(() => { - LogTextBlock.Text += formatter.Format(content) + Environment.NewLine; + model.Notify(content); LogScrollViewer.ScrollToEnd(); }); } @@ -137,7 +134,7 @@ namespace SafeExamBrowser.UserInterface.Classic InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 10 }); - model = new RuntimeWindowViewModel(); + model = new RuntimeWindowViewModel(LogTextBlock); AnimatedBorder.DataContext = model; ProgressBar.DataContext = model; StatusTextBlock.DataContext = model; diff --git a/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj b/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj index 7c98f041..f1d6f1da 100644 --- a/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj +++ b/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj @@ -116,7 +116,6 @@ - diff --git a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs index 664770fd..5415680e 100644 --- a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs @@ -17,7 +17,6 @@ using SafeExamBrowser.Contracts.UserInterface.Browser; using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Windows; using SafeExamBrowser.UserInterface.Classic.Controls; -using SafeExamBrowser.UserInterface.Classic.Utilities; namespace SafeExamBrowser.UserInterface.Classic { @@ -66,7 +65,6 @@ namespace SafeExamBrowser.UserInterface.Classic }); logWindowThread.SetApartmentState(ApartmentState.STA); - logWindowThread.Name = nameof(LogWindow); logWindowThread.IsBackground = true; logWindowThread.Start(); @@ -96,7 +94,7 @@ namespace SafeExamBrowser.UserInterface.Classic var windowReadyEvent = new AutoResetEvent(false); var runtimeWindowThread = new Thread(() => { - runtimeWindow = new RuntimeWindow(appConfig, new RuntimeWindowLogFormatter(), text); + runtimeWindow = new RuntimeWindow(appConfig, text); runtimeWindow.Closed += (o, args) => runtimeWindow.Dispatcher.InvokeShutdown(); windowReadyEvent.Set(); diff --git a/SafeExamBrowser.UserInterface.Classic/Utilities/RuntimeWindowLogFormatter.cs b/SafeExamBrowser.UserInterface.Classic/Utilities/RuntimeWindowLogFormatter.cs deleted file mode 100644 index b8c9c605..00000000 --- a/SafeExamBrowser.UserInterface.Classic/Utilities/RuntimeWindowLogFormatter.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2018 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 SafeExamBrowser.Contracts.Logging; - -namespace SafeExamBrowser.UserInterface.Classic.Utilities -{ - internal class RuntimeWindowLogFormatter : ILogContentFormatter - { - public string Format(ILogContent content) - { - if (content is ILogText text) - { - return text.Text; - } - - if (content is ILogMessage message) - { - return FormatLogMessage(message); - } - - throw new NotImplementedException($"The runtime window formatter is not yet implemented for log content of type {content.GetType()}!"); - } - - private string FormatLogMessage(ILogMessage message) - { - var time = message.DateTime.ToString("HH:mm:ss.fff"); - var severity = message.Severity.ToString().ToUpper(); - - return $"{time} - {severity}: {message.Message}"; - } - } -} diff --git a/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs b/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs index 1c2761c3..1fe94eec 100644 --- a/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs +++ b/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs @@ -33,17 +33,16 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels public void Notify(ILogContent content) { - if (content is ILogText text) + switch (content) { - AppendLogText(text); - } - else if (content is ILogMessage message) - { - AppendLogMessage(message); - } - else - { - throw new NotImplementedException($"The default formatter is not yet implemented for log content of type {content.GetType()}!"); + case ILogText text: + AppendLogText(text); + break; + case ILogMessage message: + AppendLogMessage(message); + break; + default: + throw new NotImplementedException($"The log window is not yet implemented for log content of type {content.GetType()}!"); } scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd); @@ -85,6 +84,8 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels { switch (severity) { + case LogLevel.Debug: + return Brushes.Gray; case LogLevel.Error: return Brushes.Red; case LogLevel.Warning: diff --git a/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs b/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs index 6c5bd6e2..e9dbb517 100644 --- a/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs +++ b/SafeExamBrowser.UserInterface.Classic/ViewModels/RuntimeWindowViewModel.cs @@ -6,13 +6,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.UserInterface.Classic.ViewModels { internal class RuntimeWindowViewModel : ProgressIndicatorViewModel { private Visibility animatedBorderVisibility, progressBarVisibility; + private TextBlock textBlock; public Visibility AnimatedBorderVisibility { @@ -40,6 +46,26 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels } } + public RuntimeWindowViewModel(TextBlock textBlock) + { + this.textBlock = textBlock; + } + + public void Notify(ILogContent content) + { + switch (content) + { + case ILogText text: + AppendLogText(text); + break; + case ILogMessage message: + AppendLogMessage(message); + break; + default: + throw new NotImplementedException($"The runtime window is not yet implemented for log content of type {content.GetType()}!"); + } + } + public override void StartBusyIndication() { base.StartBusyIndication(); @@ -53,5 +79,37 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels AnimatedBorderVisibility = Visibility.Visible; } + + private void AppendLogMessage(ILogMessage message) + { + var time = message.DateTime.ToString("HH:mm:ss.fff"); + var severity = message.Severity.ToString().ToUpper(); + + var infoRun = new Run($"{time} - ") { Foreground = Brushes.Gray }; + var messageRun = new Run($"{severity}: {message.Message}{Environment.NewLine}") { Foreground = GetBrushFor(message.Severity) }; + + textBlock.Inlines.Add(infoRun); + textBlock.Inlines.Add(messageRun); + } + + private void AppendLogText(ILogText text) + { + textBlock.Inlines.Add(new Run($"{text.Text}{Environment.NewLine}")); + } + + private Brush GetBrushFor(LogLevel severity) + { + switch (severity) + { + case LogLevel.Debug: + return Brushes.Gray; + case LogLevel.Error: + return Brushes.Red; + case LogLevel.Warning: + return Brushes.Orange; + default: + return Brushes.Black; + } + } } } diff --git a/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs index 7c524531..65a198fd 100644 --- a/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs @@ -61,7 +61,6 @@ namespace SafeExamBrowser.UserInterface.Windows10 }); logWindowThread.SetApartmentState(ApartmentState.STA); - logWindowThread.Name = nameof(LogWindow); logWindowThread.IsBackground = true; logWindowThread.Start(); diff --git a/SafeExamBrowser.UserInterface.Windows10/ViewModels/LogViewModel.cs b/SafeExamBrowser.UserInterface.Windows10/ViewModels/LogViewModel.cs index e012152f..1532404a 100644 --- a/SafeExamBrowser.UserInterface.Windows10/ViewModels/LogViewModel.cs +++ b/SafeExamBrowser.UserInterface.Windows10/ViewModels/LogViewModel.cs @@ -33,17 +33,16 @@ namespace SafeExamBrowser.UserInterface.Windows10.ViewModels public void Notify(ILogContent content) { - if (content is ILogText text) + switch (content) { - AppendLogText(text); - } - else if (content is ILogMessage message) - { - AppendLogMessage(message); - } - else - { - throw new NotImplementedException($"The default formatter is not yet implemented for log content of type {content.GetType()}!"); + case ILogText text: + AppendLogText(text); + break; + case ILogMessage message: + AppendLogMessage(message); + break; + default: + throw new NotImplementedException($"The log window is not yet implemented for log content of type {content.GetType()}!"); } scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd); @@ -85,6 +84,8 @@ namespace SafeExamBrowser.UserInterface.Windows10.ViewModels { switch (severity) { + case LogLevel.Debug: + return Brushes.Gray; case LogLevel.Error: return Brushes.Red; case LogLevel.Warning: diff --git a/SafeExamBrowser.WindowsApi/Constants/ACCESS_MASK.cs b/SafeExamBrowser.WindowsApi/Constants/ACCESS_MASK.cs new file mode 100644 index 00000000..edd12cfe --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Constants/ACCESS_MASK.cs @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 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.Constants +{ + /// + /// See https://docs.microsoft.com/de-de/windows/desktop/SecAuthZ/access-mask + /// + [Flags] + internal enum ACCESS_MASK : uint + { + DESKTOP_NONE = 0, + DESKTOP_READOBJECTS = 0x0001, + DESKTOP_CREATEWINDOW = 0x0002, + DESKTOP_CREATEMENU = 0x0004, + DESKTOP_HOOKCONTROL = 0x0008, + DESKTOP_JOURNALRECORD = 0x0010, + DESKTOP_JOURNALPLAYBACK = 0x0020, + DESKTOP_ENUMERATE = 0x0040, + DESKTOP_WRITEOBJECTS = 0x0080, + DESKTOP_SWITCHDESKTOP = 0x0100, + + GENERIC_ALL = (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD + | DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP + | Constant.STANDARD_RIGHTS_REQUIRED) + } +} diff --git a/SafeExamBrowser.WindowsApi/Constants/Constant.cs b/SafeExamBrowser.WindowsApi/Constants/Constant.cs index f0dd00d6..3675c528 100644 --- a/SafeExamBrowser.WindowsApi/Constants/Constant.cs +++ b/SafeExamBrowser.WindowsApi/Constants/Constant.cs @@ -39,6 +39,20 @@ namespace SafeExamBrowser.WindowsApi.Constants /// internal const int NORMAL_PRIORITY_CLASS = 0x20; + /// + /// Standard access rights required for a desktop. + /// + /// See https://docs.microsoft.com/de-de/windows/desktop/SecAuthZ/standard-access-rights. + /// + internal const int STANDARD_RIGHTS_REQUIRED = 0xF0000; + + /// + /// The constant for the name of a user object. + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683238(v=vs.85).aspx. + /// + internal const int UOI_NAME = 2; + /// /// The callback function is not mapped into the address space of the process that generates the event. Because the hook function /// is called across process boundaries, the system must queue events. Although this method is asynchronous, events are guaranteed diff --git a/SafeExamBrowser.WindowsApi/Delegates/EnumDesktopDelegate.cs b/SafeExamBrowser.WindowsApi/Delegates/EnumDesktopDelegate.cs new file mode 100644 index 00000000..0adab678 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Delegates/EnumDesktopDelegate.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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.Delegates +{ + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682612(v=vs.85).aspx + /// + internal delegate bool EnumDesktopDelegate(string lpszDesktop, IntPtr lParam); +} diff --git a/SafeExamBrowser.WindowsApi/Delegates/EnumWindowsDelegate.cs b/SafeExamBrowser.WindowsApi/Delegates/EnumWindowsDelegate.cs new file mode 100644 index 00000000..9b5cc89b --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Delegates/EnumWindowsDelegate.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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.Delegates +{ + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633498(v=vs.85).aspx + /// + internal delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam); +} diff --git a/SafeExamBrowser.WindowsApi/Delegates/EventDelegate.cs b/SafeExamBrowser.WindowsApi/Delegates/EventDelegate.cs new file mode 100644 index 00000000..0e67ad03 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Delegates/EventDelegate.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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.Delegates +{ + /// + /// See https://docs.microsoft.com/de-de/windows/desktop/api/winuser/nc-winuser-wineventproc + /// + internal delegate void EventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); +} diff --git a/SafeExamBrowser.WindowsApi/Delegates/HookDelegate.cs b/SafeExamBrowser.WindowsApi/Delegates/HookDelegate.cs new file mode 100644 index 00000000..46732f42 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Delegates/HookDelegate.cs @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 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.Delegates +{ + /// + /// See https://docs.microsoft.com/de-de/windows/desktop/api/winuser/nc-winuser-hookproc + /// + internal delegate IntPtr HookDelegate(int code, IntPtr wParam, IntPtr lParam); +} diff --git a/SafeExamBrowser.WindowsApi/Desktop.cs b/SafeExamBrowser.WindowsApi/Desktop.cs index d54333db..1598304f 100644 --- a/SafeExamBrowser.WindowsApi/Desktop.cs +++ b/SafeExamBrowser.WindowsApi/Desktop.cs @@ -6,21 +6,42 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Logging; +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.WindowsApi { public class Desktop : IDesktop { - private ILogger logger; + public IntPtr Handle { get; private set; } + public string Name { get; private set; } - // TODO: Implement desktop functionality! - public string CurrentName => "Default"; - - public Desktop(ILogger logger) + public Desktop(IntPtr handle, string name) { - this.logger = logger; + Handle = handle; + Name = name; + } + + public void Activate() + { + var success = User32.SwitchDesktop(Handle); + + if (!success) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + + public void Close() + { + var success = User32.CloseDesktop(Handle); + + if (!success) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } } } } diff --git a/SafeExamBrowser.WindowsApi/DesktopFactory.cs b/SafeExamBrowser.WindowsApi/DesktopFactory.cs new file mode 100644 index 00000000..eb37a8c7 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/DesktopFactory.cs @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 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.ComponentModel; +using System.Runtime.InteropServices; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.WindowsApi; +using SafeExamBrowser.WindowsApi.Constants; + +namespace SafeExamBrowser.WindowsApi +{ + public class DesktopFactory : IDesktopFactory + { + private ILogger logger; + + public DesktopFactory(ILogger logger) + { + this.logger = logger; + } + + public IDesktop CreateNew(string name) + { + logger.Debug($"Attempting to create new desktop '{name}'..."); + + var handle = User32.CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0, (uint) ACCESS_MASK.GENERIC_ALL, IntPtr.Zero); + + if (handle == IntPtr.Zero) + { + logger.Error($"Failed to create new desktop '{name}'!"); + + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + logger.Debug($"Successfully created desktop '{name}' [{handle}]."); + + return new Desktop(handle, name); + } + + public IDesktop GetCurrent() + { + var threadId = Kernel32.GetCurrentThreadId(); + var handle = User32.GetThreadDesktop(threadId); + var name = String.Empty; + var nameLength = 0; + + if (handle != IntPtr.Zero) + { + logger.Debug($"Found desktop handle {handle} for thread {threadId}. Attempting to get desktop name..."); + } + else + { + logger.Error($"Failed to get desktop handle for thread {threadId}!"); + + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + User32.GetUserObjectInformation(handle, Constant.UOI_NAME, IntPtr.Zero, 0, ref nameLength); + + var namePointer = Marshal.AllocHGlobal(nameLength); + var success = User32.GetUserObjectInformation(handle, Constant.UOI_NAME, namePointer, nameLength, ref nameLength); + + if (!success) + { + logger.Error($"Failed to retrieve name for desktop with handle {handle}!"); + + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + name = Marshal.PtrToStringAnsi(namePointer); + Marshal.FreeHGlobal(namePointer); + + logger.Debug($"Successfully determined current desktop as '{name}' [{handle}]."); + + return new Desktop(handle, name); + } + } +} diff --git a/SafeExamBrowser.WindowsApi/Kernel32.cs b/SafeExamBrowser.WindowsApi/Kernel32.cs index 3311cbef..dacffd5e 100644 --- a/SafeExamBrowser.WindowsApi/Kernel32.cs +++ b/SafeExamBrowser.WindowsApi/Kernel32.cs @@ -17,20 +17,23 @@ namespace SafeExamBrowser.WindowsApi /// internal class Kernel32 { - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [DllImport("kernel32.dll", SetLastError = true)] internal static extern bool CreateProcess( string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, - uint dwCreationFlags, + int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, - [In] ref STARTUPINFO lpStartupInfo, + ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation); - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int GetCurrentThreadId(); + + [DllImport("kernel32.dll", SetLastError = true)] internal static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll", SetLastError = true)] diff --git a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs index 1828c85f..ceb97ad1 100644 --- a/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs +++ b/SafeExamBrowser.WindowsApi/Monitoring/KeyboardHook.cs @@ -10,6 +10,7 @@ using System; using System.Runtime.InteropServices; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.WindowsApi.Constants; +using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Types; namespace SafeExamBrowser.WindowsApi.Monitoring @@ -23,7 +24,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring private const int DELETE = 46; private bool altPressed, ctrlPressed; - private HookProc hookProc; + private HookDelegate hookProc; internal IntPtr Handle { get; private set; } internal IKeyboardInterceptor Interceptor { get; private set; } @@ -40,7 +41,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring // IMORTANT: // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - hookProc = new HookProc(LowLevelKeyboardProc); + hookProc = new HookDelegate(LowLevelKeyboardProc); Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, module, 0); } diff --git a/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs b/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs index 56f47f44..f738d1e8 100644 --- a/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs +++ b/SafeExamBrowser.WindowsApi/Monitoring/MouseHook.cs @@ -10,13 +10,14 @@ using System; using System.Runtime.InteropServices; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.WindowsApi.Constants; +using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Types; namespace SafeExamBrowser.WindowsApi.Monitoring { internal class MouseHook { - private HookProc hookProc; + private HookDelegate hookProc; internal IntPtr Handle { get; private set; } internal IMouseInterceptor Interceptor { get; private set; } @@ -33,7 +34,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring // IMORTANT: // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - hookProc = new HookProc(LowLevelMouseProc); + hookProc = new HookDelegate(LowLevelMouseProc); Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, module, 0); } diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index e4b9d441..ded4adda 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -16,6 +16,7 @@ using System.Text; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.WindowsApi.Constants; +using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Monitoring; using SafeExamBrowser.WindowsApi.Types; @@ -23,7 +24,7 @@ namespace SafeExamBrowser.WindowsApi { public class NativeMethods : INativeMethods { - private ConcurrentDictionary EventDelegates = new ConcurrentDictionary(); + private ConcurrentDictionary EventDelegates = new ConcurrentDictionary(); private ConcurrentDictionary KeyboardHooks = new ConcurrentDictionary(); private ConcurrentDictionary MouseHooks = new ConcurrentDictionary(); @@ -91,7 +92,7 @@ namespace SafeExamBrowser.WindowsApi throw new Win32Exception(Marshal.GetLastWin32Error()); } - EventDelegates.TryRemove(handle, out EventProc d); + EventDelegates.TryRemove(handle, out EventDelegate d); } public void EmptyClipboard() @@ -111,7 +112,8 @@ namespace SafeExamBrowser.WindowsApi public IEnumerable GetOpenWindows() { var windows = new List(); - var success = User32.EnumWindows(delegate (IntPtr hWnd, IntPtr lParam) + + bool EnumWindows(IntPtr hWnd, IntPtr lParam) { if (hWnd != GetShellWindowHandle() && User32.IsWindowVisible(hWnd) && User32.GetWindowTextLength(hWnd) > 0) { @@ -119,7 +121,9 @@ namespace SafeExamBrowser.WindowsApi } return true; - }, IntPtr.Zero); + } + + var success = User32.EnumWindows(EnumWindows, IntPtr.Zero); if (!success) { @@ -245,34 +249,34 @@ namespace SafeExamBrowser.WindowsApi public IntPtr RegisterSystemForegroundEvent(Action callback) { - void eventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + void evenDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { callback(hwnd); } - var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_FOREGROUND, Constant.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); + var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_FOREGROUND, Constant.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, evenDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); // IMORTANT: // Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - EventDelegates[handle] = eventProc; + EventDelegates[handle] = evenDelegate; return handle; } public IntPtr RegisterSystemCaptureStartEvent(Action callback) { - void eventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + void eventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { callback(hwnd); } - var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); + var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); // IMORTANT: // Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Not doing so will result in a CallbackOnCollectedDelegate error and subsequent application crash! - EventDelegates[handle] = eventProc; + EventDelegates[handle] = eventDelegate; return handle; } diff --git a/SafeExamBrowser.WindowsApi/ProcessFactory.cs b/SafeExamBrowser.WindowsApi/ProcessFactory.cs index 8e7dea7b..e87f1911 100644 --- a/SafeExamBrowser.WindowsApi/ProcessFactory.cs +++ b/SafeExamBrowser.WindowsApi/ProcessFactory.cs @@ -8,6 +8,7 @@ using System; using System.ComponentModel; +using System.IO; using System.Runtime.InteropServices; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.WindowsApi; @@ -18,12 +19,12 @@ namespace SafeExamBrowser.WindowsApi { public class ProcessFactory : IProcessFactory { - private IDesktop desktop; private ILogger logger; - public ProcessFactory(IDesktop desktop, ILogger logger) + public IDesktop StartupDesktop { private get; set; } + + public ProcessFactory(ILogger logger) { - this.desktop = desktop; this.logger = logger; } @@ -34,13 +35,20 @@ namespace SafeExamBrowser.WindowsApi var startupInfo = new STARTUPINFO(); startupInfo.cb = Marshal.SizeOf(startupInfo); - // TODO: Specify target desktop! - //startupInfo.lpDesktop = desktop.CurrentName; + startupInfo.lpDesktop = StartupDesktop?.Name; + + logger.Info($"Attempting to start process '{path}'..."); var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo); - if (!success) + if (success) { + logger.Info($"Successfully started process '{Path.GetFileName(path)}' with ID {processInfo.dwProcessId}."); + } + else + { + logger.Error($"Failed to start process '{Path.GetFileName(path)}'!"); + throw new Win32Exception(Marshal.GetLastWin32Error()); } diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj index 4ffd72d5..c5dfc3e3 100644 --- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj +++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj @@ -53,10 +53,16 @@ + + + + + + diff --git a/SafeExamBrowser.WindowsApi/User32.cs b/SafeExamBrowser.WindowsApi/User32.cs index b3755088..cb31d60a 100644 --- a/SafeExamBrowser.WindowsApi/User32.cs +++ b/SafeExamBrowser.WindowsApi/User32.cs @@ -10,14 +10,11 @@ using System; using System.Runtime.InteropServices; using System.Text; using SafeExamBrowser.WindowsApi.Constants; +using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Types; namespace SafeExamBrowser.WindowsApi { - internal delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam); - internal delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam); - internal delegate void EventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); - /// /// Provides access to the native Windows API exposed by user32.dll. /// @@ -30,18 +27,37 @@ namespace SafeExamBrowser.WindowsApi [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseClipboard(); + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool CloseDesktop(IntPtr hDesktop); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EmptyClipboard(); + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool EnumDesktops(IntPtr hwinsta, EnumDesktopDelegate lpEnumFunc, IntPtr lParam); + [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EnumWindows(EnumWindowsDelegate enumProc, IntPtr lParam); - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [DllImport("user32.dll", SetLastError = true)] internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName); - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr GetThreadDesktop(int dwThreadId); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr GetProcessWindowStation(); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool GetUserObjectInformation(IntPtr hObj, int nIndex, IntPtr pvInfo, int nLength, ref int lpnLengthNeeded); + + [DllImport("user32.dll", SetLastError = true)] internal static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount); [DllImport("user32.dll", SetLastError = true)] @@ -66,20 +82,23 @@ namespace SafeExamBrowser.WindowsApi internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] - internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, EventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); + internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, EventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); [DllImport("user32.dll", SetLastError = true)] - internal static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId); + internal static extern IntPtr SetWindowsHookEx(HookType hookType, HookDelegate lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool SwitchDesktop(IntPtr hDesktop); + [DllImport("user32.dll", SetLastError = true)] [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)] + [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SystemParametersInfo(SPI uiAction, int uiParam, string pvParam, SPIF fWinIni);