SEBWIN-220: Implemented kiosk mode "Create New Desktop".
This commit is contained in:
parent
02d69005c0
commit
729133ac78
35 changed files with 533 additions and 152 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<AssemblyInformationalVersionAttribute>().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";
|
||||
|
|
|
@ -181,6 +181,7 @@
|
|||
<Compile Include="WindowsApi\Events\ProcessTerminatedEventHandler.cs" />
|
||||
<Compile Include="WindowsApi\IBounds.cs" />
|
||||
<Compile Include="WindowsApi\IDesktop.cs" />
|
||||
<Compile Include="WindowsApi\IDesktopFactory.cs" />
|
||||
<Compile Include="WindowsApi\INativeMethods.cs" />
|
||||
<Compile Include="WindowsApi\IProcess.cs" />
|
||||
<Compile Include="WindowsApi\IProcessFactory.cs" />
|
||||
|
|
|
@ -6,16 +6,35 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.WindowsApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the API to retrieve information about desktops and perform desktop-related operations.
|
||||
/// Represents a desktop and defines its functionality.
|
||||
/// </summary>
|
||||
public interface IDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the name of the currently active desktop.
|
||||
/// The handle identifying the desktop.
|
||||
/// </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();
|
||||
}
|
||||
}
|
||||
|
|
29
SafeExamBrowser.Contracts/WindowsApi/IDesktopFactory.cs
Normal file
29
SafeExamBrowser.Contracts/WindowsApi/IDesktopFactory.cs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -14,7 +14,13 @@ namespace SafeExamBrowser.Contracts.WindowsApi
|
|||
public interface IProcessFactory
|
||||
{
|
||||
/// <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>
|
||||
/// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception>
|
||||
IProcess StartNew(string path, params string[] args);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<IOperation>();
|
||||
var sessionOperations = new Queue<IOperation>();
|
||||
|
@ -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);
|
||||
|
|
|
@ -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">
|
||||
<Grid>
|
||||
<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"
|
||||
IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}"
|
||||
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" />
|
||||
<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">
|
||||
|
@ -52,7 +52,7 @@
|
|||
<s:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">5</s:Double>
|
||||
<s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double>
|
||||
</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>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -116,7 +116,6 @@
|
|||
</Compile>
|
||||
<Compile Include="UserInterfaceFactory.cs" />
|
||||
<Compile Include="Utilities\IconResourceLoader.cs" />
|
||||
<Compile Include="Utilities\RuntimeWindowLogFormatter.cs" />
|
||||
<Compile Include="Utilities\VisualExtensions.cs" />
|
||||
<Compile Include="Utilities\XamlIconResource.cs" />
|
||||
<Compile Include="ViewModels\DateTimeViewModel.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();
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ namespace SafeExamBrowser.UserInterface.Windows10
|
|||
});
|
||||
|
||||
logWindowThread.SetApartmentState(ApartmentState.STA);
|
||||
logWindowThread.Name = nameof(LogWindow);
|
||||
logWindowThread.IsBackground = true;
|
||||
logWindowThread.Start();
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
34
SafeExamBrowser.WindowsApi/Constants/ACCESS_MASK.cs
Normal file
34
SafeExamBrowser.WindowsApi/Constants/ACCESS_MASK.cs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -39,6 +39,20 @@ namespace SafeExamBrowser.WindowsApi.Constants
|
|||
/// </summary>
|
||||
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>
|
||||
/// 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
|
||||
|
|
17
SafeExamBrowser.WindowsApi/Delegates/EnumDesktopDelegate.cs
Normal file
17
SafeExamBrowser.WindowsApi/Delegates/EnumDesktopDelegate.cs
Normal 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);
|
||||
}
|
17
SafeExamBrowser.WindowsApi/Delegates/EnumWindowsDelegate.cs
Normal file
17
SafeExamBrowser.WindowsApi/Delegates/EnumWindowsDelegate.cs
Normal 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);
|
||||
}
|
17
SafeExamBrowser.WindowsApi/Delegates/EventDelegate.cs
Normal file
17
SafeExamBrowser.WindowsApi/Delegates/EventDelegate.cs
Normal 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);
|
||||
}
|
17
SafeExamBrowser.WindowsApi/Delegates/HookDelegate.cs
Normal file
17
SafeExamBrowser.WindowsApi/Delegates/HookDelegate.cs
Normal 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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
83
SafeExamBrowser.WindowsApi/DesktopFactory.cs
Normal file
83
SafeExamBrowser.WindowsApi/DesktopFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,20 +17,23 @@ namespace SafeExamBrowser.WindowsApi
|
|||
/// </summary>
|
||||
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)]
|
||||
|
|
|
@ -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 <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);
|
||||
}
|
||||
|
|
|
@ -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 <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);
|
||||
}
|
||||
|
|
|
@ -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<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, MouseHook> MouseHooks = new ConcurrentDictionary<IntPtr, MouseHook>();
|
||||
|
||||
|
@ -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<IntPtr> GetOpenWindows()
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -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<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);
|
||||
}
|
||||
|
||||
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 <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
EventDelegates[handle] = eventProc;
|
||||
EventDelegates[handle] = evenDelegate;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
|
||||
EventDelegates[handle] = eventProc;
|
||||
EventDelegates[handle] = eventDelegate;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -53,10 +53,16 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="Constants\Constant.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="DesktopFactory.cs" />
|
||||
<Compile Include="Monitoring\MouseHook.cs" />
|
||||
<Compile Include="Process.cs" />
|
||||
<Compile Include="ProcessFactory.cs" />
|
||||
<Compile Include="Constants\ACCESS_MASK.cs" />
|
||||
<Compile Include="Types\Bounds.cs" />
|
||||
<Compile Include="Types\EXECUTION_STATE.cs" />
|
||||
<Compile Include="Types\KBDLLHOOKSTRUCT.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);
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the native Windows API exposed by <c>user32.dll</c>.
|
||||
/// </summary>
|
||||
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue