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

View file

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

View file

@ -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" />

View file

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

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
{
/// <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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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" />

View file

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

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)
{
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:

View file

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

View file

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

View file

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

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

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/.
*/
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());
}
}
}
}

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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" />

View file

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