Created stub for process monitoring (explorer termination / restart) and enhanced startup and shutdown procedures.

This commit is contained in:
Damian Büchel 2017-07-19 14:43:54 +02:00
parent 72e8dcbb54
commit a35fe0811f
25 changed files with 580 additions and 85 deletions

View file

@ -21,7 +21,15 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_StartupErrorTitle, MessageBox_StartupErrorTitle,
Notification_AboutTooltip, Notification_AboutTooltip,
SplashScreen_InitializeBrowser, SplashScreen_InitializeBrowser,
SplashScreen_InitializeProcessMonitoring,
SplashScreen_InitializeTaskbar,
SplashScreen_InitializeWorkArea,
SplashScreen_RestoreWorkArea,
SplashScreen_ShutdownProcedure,
SplashScreen_StartupProcedure, SplashScreen_StartupProcedure,
SplashScreen_StopProcessMonitoring,
SplashScreen_WaitExplorerStartup,
SplashScreen_WaitExplorerTermination,
Version Version
} }
} }

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2017 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.Monitoring
{
public interface IKeyboardInterceptor
{
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2017 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.Monitoring
{
public interface IMouseInterceptor
{
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2017 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.Monitoring
{
public interface IProcessMonitor
{
/// <summary>
/// Starts a new instance of the Windows explorer shell.
/// </summary>
void StartExplorerShell();
/// <summary>
/// Starts monitoring the Windows explorer, i.e. any newly created instances of
/// <c>explorer.exe</c> will automatically be terminated.
/// </summary>
void StartMonitoringExplorer();
/// <summary>
/// Stops monitoring the Windows explorer.
/// </summary>
void StopMonitoringExplorer();
/// <summary>
/// Terminates the Windows explorer shell, i.e. the taskbar.
/// </summary>
void CloseExplorerShell();
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2017 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.Monitoring
{
public interface IWindowMonitor
{
}
}

View file

@ -58,6 +58,10 @@
<Compile Include="Logging\ILogText.cs" /> <Compile Include="Logging\ILogText.cs" />
<Compile Include="Logging\IThreadInfo.cs" /> <Compile Include="Logging\IThreadInfo.cs" />
<Compile Include="Logging\LogLevel.cs" /> <Compile Include="Logging\LogLevel.cs" />
<Compile Include="Monitoring\IKeyboardInterceptor.cs" />
<Compile Include="Monitoring\IMouseInterceptor.cs" />
<Compile Include="Monitoring\IProcessMonitor.cs" />
<Compile Include="Monitoring\IWindowMonitor.cs" />
<Compile Include="UserInterface\IMessageBox.cs" /> <Compile Include="UserInterface\IMessageBox.cs" />
<Compile Include="UserInterface\ITaskbarNotification.cs" /> <Compile Include="UserInterface\ITaskbarNotification.cs" />
<Compile Include="UserInterface\ISplashScreen.cs" /> <Compile Include="UserInterface\ISplashScreen.cs" />

View file

@ -13,9 +13,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
public interface ISplashScreen public interface ISplashScreen
{ {
/// <summary> /// <summary>
/// Closes the splash screen. /// Closes the splash screen on its own thread.
/// </summary> /// </summary>
void Close(); void InvokeClose();
/// <summary>
/// Shows the splash screen on its own thread.
/// </summary>
void InvokeShow();
/// <summary> /// <summary>
/// Set the maximum of the splash screen's progress bar. /// Set the maximum of the splash screen's progress bar.
@ -23,9 +28,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
void SetMaxProgress(int max); void SetMaxProgress(int max);
/// <summary> /// <summary>
/// Shows the splash screen to the user. /// Starts an animation indicating the user that something is going on.
/// </summary> /// </summary>
void Show(); void StartBusyIndication();
/// <summary>
/// Stops the busy animation, if it was running.
/// </summary>
void StopBusyIndication();
/// <summary> /// <summary>
/// Updates the progress bar of the splash screen according to the specified amount. /// Updates the progress bar of the splash screen according to the specified amount.

View file

@ -30,10 +30,5 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// Sets the size of the taskbar. /// Sets the size of the taskbar.
/// </summary> /// </summary>
void SetSize(int width, int height); void SetSize(int width, int height);
/// <summary>
/// Displays the taskbar on the screen.
/// </summary>
void Show();
} }
} }

View file

@ -7,6 +7,7 @@
*/ */
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
namespace SafeExamBrowser.Contracts.UserInterface namespace SafeExamBrowser.Contracts.UserInterface
{ {
@ -17,6 +18,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary> /// </summary>
ITaskbarButton CreateApplicationButton(IApplicationInfo info); ITaskbarButton CreateApplicationButton(IApplicationInfo info);
/// <summary>
/// Creates a new splash screen which runs on its own thread.
/// </summary>
ISplashScreen CreateSplashScreen(ISettings settings, IText text);
/// <summary> /// <summary>
/// Creates a taskbar notification, initialized with the given notification information. /// Creates a taskbar notification, initialized with the given notification information.
/// </summary> /// </summary>

View file

@ -7,9 +7,14 @@
*/ */
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour namespace SafeExamBrowser.Core.Behaviour
@ -18,23 +23,52 @@ namespace SafeExamBrowser.Core.Behaviour
{ {
private ILogger logger; private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private IProcessMonitor processMonitor;
private ISettings settings;
private ISplashScreen splashScreen;
private IText text; private IText text;
private IUiElementFactory uiFactory;
public ShutdownController(ILogger logger, IMessageBox messageBox, IText text) private IEnumerable<Action> ShutdownOperations
{
get
{
yield return StopProcessMonitoring;
yield return RestoreWorkArea;
yield return FinalizeApplicationLog;
}
}
public ShutdownController(
ILogger logger,
IMessageBox messageBox,
IProcessMonitor processMonitor,
ISettings settings,
IText text,
IUiElementFactory uiFactory)
{ {
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
this.processMonitor = processMonitor;
this.settings = settings;
this.text = text; this.text = text;
this.uiFactory = uiFactory;
} }
public void FinalizeApplication() public void FinalizeApplication()
{ {
try try
{ {
// TODO: InitializeSplashScreen();
// - Gather TODOs!
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); foreach (var operation in ShutdownOperations)
{
operation();
splashScreen.UpdateProgress();
// TODO: Remove!
Thread.Sleep(250);
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -42,5 +76,41 @@ namespace SafeExamBrowser.Core.Behaviour
messageBox.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error); messageBox.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error);
} }
} }
private void InitializeSplashScreen()
{
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(ShutdownOperations.Count());
splashScreen.UpdateText(Key.SplashScreen_ShutdownProcedure);
splashScreen.InvokeShow();
}
private void StopProcessMonitoring()
{
logger.Info("Stopping process monitoring.");
splashScreen.UpdateText(Key.SplashScreen_StopProcessMonitoring);
// TODO
processMonitor.StopMonitoringExplorer();
}
private void RestoreWorkArea()
{
logger.Info("Restoring work area.");
splashScreen.UpdateText(Key.SplashScreen_RestoreWorkArea);
// TODO
splashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup);
splashScreen.StartBusyIndication();
processMonitor.StartExplorerShell();
splashScreen.StopBusyIndication();
}
private void FinalizeApplicationLog()
{
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
} }
} }

View file

@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour namespace SafeExamBrowser.Core.Behaviour
@ -25,6 +26,7 @@ namespace SafeExamBrowser.Core.Behaviour
private ILogger logger; private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private INotificationInfo aboutInfo; private INotificationInfo aboutInfo;
private IProcessMonitor processMonitor;
private ISettings settings; private ISettings settings;
private ISplashScreen splashScreen; private ISplashScreen splashScreen;
private ITaskbar taskbar; private ITaskbar taskbar;
@ -53,8 +55,8 @@ namespace SafeExamBrowser.Core.Behaviour
ILogger logger, ILogger logger,
IMessageBox messageBox, IMessageBox messageBox,
INotificationInfo aboutInfo, INotificationInfo aboutInfo,
IProcessMonitor processMonitor,
ISettings settings, ISettings settings,
ISplashScreen splashScreen,
ITaskbar taskbar, ITaskbar taskbar,
IText text, IText text,
IUiElementFactory uiFactory) IUiElementFactory uiFactory)
@ -64,8 +66,8 @@ namespace SafeExamBrowser.Core.Behaviour
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
this.aboutInfo = aboutInfo; this.aboutInfo = aboutInfo;
this.processMonitor = processMonitor;
this.settings = settings; this.settings = settings;
this.splashScreen = splashScreen;
this.taskbar = taskbar; this.taskbar = taskbar;
this.text = text; this.text = text;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
@ -112,62 +114,63 @@ namespace SafeExamBrowser.Core.Behaviour
private void InitializeSplashScreen() private void InitializeSplashScreen()
{ {
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(StartupOperations.Count()); splashScreen.SetMaxProgress(StartupOperations.Count());
splashScreen.UpdateText(Key.SplashScreen_StartupProcedure); splashScreen.UpdateText(Key.SplashScreen_StartupProcedure);
splashScreen.InvokeShow();
} }
private void HandleCommandLineArguments() private void HandleCommandLineArguments()
{ {
logger.Info("Parsing command line arguments.");
// TODO // TODO
} }
private void DetectOperatingSystem() private void DetectOperatingSystem()
{ {
logger.Info("Detecting operating system.");
// TODO // TODO
} }
private void EstablishWcfServiceConnection() private void EstablishWcfServiceConnection()
{ {
logger.Info("Establishing connection to WCF service.");
// TODO // TODO
} }
private void DeactivateWindowsFeatures() private void DeactivateWindowsFeatures()
{ {
logger.Info("Deactivating Windows Update.");
// TODO
logger.Info("Disabling lock screen options.");
// TODO // TODO
} }
private void InitializeProcessMonitoring() private void InitializeProcessMonitoring()
{ {
logger.Info("Initializing process monitoring."); logger.Info("Initializing process monitoring.");
splashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring);
// TODO // TODO
processMonitor.StartMonitoringExplorer();
} }
private void InitializeWorkArea() private void InitializeWorkArea()
{ {
logger.Info("Initializing work area."); logger.Info("Initializing work area.");
splashScreen.UpdateText(Key.SplashScreen_InitializeWorkArea);
// TODO // TODO
// - Killing explorer.exe
// - Minimizing all open windows // - Minimizing all open windows
// - Emptying clipboard // - Emptying clipboard
splashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination);
splashScreen.StartBusyIndication();
processMonitor.CloseExplorerShell();
splashScreen.StopBusyIndication();
} }
private void InitializeTaskbar() private void InitializeTaskbar()
{ {
logger.Info("Initializing taskbar."); logger.Info("Initializing taskbar.");
splashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar);
// TODO
var aboutNotification = uiFactory.CreateNotification(aboutInfo); var aboutNotification = uiFactory.CreateNotification(aboutInfo);
@ -179,6 +182,8 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("Initializing browser."); logger.Info("Initializing browser.");
splashScreen.UpdateText(Key.SplashScreen_InitializeBrowser); splashScreen.UpdateText(Key.SplashScreen_InitializeBrowser);
// TODO
var browserButton = uiFactory.CreateApplicationButton(browserInfo); var browserButton = uiFactory.CreateApplicationButton(browserInfo);
browserController.RegisterApplicationButton(browserButton); browserController.RegisterApplicationButton(browserButton);
@ -188,6 +193,7 @@ namespace SafeExamBrowser.Core.Behaviour
private void FinishInitialization() private void FinishInitialization()
{ {
logger.Info("Application successfully initialized!"); logger.Info("Application successfully initialized!");
splashScreen.InvokeClose();
} }
} }
} }

View file

@ -5,7 +5,15 @@
<MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError> <MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError>
<MessageBox_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle> <MessageBox_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle>
<Notification_AboutTooltip>About Safe Exam Browser</Notification_AboutTooltip> <Notification_AboutTooltip>About Safe Exam Browser</Notification_AboutTooltip>
<SplashScreen_InitializeBrowser>Initializing browser.</SplashScreen_InitializeBrowser> <SplashScreen_InitializeBrowser>Initializing browser</SplashScreen_InitializeBrowser>
<SplashScreen_StartupProcedure>Initiating startup procedure.</SplashScreen_StartupProcedure> <SplashScreen_InitializeProcessMonitoring>Initializing process monitoring</SplashScreen_InitializeProcessMonitoring>
<SplashScreen_InitializeTaskbar>Initializing taskbar</SplashScreen_InitializeTaskbar>
<SplashScreen_InitializeWorkArea>Initializing work area</SplashScreen_InitializeWorkArea>
<SplashScreen_RestoreWorkArea>Restoring work area</SplashScreen_RestoreWorkArea>
<SplashScreen_ShutdownProcedure>Initiating shutdown procedure</SplashScreen_ShutdownProcedure>
<SplashScreen_StartupProcedure>Initiating startup procedure</SplashScreen_StartupProcedure>
<SplashScreen_StopProcessMonitoring>Stopping process monitoring</SplashScreen_StopProcessMonitoring>
<SplashScreen_WaitExplorerStartup>Waiting for Windows explorer to start up</SplashScreen_WaitExplorerStartup>
<SplashScreen_WaitExplorerTermination>Waiting for Windows explorer to shut down</SplashScreen_WaitExplorerTermination>
<Version>Version</Version> <Version>Version</Version>
</Text> </Text>

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2017 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
namespace SafeExamBrowser.Monitoring.Processes
{
public class ProcessMonitor : IProcessMonitor
{
private ILogger logger;
public ProcessMonitor(ILogger logger)
{
this.logger = logger;
}
public void StartExplorerShell()
{
var process = new Process();
var explorerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "explorer.exe");
Log("Restarting explorer shell...");
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = explorerPath;
process.Start();
while (User32.GetShellWindowHandle() == IntPtr.Zero)
{
Thread.Sleep(20);
}
process.Refresh();
Log($"Explorer shell successfully started with PID = {process.Id}.");
process.Close();
}
public void StartMonitoringExplorer()
{
// TODO
}
public void StopMonitoringExplorer()
{
// TODO
}
public void CloseExplorerShell()
{
var processId = User32.GetShellProcessId();
var explorerProcesses = Process.GetProcessesByName("explorer");
var shellProcess = explorerProcesses.FirstOrDefault(p => p.Id == processId);
if (shellProcess != null)
{
Log($"Found explorer shell processes with PID = {processId}. Sending close message...");
User32.PostCloseMessageToShell();
while (!shellProcess.HasExited)
{
shellProcess.Refresh();
Thread.Sleep(20);
}
Log($"Successfully terminated explorer shell process with PID = {processId}.");
}
else
{
Log("The explorer shell seems to already be terminated. Skipping this step...");
}
}
private void Log(string message)
{
logger.Info($"[{nameof(ProcessMonitor)}] {message}");
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.Monitoring")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SafeExamBrowser.Monitoring")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ef563531-4eb5-44b9-a5ec-d6d6f204469b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Monitoring</RootNamespace>
<AssemblyName>SafeExamBrowser.Monitoring</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Processes\ProcessMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="User32.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Contracts\SafeExamBrowser.Contracts.csproj">
<Project>{47da5933-bef8-4729-94e6-abde2db12262}</Project>
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Keyboard\" />
<Folder Include="Mouse\" />
<Folder Include="Windows\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2017 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;
namespace SafeExamBrowser.Monitoring
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>user32.dll</c>.
/// </summary>
internal static class User32
{
internal static IntPtr GetShellWindowHandle()
{
return FindWindow("Shell_TrayWnd", null);
}
internal static uint GetShellProcessId()
{
var handle = FindWindow("Shell_TrayWnd", null);
var threadId = GetWindowThreadProcessId(handle, out uint processId);
return processId;
}
/// <remarks>
/// The close message <c>0x5B4</c> posted to the shell is undocumented and not officially supported:
/// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965
/// </remarks>
internal static void PostCloseMessageToShell()
{
var taskbarHandle = FindWindow("Shell_TrayWnd", null);
var success = PostMessage(taskbarHandle, 0x5B4, IntPtr.Zero, IntPtr.Zero);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
}
}

View file

@ -58,7 +58,9 @@ namespace SafeExamBrowser.UserInterface.Controls
Button.ToolTip = info.Tooltip; Button.ToolTip = info.Tooltip;
Button.Content = IconResourceLoader.Load(info.IconResource); Button.Content = IconResourceLoader.Load(info.IconResource);
Button.MouseEnter += (o, args) => InstancePopup.IsOpen = instances.Count > 1;
Button.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || ActiveBar.IsMouseOver; Button.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || ActiveBar.IsMouseOver;
ActiveBar.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || Button.IsMouseOver;
InstancePopup.MouseLeave += (o, args) => InstancePopup.IsOpen = false; InstancePopup.MouseLeave += (o, args) => InstancePopup.IsOpen = false;
InstancePopup.Opened += (o, args) => ActiveBar.Width = Double.NaN; InstancePopup.Opened += (o, args) => ActiveBar.Width = Double.NaN;
InstancePopup.Closed += (o, args) => ActiveBar.Width = 40; InstancePopup.Closed += (o, args) => ActiveBar.Width = 40;

View file

@ -20,7 +20,7 @@ namespace SafeExamBrowser.UserInterface.Controls
private void Button_Click(object sender, RoutedEventArgs e) private void Button_Click(object sender, RoutedEventArgs e)
{ {
Application.Current.Shutdown(); Application.Current.MainWindow.Close();
} }
} }
} }

View file

@ -30,11 +30,31 @@ namespace SafeExamBrowser.UserInterface
InitializeSplashScreen(); InitializeSplashScreen();
} }
public void InvokeClose()
{
Dispatcher.Invoke(Close);
}
public void InvokeShow()
{
Dispatcher.Invoke(Show);
}
public void SetMaxProgress(int max) public void SetMaxProgress(int max)
{ {
model.MaxProgress = max; model.MaxProgress = max;
} }
public void StartBusyIndication()
{
model.StartBusyIndication();
}
public void StopBusyIndication()
{
model.StopBusyIndication();
}
public void UpdateProgress(int amount = 1) public void UpdateProgress(int amount = 1)
{ {
model.CurrentProgress += amount; model.CurrentProgress += amount;

View file

@ -6,7 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System.Threading;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.Controls; using SafeExamBrowser.UserInterface.Controls;
@ -19,6 +21,31 @@ namespace SafeExamBrowser.UserInterface
return new ApplicationButton(info); return new ApplicationButton(info);
} }
public ISplashScreen CreateSplashScreen(ISettings settings, IText text)
{
SplashScreen splashScreen = null;
var splashReadyEvent = new AutoResetEvent(false);
var splashScreenThread = new Thread(() =>
{
splashScreen = new SplashScreen(settings, text);
splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown();
splashScreen.Show();
splashReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run();
});
splashScreenThread.SetApartmentState(ApartmentState.STA);
splashScreenThread.Name = "Splash Screen Thread";
splashScreenThread.IsBackground = true;
splashScreenThread.Start();
splashReadyEvent.WaitOne();
return splashScreen;
}
public ITaskbarNotification CreateNotification(INotificationInfo info) public ITaskbarNotification CreateNotification(INotificationInfo info)
{ {
return new NotificationIcon(info); return new NotificationIcon(info);

View file

@ -7,6 +7,7 @@
*/ */
using System.ComponentModel; using System.ComponentModel;
using System.Timers;
namespace SafeExamBrowser.UserInterface.ViewModels namespace SafeExamBrowser.UserInterface.ViewModels
{ {
@ -15,6 +16,7 @@ namespace SafeExamBrowser.UserInterface.ViewModels
private int currentProgress; private int currentProgress;
private int maxProgress; private int maxProgress;
private string status; private string status;
private Timer busyTimer;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
@ -56,5 +58,41 @@ namespace SafeExamBrowser.UserInterface.ViewModels
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
} }
} }
public void StartBusyIndication()
{
StopBusyIndication();
busyTimer = new Timer
{
AutoReset = true,
Interval = 750
};
busyTimer.Elapsed += BusyTimer_Elapsed;
busyTimer.Start();
}
public void StopBusyIndication()
{
busyTimer?.Stop();
busyTimer?.Close();
}
private void BusyTimer_Elapsed(object sender, ElapsedEventArgs e)
{
var next = Status ?? string.Empty;
if (next.EndsWith("..."))
{
next = Status.Substring(0, Status.Length - 3);
}
else
{
next += ".";
}
Status = next;
}
} }
} }

View file

@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Contracts",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Browser", "SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj", "{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Browser", "SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj", "{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Monitoring", "SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj", "{EF563531-4EB5-44B9-A5EC-D6D6F204469B}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -51,6 +53,10 @@ Global
{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Debug|Any CPU.Build.0 = Debug|Any CPU {04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Release|Any CPU.ActiveCfg = Release|Any CPU {04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Release|Any CPU.Build.0 = Release|Any CPU {04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}.Release|Any CPU.Build.0 = Release|Any CPU
{EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -26,7 +26,11 @@ namespace SafeExamBrowser
} }
catch (Exception e) catch (Exception e)
{ {
MessageBox.Show(e.Message + "\n\n" + e.StackTrace, "Fatal Error"); MessageBox.Show(e.Message + "\n\n" + e.StackTrace, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
mutex.Close();
} }
} }
@ -38,7 +42,7 @@ namespace SafeExamBrowser
} }
else else
{ {
MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed"); MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed", MessageBoxButton.OK, MessageBoxImage.Information);
} }
} }
@ -51,43 +55,6 @@ namespace SafeExamBrowser
{ {
base.OnStartup(e); base.OnStartup(e);
ShowSplashScreen();
InitializeApplication();
}
protected override void OnExit(ExitEventArgs e)
{
instances.ShutdownController.FinalizeApplication();
base.OnExit(e);
}
private void ShowSplashScreen()
{
instances.BuildModulesRequiredBySplashScreen();
var splashReadyEvent = new AutoResetEvent(false);
var splashScreenThread = new Thread(() =>
{
instances.SplashScreen = new UserInterface.SplashScreen(instances.Settings, instances.Text);
instances.SplashScreen.Closed += (o, args) => instances.SplashScreen.Dispatcher.InvokeShutdown();
instances.SplashScreen.Show();
splashReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run();
});
splashScreenThread.SetApartmentState(ApartmentState.STA);
splashScreenThread.Name = "Splash Screen Thread";
splashScreenThread.IsBackground = true;
splashScreenThread.Start();
splashReadyEvent.WaitOne();
}
private void InitializeApplication()
{
instances.BuildObjectGraph(); instances.BuildObjectGraph();
var success = instances.StartupController.TryInitializeApplication(); var success = instances.StartupController.TryInitializeApplication();
@ -95,14 +62,19 @@ namespace SafeExamBrowser
if (success) if (success)
{ {
MainWindow = instances.Taskbar; MainWindow = instances.Taskbar;
MainWindow.Closing += (o, args) => ShutdownApplication();
MainWindow.Show(); MainWindow.Show();
} }
else else
{ {
Shutdown(); Shutdown();
} }
}
instances.SplashScreen?.Dispatcher.InvokeAsync(instances.SplashScreen.Close); private void ShutdownApplication()
{
MainWindow.Hide();
instances.ShutdownController.FinalizeApplication();
} }
} }
} }

View file

@ -11,11 +11,13 @@ using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Core.Behaviour; using SafeExamBrowser.Core.Behaviour;
using SafeExamBrowser.Core.Configuration; using SafeExamBrowser.Core.Configuration;
using SafeExamBrowser.Core.I18n; using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging; using SafeExamBrowser.Core.Logging;
using SafeExamBrowser.Monitoring.Processes;
using SafeExamBrowser.UserInterface; using SafeExamBrowser.UserInterface;
namespace SafeExamBrowser namespace SafeExamBrowser
@ -24,23 +26,18 @@ namespace SafeExamBrowser
{ {
private IApplicationController browserController; private IApplicationController browserController;
private IApplicationInfo browserInfo; private IApplicationInfo browserInfo;
private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private INotificationInfo aboutInfo; private INotificationInfo aboutInfo;
private ILogger logger; private IProcessMonitor processMonitor;
private ISettings settings;
private IText text;
private IUiElementFactory uiFactory; private IUiElementFactory uiFactory;
private ITextResource textResource;
public ISettings Settings { get; private set; }
public IShutdownController ShutdownController { get; private set; } public IShutdownController ShutdownController { get; private set; }
public IStartupController StartupController { get; private set; } public IStartupController StartupController { get; private set; }
public SplashScreen SplashScreen { get; set; }
public Taskbar Taskbar { get; private set; } public Taskbar Taskbar { get; private set; }
public IText Text { get; private set; }
public void BuildModulesRequiredBySplashScreen()
{
Settings = new Settings();
Text = new Text(new XmlTextResource());
}
public void BuildObjectGraph() public void BuildObjectGraph()
{ {
@ -48,14 +45,18 @@ namespace SafeExamBrowser
browserInfo = new BrowserApplicationInfo(); browserInfo = new BrowserApplicationInfo();
logger = new Logger(); logger = new Logger();
messageBox = new WpfMessageBox(); messageBox = new WpfMessageBox();
settings = new Settings();
Taskbar = new Taskbar(); Taskbar = new Taskbar();
textResource = new XmlTextResource();
uiFactory = new UiElementFactory(); uiFactory = new UiElementFactory();
logger.Subscribe(new LogFileWriter(Settings)); logger.Subscribe(new LogFileWriter(settings));
aboutInfo = new AboutNotificationInfo(Text); text = new Text(textResource);
ShutdownController = new ShutdownController(logger, messageBox, Text); aboutInfo = new AboutNotificationInfo(text);
StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, Settings, SplashScreen, Taskbar, Text, uiFactory); processMonitor = new ProcessMonitor(logger);
ShutdownController = new ShutdownController(logger, messageBox, processMonitor, settings, text, uiFactory);
StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, processMonitor, settings, Taskbar, text, uiFactory);
} }
} }
} }

View file

@ -111,6 +111,10 @@
<Project>{3d6fdbb6-a4af-4626-bb2b-bf329d44f9cc}</Project> <Project>{3d6fdbb6-a4af-4626-bb2b-bf329d44f9cc}</Project>
<Name>SafeExamBrowser.Core</Name> <Name>SafeExamBrowser.Core</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj">
<Project>{ef563531-4eb5-44b9-a5ec-d6d6f204469b}</Project>
<Name>SafeExamBrowser.Monitoring</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface\SafeExamBrowser.UserInterface.csproj"> <ProjectReference Include="..\SafeExamBrowser.UserInterface\SafeExamBrowser.UserInterface.csproj">
<Project>{e1be031a-4354-41e7-83e8-843ded4489ff}</Project> <Project>{e1be031a-4354-41e7-83e8-843ded4489ff}</Project>
<Name>SafeExamBrowser.UserInterface</Name> <Name>SafeExamBrowser.UserInterface</Name>