SEBWIN-220: Implemented kiosk mode "Create New Desktop".

This commit is contained in:
dbuechel 2018-08-16 11:23:37 +02:00
parent 02d69005c0
commit 729133ac78
35 changed files with 533 additions and 152 deletions

View file

@ -90,7 +90,7 @@ namespace SafeExamBrowser.Client.Behaviour
public bool TryStart() public bool TryStart()
{ {
logger.Info("--- Initiating startup procedure ---"); logger.Info("Initiating startup procedure...");
splashScreen = uiFactory.CreateSplashScreen(); splashScreen = uiFactory.CreateSplashScreen();
operations.ProgressIndicator = splashScreen; operations.ProgressIndicator = splashScreen;
@ -107,7 +107,7 @@ namespace SafeExamBrowser.Client.Behaviour
{ {
splashScreen.Hide(); splashScreen.Hide();
logger.Info("--- Application successfully initialized ---"); logger.Info("Application successfully initialized.");
logger.Log(string.Empty); logger.Log(string.Empty);
} }
else else
@ -118,7 +118,7 @@ namespace SafeExamBrowser.Client.Behaviour
} }
else else
{ {
logger.Info("--- Application startup aborted! ---"); logger.Info("Application startup aborted!");
logger.Log(string.Empty); logger.Log(string.Empty);
} }
@ -128,7 +128,7 @@ namespace SafeExamBrowser.Client.Behaviour
public void Terminate() public void Terminate()
{ {
logger.Log(string.Empty); logger.Log(string.Empty);
logger.Info("--- Initiating shutdown procedure ---"); logger.Info("Initiating shutdown procedure...");
splashScreen.Show(); splashScreen.Show();
splashScreen.BringToForeground(); splashScreen.BringToForeground();
@ -139,12 +139,12 @@ namespace SafeExamBrowser.Client.Behaviour
if (success) if (success)
{ {
logger.Info("--- Application successfully finalized ---"); logger.Info("Application successfully finalized.");
logger.Log(string.Empty); logger.Log(string.Empty);
} }
else else
{ {
logger.Info("--- Shutdown procedure failed! ---"); logger.Info("Shutdown procedure failed!");
logger.Log(string.Empty); logger.Log(string.Empty);
} }

View file

@ -81,6 +81,7 @@ namespace SafeExamBrowser.Configuration
CurrentSettings = new Settings(); CurrentSettings = new Settings();
CurrentSettings.KioskMode = KioskMode.CreateNewDesktop;
CurrentSettings.ServicePolicy = ServicePolicy.Optional; CurrentSettings.ServicePolicy = ServicePolicy.Optional;
CurrentSettings.Browser.StartUrl = "https://www.safeexambrowser.org/testing"; CurrentSettings.Browser.StartUrl = "https://www.safeexambrowser.org/testing";
@ -108,11 +109,11 @@ namespace SafeExamBrowser.Configuration
appConfig.ApplicationStartTime = startTime; appConfig.ApplicationStartTime = startTime;
appConfig.AppDataFolder = appDataFolder; appConfig.AppDataFolder = appDataFolder;
appConfig.BrowserCachePath = Path.Combine(appDataFolder, "Cache"); 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.ClientId = Guid.NewGuid();
appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}";
appConfig.ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executable.Location), $"{nameof(SafeExamBrowser)}.Client.exe"); 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.ConfigurationFileExtension = ".seb";
appConfig.DefaultSettingsFileName = "SebClientSettings.seb"; appConfig.DefaultSettingsFileName = "SebClientSettings.seb";
appConfig.DownloadDirectory = Path.Combine(appDataFolder, "Downloads"); appConfig.DownloadDirectory = Path.Combine(appDataFolder, "Downloads");
@ -122,7 +123,7 @@ namespace SafeExamBrowser.Configuration
appConfig.ProgramVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; appConfig.ProgramVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
appConfig.RuntimeId = Guid.NewGuid(); appConfig.RuntimeId = Guid.NewGuid();
appConfig.RuntimeAddress = $"{BASE_ADDRESS}/runtime/{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.SebUriScheme = "seb";
appConfig.SebUriSchemeSecure = "sebs"; appConfig.SebUriSchemeSecure = "sebs";
appConfig.ServiceAddress = $"{BASE_ADDRESS}/service"; appConfig.ServiceAddress = $"{BASE_ADDRESS}/service";

View file

@ -181,6 +181,7 @@
<Compile Include="WindowsApi\Events\ProcessTerminatedEventHandler.cs" /> <Compile Include="WindowsApi\Events\ProcessTerminatedEventHandler.cs" />
<Compile Include="WindowsApi\IBounds.cs" /> <Compile Include="WindowsApi\IBounds.cs" />
<Compile Include="WindowsApi\IDesktop.cs" /> <Compile Include="WindowsApi\IDesktop.cs" />
<Compile Include="WindowsApi\IDesktopFactory.cs" />
<Compile Include="WindowsApi\INativeMethods.cs" /> <Compile Include="WindowsApi\INativeMethods.cs" />
<Compile Include="WindowsApi\IProcess.cs" /> <Compile Include="WindowsApi\IProcess.cs" />
<Compile Include="WindowsApi\IProcessFactory.cs" /> <Compile Include="WindowsApi\IProcessFactory.cs" />

View file

@ -6,16 +6,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
namespace SafeExamBrowser.Contracts.WindowsApi namespace SafeExamBrowser.Contracts.WindowsApi
{ {
/// <summary> /// <summary>
/// Defines the API to retrieve information about desktops and perform desktop-related operations. /// Represents a desktop and defines its functionality.
/// </summary> /// </summary>
public interface IDesktop public interface IDesktop
{ {
/// <summary> /// <summary>
/// Retrieves the name of the currently active desktop. /// The handle identifying the desktop.
/// </summary> /// </summary>
string CurrentName { get; } IntPtr Handle { get; }
/// <summary>
/// The name of the desktop.
/// </summary>
string Name { get; }
/// <summary>
/// Activates the desktop.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the desktop could not be activated.</exception>
void Activate();
/// <summary>
/// Closes the desktop.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the desktop could not be closed.</exception>
void Close();
} }
} }

View file

@ -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
{
/// <summary>
/// The factory for desktops of the operating system.
/// </summary>
public interface IDesktopFactory
{
/// <summary>
/// Creates a new desktop with the given name.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the desktop could not be created.</exception>
IDesktop CreateNew(string name);
/// <summary>
/// Retrieves the currently active desktop.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the current desktop could not be retrieved.</exception>
IDesktop GetCurrent();
}
}

View file

@ -14,7 +14,13 @@ namespace SafeExamBrowser.Contracts.WindowsApi
public interface IProcessFactory public interface IProcessFactory
{ {
/// <summary> /// <summary>
/// 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.
/// </summary>
IDesktop StartupDesktop { set; }
/// <summary>
/// Starts a new process with the given command-line arguments.
/// </summary> /// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception> /// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception>
IProcess StartNew(string path, params string[] args); IProcess StartNew(string path, params string[] args);

View file

@ -89,7 +89,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies
var response = proxy.Disconnect(message); var response = proxy.Disconnect(message);
var success = response.ConnectionTerminated; var success = response.ConnectionTerminated;
Logger.Debug($"{(success ? "Disconnected" : "Failed to disconnect")} from {address}."); Logger.Debug($"{(success ? "Disconnected" : "Failed to disconnect")} from '{address}'.");
if (success) if (success)
{ {

View file

@ -81,16 +81,24 @@ namespace SafeExamBrowser.Core.Logging
details.AppendLine(); details.AppendLine();
details.AppendLine($" Exception Message: {exception.Message}"); details.AppendLine($" Exception Message: {exception.Message}");
details.AppendLine($" Exception Type: {exception.GetType()}"); 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) for (var inner = exception.InnerException; inner != null; inner = inner.InnerException)
{ {
details.AppendLine(); details.AppendLine();
details.AppendLine($" Inner Exception Message: {inner.Message}"); details.AppendLine($" Inner Exception Message: {inner.Message}");
details.AppendLine($" Inner Exception Type: {inner.GetType()}"); 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); Add(LogLevel.Error, message);

View file

@ -12,21 +12,32 @@ using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal class KioskModeOperation : IOperation internal class KioskModeOperation : IOperation
{ {
private ILogger logger;
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private IDesktopFactory desktopFactory;
private KioskMode kioskMode; private KioskMode kioskMode;
private ILogger logger;
private IProcessFactory processFactory;
private IDesktop newDesktop;
private IDesktop originalDesktop;
public IProgressIndicator ProgressIndicator { private get; set; } 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.configuration = configuration;
this.desktopFactory = desktopFactory;
this.logger = logger;
this.processFactory = processFactory;
} }
public OperationResult Perform() public OperationResult Perform()
@ -74,12 +85,37 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private void CreateNewDesktop() 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() 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() private void DisableExplorerShell()
@ -91,5 +127,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
// TODO // TODO
} }
private string ToString(IDesktop desktop)
{
return $"'{desktop.Name}' [{desktop.Handle}]";
}
} }
} }

View file

@ -65,7 +65,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
public bool TryStart() public bool TryStart()
{ {
logger.Info("--- Initiating startup procedure ---"); logger.Info("Initiating startup procedure...");
runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig); runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig);
splashScreen = uiFactory.CreateSplashScreen(appConfig); splashScreen = uiFactory.CreateSplashScreen(appConfig);
@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
{ {
RegisterEvents(); RegisterEvents();
logger.Info("--- Application successfully initialized ---"); logger.Info("Application successfully initialized.");
logger.Log(string.Empty); logger.Log(string.Empty);
logger.Subscribe(runtimeWindow); logger.Subscribe(runtimeWindow);
splashScreen.Hide(); splashScreen.Hide();
@ -90,7 +90,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
} }
else else
{ {
logger.Info("--- Application startup aborted! ---"); logger.Info("Application startup aborted!");
logger.Log(string.Empty); logger.Log(string.Empty);
messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error); messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error);
@ -114,18 +114,18 @@ namespace SafeExamBrowser.Runtime.Behaviour
splashScreen?.BringToForeground(); splashScreen?.BringToForeground();
logger.Log(string.Empty); logger.Log(string.Empty);
logger.Info("--- Initiating shutdown procedure ---"); logger.Info("Initiating shutdown procedure...");
var success = bootstrapSequence.TryRevert(); var success = bootstrapSequence.TryRevert();
if (success) if (success)
{ {
logger.Info("--- Application successfully finalized ---"); logger.Info("Application successfully finalized.");
logger.Log(string.Empty); logger.Log(string.Empty);
} }
else else
{ {
logger.Info("--- Shutdown procedure failed! ---"); logger.Info("Shutdown procedure failed!");
logger.Log(string.Empty); logger.Log(string.Empty);
messageBox.Show(TextKey.MessageBox_ShutdownError, TextKey.MessageBox_ShutdownErrorTitle, icon: MessageBoxIcon.Error); messageBox.Show(TextKey.MessageBox_ShutdownError, TextKey.MessageBox_ShutdownErrorTitle, icon: MessageBoxIcon.Error);
@ -139,7 +139,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
runtimeWindow.Show(); runtimeWindow.Show();
runtimeWindow.BringToForeground(); runtimeWindow.BringToForeground();
runtimeWindow.ShowProgressBar(); runtimeWindow.ShowProgressBar();
logger.Info(">>>--- Initiating session procedure ---<<<"); logger.Info("Initiating session procedure...");
if (sessionRunning) if (sessionRunning)
{ {
@ -152,7 +152,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
{ {
RegisterSessionEvents(); RegisterSessionEvents();
logger.Info(">>>--- Session is running ---<<<"); logger.Info("Session is running.");
runtimeWindow.HideProgressBar(); runtimeWindow.HideProgressBar();
runtimeWindow.UpdateText(TextKey.RuntimeWindow_ApplicationRunning); runtimeWindow.UpdateText(TextKey.RuntimeWindow_ApplicationRunning);
runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None; runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None;
@ -166,7 +166,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
} }
else 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) if (result == OperationResult.Failed)
{ {
@ -187,7 +187,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
runtimeWindow.Show(); runtimeWindow.Show();
runtimeWindow.BringToForeground(); runtimeWindow.BringToForeground();
runtimeWindow.ShowProgressBar(); runtimeWindow.ShowProgressBar();
logger.Info(">>>--- Reverting session operations ---<<<"); logger.Info("Reverting session operations...");
DeregisterSessionEvents(); DeregisterSessionEvents();
@ -195,12 +195,12 @@ namespace SafeExamBrowser.Runtime.Behaviour
if (success) if (success)
{ {
logger.Info(">>>--- Session is terminated ---<<<"); logger.Info("Session is terminated.");
sessionRunning = false; sessionRunning = false;
} }
else else
{ {
logger.Info(">>>--- Session reversion was erroneous! ---<<<"); logger.Info("Session reversion was erroneous!");
messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error); messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error);
} }
} }

View file

@ -50,13 +50,13 @@ namespace SafeExamBrowser.Runtime
var text = new Text(logger); var text = new Text(logger);
var messageBox = new MessageBox(text); var messageBox = new MessageBox(text);
var uiFactory = new UserInterfaceFactory(text); var desktopFactory = new DesktopFactory(new ModuleLogger(logger, typeof(DesktopFactory)));
var desktop = new Desktop(new ModuleLogger(logger, typeof(Desktop))); var processFactory = new ProcessFactory(new ModuleLogger(logger, typeof(ProcessFactory)));
var processFactory = new ProcessFactory(desktop, new ModuleLogger(logger, typeof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger);
var resourceLoader = new ResourceLoader(); var resourceLoader = new ResourceLoader();
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, configuration, new HostObjectFactory(), new ModuleLogger(logger, typeof(RuntimeHost))); 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 serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, typeof(ServiceProxy)));
var uiFactory = new UserInterfaceFactory(text);
var bootstrapOperations = new Queue<IOperation>(); var bootstrapOperations = new Queue<IOperation>();
var sessionOperations = new Queue<IOperation>(); var sessionOperations = new Queue<IOperation>();
@ -68,7 +68,7 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost)); sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost));
sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text)); sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text));
sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS)); 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)); sessionOperations.Enqueue(new ClientOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS));
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);

View file

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic"
xmlns:s="clr-namespace:System;assembly=mscorlib" 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"> Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
<Grid> <Grid>
<Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5" Visibility="{Binding AnimatedBorderVisibility}"> <Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5" Visibility="{Binding AnimatedBorderVisibility}">
@ -44,7 +44,7 @@
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue" <ProgressBar x:Name="ProgressBar" Grid.Row="1" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue"
IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}" IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}"
Visibility="{Binding ProgressBarVisibility}" /> Visibility="{Binding ProgressBarVisibility}" />
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" FontSize="12" FontWeight="Bold" Foreground="Black" HorizontalAlignment="Center" <TextBlock x:Name="StatusTextBlock" Grid.Row="1" FontSize="12" FontWeight="Bold" HorizontalAlignment="Center"
Text="{Binding Status}" VerticalAlignment="Center" /> Text="{Binding Status}" VerticalAlignment="Center" />
<Border Grid.Row="2" BorderBrush="DodgerBlue" BorderThickness="0,0.5,0,0"> <Border Grid.Row="2" BorderBrush="DodgerBlue" BorderThickness="0,0.5,0,0">
<ScrollViewer x:Name="LogScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,10,0,0"> <ScrollViewer x:Name="LogScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,10,0,0">
@ -52,7 +52,7 @@
<s:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">5</s:Double> <s:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">5</s:Double>
<s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double> <s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double>
</ScrollViewer.Resources> </ScrollViewer.Resources>
<TextBox x:Name="LogTextBlock" Background="Transparent" BorderThickness="0" FontFamily="Courier New" FontSize="10" Foreground="Black" IsReadOnly="True" /> <TextBlock x:Name="LogTextBlock" Background="Transparent" FontFamily="Consolas" FontSize="10" />
</ScrollViewer> </ScrollViewer>
</Border> </Border>
</Grid> </Grid>

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.Windows; using System.Windows;
using System.Windows.Documents; using System.Windows.Documents;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -22,7 +21,6 @@ namespace SafeExamBrowser.UserInterface.Classic
{ {
private bool allowClose; private bool allowClose;
private AppConfig appConfig; private AppConfig appConfig;
private ILogContentFormatter formatter;
private IText text; private IText text;
private RuntimeWindowViewModel model; private RuntimeWindowViewModel model;
private WindowClosingEventHandler closing; private WindowClosingEventHandler closing;
@ -39,10 +37,9 @@ namespace SafeExamBrowser.UserInterface.Classic
remove { closing -= value; } remove { closing -= value; }
} }
public RuntimeWindow(AppConfig appConfig, ILogContentFormatter formatter, IText text) public RuntimeWindow(AppConfig appConfig, IText text)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.formatter = formatter;
this.text = text; this.text = text;
InitializeComponent(); InitializeComponent();
@ -77,7 +74,7 @@ namespace SafeExamBrowser.UserInterface.Classic
{ {
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
LogTextBlock.Text += formatter.Format(content) + Environment.NewLine; model.Notify(content);
LogScrollViewer.ScrollToEnd(); LogScrollViewer.ScrollToEnd();
}); });
} }
@ -137,7 +134,7 @@ namespace SafeExamBrowser.UserInterface.Classic
InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 10 }); InfoTextBlock.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 10 });
model = new RuntimeWindowViewModel(); model = new RuntimeWindowViewModel(LogTextBlock);
AnimatedBorder.DataContext = model; AnimatedBorder.DataContext = model;
ProgressBar.DataContext = model; ProgressBar.DataContext = model;
StatusTextBlock.DataContext = model; StatusTextBlock.DataContext = model;

View file

@ -116,7 +116,6 @@
</Compile> </Compile>
<Compile Include="UserInterfaceFactory.cs" /> <Compile Include="UserInterfaceFactory.cs" />
<Compile Include="Utilities\IconResourceLoader.cs" /> <Compile Include="Utilities\IconResourceLoader.cs" />
<Compile Include="Utilities\RuntimeWindowLogFormatter.cs" />
<Compile Include="Utilities\VisualExtensions.cs" /> <Compile Include="Utilities\VisualExtensions.cs" />
<Compile Include="Utilities\XamlIconResource.cs" /> <Compile Include="Utilities\XamlIconResource.cs" />
<Compile Include="ViewModels\DateTimeViewModel.cs" /> <Compile Include="ViewModels\DateTimeViewModel.cs" />

View file

@ -17,7 +17,6 @@ using SafeExamBrowser.Contracts.UserInterface.Browser;
using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.Contracts.UserInterface.Windows; using SafeExamBrowser.Contracts.UserInterface.Windows;
using SafeExamBrowser.UserInterface.Classic.Controls; using SafeExamBrowser.UserInterface.Classic.Controls;
using SafeExamBrowser.UserInterface.Classic.Utilities;
namespace SafeExamBrowser.UserInterface.Classic namespace SafeExamBrowser.UserInterface.Classic
{ {
@ -66,7 +65,6 @@ namespace SafeExamBrowser.UserInterface.Classic
}); });
logWindowThread.SetApartmentState(ApartmentState.STA); logWindowThread.SetApartmentState(ApartmentState.STA);
logWindowThread.Name = nameof(LogWindow);
logWindowThread.IsBackground = true; logWindowThread.IsBackground = true;
logWindowThread.Start(); logWindowThread.Start();
@ -96,7 +94,7 @@ namespace SafeExamBrowser.UserInterface.Classic
var windowReadyEvent = new AutoResetEvent(false); var windowReadyEvent = new AutoResetEvent(false);
var runtimeWindowThread = new Thread(() => var runtimeWindowThread = new Thread(() =>
{ {
runtimeWindow = new RuntimeWindow(appConfig, new RuntimeWindowLogFormatter(), text); runtimeWindow = new RuntimeWindow(appConfig, text);
runtimeWindow.Closed += (o, args) => runtimeWindow.Dispatcher.InvokeShutdown(); runtimeWindow.Closed += (o, args) => runtimeWindow.Dispatcher.InvokeShutdown();
windowReadyEvent.Set(); windowReadyEvent.Set();

View file

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

View file

@ -33,17 +33,16 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels
public void Notify(ILogContent content) public void Notify(ILogContent content)
{ {
if (content is ILogText text) switch (content)
{ {
AppendLogText(text); case ILogText text:
} AppendLogText(text);
else if (content is ILogMessage message) break;
{ case ILogMessage message:
AppendLogMessage(message); AppendLogMessage(message);
} break;
else default:
{ throw new NotImplementedException($"The log window is not yet implemented for log content of type {content.GetType()}!");
throw new NotImplementedException($"The default formatter is not yet implemented for log content of type {content.GetType()}!");
} }
scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd); scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd);
@ -85,6 +84,8 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels
{ {
switch (severity) switch (severity)
{ {
case LogLevel.Debug:
return Brushes.Gray;
case LogLevel.Error: case LogLevel.Error:
return Brushes.Red; return Brushes.Red;
case LogLevel.Warning: case LogLevel.Warning:

View file

@ -6,13 +6,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.UserInterface.Classic.ViewModels namespace SafeExamBrowser.UserInterface.Classic.ViewModels
{ {
internal class RuntimeWindowViewModel : ProgressIndicatorViewModel internal class RuntimeWindowViewModel : ProgressIndicatorViewModel
{ {
private Visibility animatedBorderVisibility, progressBarVisibility; private Visibility animatedBorderVisibility, progressBarVisibility;
private TextBlock textBlock;
public Visibility AnimatedBorderVisibility 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() public override void StartBusyIndication()
{ {
base.StartBusyIndication(); base.StartBusyIndication();
@ -53,5 +79,37 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels
AnimatedBorderVisibility = Visibility.Visible; 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;
}
}
} }
} }

View file

@ -61,7 +61,6 @@ namespace SafeExamBrowser.UserInterface.Windows10
}); });
logWindowThread.SetApartmentState(ApartmentState.STA); logWindowThread.SetApartmentState(ApartmentState.STA);
logWindowThread.Name = nameof(LogWindow);
logWindowThread.IsBackground = true; logWindowThread.IsBackground = true;
logWindowThread.Start(); logWindowThread.Start();

View file

@ -33,17 +33,16 @@ namespace SafeExamBrowser.UserInterface.Windows10.ViewModels
public void Notify(ILogContent content) public void Notify(ILogContent content)
{ {
if (content is ILogText text) switch (content)
{ {
AppendLogText(text); case ILogText text:
} AppendLogText(text);
else if (content is ILogMessage message) break;
{ case ILogMessage message:
AppendLogMessage(message); AppendLogMessage(message);
} break;
else default:
{ throw new NotImplementedException($"The log window is not yet implemented for log content of type {content.GetType()}!");
throw new NotImplementedException($"The default formatter is not yet implemented for log content of type {content.GetType()}!");
} }
scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd); scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd);
@ -85,6 +84,8 @@ namespace SafeExamBrowser.UserInterface.Windows10.ViewModels
{ {
switch (severity) switch (severity)
{ {
case LogLevel.Debug:
return Brushes.Gray;
case LogLevel.Error: case LogLevel.Error:
return Brushes.Red; return Brushes.Red;
case LogLevel.Warning: case LogLevel.Warning:

View file

@ -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
{
/// <remarks>
/// See https://docs.microsoft.com/de-de/windows/desktop/SecAuthZ/access-mask
/// </remarks>
[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)
}
}

View file

@ -39,6 +39,20 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// </summary> /// </summary>
internal const int NORMAL_PRIORITY_CLASS = 0x20; internal const int NORMAL_PRIORITY_CLASS = 0x20;
/// <summary>
/// Standard access rights required for a desktop.
///
/// See https://docs.microsoft.com/de-de/windows/desktop/SecAuthZ/standard-access-rights.
/// </summary>
internal const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
/// <summary>
/// The constant for the name of a user object.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683238(v=vs.85).aspx.
/// </summary>
internal const int UOI_NAME = 2;
/// <summary> /// <summary>
/// The callback function is not mapped into the address space of the process that generates the event. Because the hook function /// 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 /// is called across process boundaries, the system must queue events. Although this method is asynchronous, events are guaranteed

View file

@ -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
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682612(v=vs.85).aspx
/// </remarks>
internal delegate bool EnumDesktopDelegate(string lpszDesktop, IntPtr lParam);
}

View file

@ -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
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633498(v=vs.85).aspx
/// </remarks>
internal delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);
}

View file

@ -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
{
/// <remarks>
/// See https://docs.microsoft.com/de-de/windows/desktop/api/winuser/nc-winuser-wineventproc
/// </remarks>
internal delegate void EventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
}

View file

@ -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
{
/// <remarks>
/// See https://docs.microsoft.com/de-de/windows/desktop/api/winuser/nc-winuser-hookproc
/// </remarks>
internal delegate IntPtr HookDelegate(int code, IntPtr wParam, IntPtr lParam);
}

View file

@ -6,21 +6,42 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * 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; using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.WindowsApi namespace SafeExamBrowser.WindowsApi
{ {
public class Desktop : IDesktop public class Desktop : IDesktop
{ {
private ILogger logger; public IntPtr Handle { get; private set; }
public string Name { get; private set; }
// TODO: Implement desktop functionality! public Desktop(IntPtr handle, string name)
public string CurrentName => "Default";
public Desktop(ILogger logger)
{ {
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());
}
} }
} }
} }

View file

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

View file

@ -17,20 +17,23 @@ namespace SafeExamBrowser.WindowsApi
/// </summary> /// </summary>
internal class Kernel32 internal class Kernel32
{ {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] [DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CreateProcess( internal static extern bool CreateProcess(
string lpApplicationName, string lpApplicationName,
string lpCommandLine, string lpCommandLine,
IntPtr lpProcessAttributes, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, IntPtr lpThreadAttributes,
bool bInheritHandles, bool bInheritHandles,
uint dwCreationFlags, int dwCreationFlags,
IntPtr lpEnvironment, IntPtr lpEnvironment,
string lpCurrentDirectory, string lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo, ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation); 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); internal static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]

View file

@ -10,6 +10,7 @@ using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Types; using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi.Monitoring namespace SafeExamBrowser.WindowsApi.Monitoring
@ -23,7 +24,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
private const int DELETE = 46; private const int DELETE = 46;
private bool altPressed, ctrlPressed; private bool altPressed, ctrlPressed;
private HookProc hookProc; private HookDelegate hookProc;
internal IntPtr Handle { get; private set; } internal IntPtr Handle { get; private set; }
internal IKeyboardInterceptor Interceptor { get; private set; } internal IKeyboardInterceptor Interceptor { get; private set; }
@ -40,7 +41,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
// IMORTANT: // IMORTANT:
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // 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 <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! // Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
hookProc = new HookProc(LowLevelKeyboardProc); hookProc = new HookDelegate(LowLevelKeyboardProc);
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, module, 0); Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, module, 0);
} }

View file

@ -10,13 +10,14 @@ using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Types; using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi.Monitoring namespace SafeExamBrowser.WindowsApi.Monitoring
{ {
internal class MouseHook internal class MouseHook
{ {
private HookProc hookProc; private HookDelegate hookProc;
internal IntPtr Handle { get; private set; } internal IntPtr Handle { get; private set; }
internal IMouseInterceptor Interceptor { get; private set; } internal IMouseInterceptor Interceptor { get; private set; }
@ -33,7 +34,7 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
// IMORTANT: // IMORTANT:
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // 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 <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! // Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
hookProc = new HookProc(LowLevelMouseProc); hookProc = new HookDelegate(LowLevelMouseProc);
Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, module, 0); Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, module, 0);
} }

View file

@ -16,6 +16,7 @@ using System.Text;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Monitoring; using SafeExamBrowser.WindowsApi.Monitoring;
using SafeExamBrowser.WindowsApi.Types; using SafeExamBrowser.WindowsApi.Types;
@ -23,7 +24,7 @@ namespace SafeExamBrowser.WindowsApi
{ {
public class NativeMethods : INativeMethods public class NativeMethods : INativeMethods
{ {
private ConcurrentDictionary<IntPtr, EventProc> EventDelegates = new ConcurrentDictionary<IntPtr, EventProc>(); private ConcurrentDictionary<IntPtr, EventDelegate> EventDelegates = new ConcurrentDictionary<IntPtr, EventDelegate>();
private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>(); private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>();
private ConcurrentDictionary<IntPtr, MouseHook> MouseHooks = new ConcurrentDictionary<IntPtr, MouseHook>(); private ConcurrentDictionary<IntPtr, MouseHook> MouseHooks = new ConcurrentDictionary<IntPtr, MouseHook>();
@ -91,7 +92,7 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error()); throw new Win32Exception(Marshal.GetLastWin32Error());
} }
EventDelegates.TryRemove(handle, out EventProc d); EventDelegates.TryRemove(handle, out EventDelegate d);
} }
public void EmptyClipboard() public void EmptyClipboard()
@ -111,7 +112,8 @@ namespace SafeExamBrowser.WindowsApi
public IEnumerable<IntPtr> GetOpenWindows() public IEnumerable<IntPtr> GetOpenWindows()
{ {
var windows = new List<IntPtr>(); var windows = new List<IntPtr>();
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) if (hWnd != GetShellWindowHandle() && User32.IsWindowVisible(hWnd) && User32.GetWindowTextLength(hWnd) > 0)
{ {
@ -119,7 +121,9 @@ namespace SafeExamBrowser.WindowsApi
} }
return true; return true;
}, IntPtr.Zero); }
var success = User32.EnumWindows(EnumWindows, IntPtr.Zero);
if (!success) if (!success)
{ {
@ -245,34 +249,34 @@ namespace SafeExamBrowser.WindowsApi
public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback) public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> 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); 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: // IMORTANT:
// Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // 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 <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! // Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
EventDelegates[handle] = eventProc; EventDelegates[handle] = evenDelegate;
return handle; return handle;
} }
public IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback) public IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> 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); 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: // IMORTANT:
// Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // 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 <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! // Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
EventDelegates[handle] = eventProc; EventDelegates[handle] = eventDelegate;
return handle; return handle;
} }

View file

@ -8,6 +8,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi;
@ -18,12 +19,12 @@ namespace SafeExamBrowser.WindowsApi
{ {
public class ProcessFactory : IProcessFactory public class ProcessFactory : IProcessFactory
{ {
private IDesktop desktop;
private ILogger logger; 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; this.logger = logger;
} }
@ -34,13 +35,20 @@ namespace SafeExamBrowser.WindowsApi
var startupInfo = new STARTUPINFO(); var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo); startupInfo.cb = Marshal.SizeOf(startupInfo);
// TODO: Specify target desktop! startupInfo.lpDesktop = StartupDesktop?.Name;
//startupInfo.lpDesktop = desktop.CurrentName;
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); 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()); throw new Win32Exception(Marshal.GetLastWin32Error());
} }

View file

@ -53,10 +53,16 @@
<ItemGroup> <ItemGroup>
<Compile Include="Constants\Constant.cs" /> <Compile Include="Constants\Constant.cs" />
<Compile Include="Constants\HookType.cs" /> <Compile Include="Constants\HookType.cs" />
<Compile Include="Delegates\EnumDesktopDelegate.cs" />
<Compile Include="Delegates\EnumWindowsDelegate.cs" />
<Compile Include="Delegates\EventDelegate.cs" />
<Compile Include="Delegates\HookDelegate.cs" />
<Compile Include="Desktop.cs" /> <Compile Include="Desktop.cs" />
<Compile Include="DesktopFactory.cs" />
<Compile Include="Monitoring\MouseHook.cs" /> <Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Process.cs" /> <Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" /> <Compile Include="ProcessFactory.cs" />
<Compile Include="Constants\ACCESS_MASK.cs" />
<Compile Include="Types\Bounds.cs" /> <Compile Include="Types\Bounds.cs" />
<Compile Include="Types\EXECUTION_STATE.cs" /> <Compile Include="Types\EXECUTION_STATE.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCT.cs" /> <Compile Include="Types\KBDLLHOOKSTRUCT.cs" />

View file

@ -10,14 +10,11 @@ using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Types; using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi 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);
/// <summary> /// <summary>
/// Provides access to the native Windows API exposed by <c>user32.dll</c>. /// Provides access to the native Windows API exposed by <c>user32.dll</c>.
/// </summary> /// </summary>
@ -30,18 +27,37 @@ namespace SafeExamBrowser.WindowsApi
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseClipboard(); 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)] [DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool EmptyClipboard(); 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)] [DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool EnumWindows(EnumWindowsDelegate enumProc, IntPtr lParam); 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); 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); internal static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", SetLastError = true)] [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); internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)] [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)] [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)] [DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 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)] [DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref RECT pvParam, SPIF fWinIni); 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)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SystemParametersInfo(SPI uiAction, int uiParam, string pvParam, SPIF fWinIni); internal static extern bool SystemParametersInfo(SPI uiAction, int uiParam, string pvParam, SPIF fWinIni);