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,
Notification_AboutTooltip,
SplashScreen_InitializeBrowser,
SplashScreen_InitializeProcessMonitoring,
SplashScreen_InitializeTaskbar,
SplashScreen_InitializeWorkArea,
SplashScreen_RestoreWorkArea,
SplashScreen_ShutdownProcedure,
SplashScreen_StartupProcedure,
SplashScreen_StopProcessMonitoring,
SplashScreen_WaitExplorerStartup,
SplashScreen_WaitExplorerTermination,
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\IThreadInfo.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\ITaskbarNotification.cs" />
<Compile Include="UserInterface\ISplashScreen.cs" />

View file

@ -13,9 +13,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
public interface ISplashScreen
{
/// <summary>
/// Closes the splash screen.
/// Closes the splash screen on its own thread.
/// </summary>
void Close();
void InvokeClose();
/// <summary>
/// Shows the splash screen on its own thread.
/// </summary>
void InvokeShow();
/// <summary>
/// Set the maximum of the splash screen's progress bar.
@ -23,9 +28,14 @@ namespace SafeExamBrowser.Contracts.UserInterface
void SetMaxProgress(int max);
/// <summary>
/// Shows the splash screen to the user.
/// Starts an animation indicating the user that something is going on.
/// </summary>
void Show();
void StartBusyIndication();
/// <summary>
/// Stops the busy animation, if it was running.
/// </summary>
void StopBusyIndication();
/// <summary>
/// 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.
/// </summary>
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.I18n;
namespace SafeExamBrowser.Contracts.UserInterface
{
@ -17,6 +18,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
ITaskbarButton CreateApplicationButton(IApplicationInfo info);
/// <summary>
/// Creates a new splash screen which runs on its own thread.
/// </summary>
ISplashScreen CreateSplashScreen(ISettings settings, IText text);
/// <summary>
/// Creates a taskbar notification, initialized with the given notification information.
/// </summary>

View file

@ -7,9 +7,14 @@
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour
@ -18,23 +23,52 @@ namespace SafeExamBrowser.Core.Behaviour
{
private ILogger logger;
private IMessageBox messageBox;
private IProcessMonitor processMonitor;
private ISettings settings;
private ISplashScreen splashScreen;
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.messageBox = messageBox;
this.processMonitor = processMonitor;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
}
public void FinalizeApplication()
{
try
{
// TODO:
// - Gather TODOs!
InitializeSplashScreen();
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)
{
@ -42,5 +76,41 @@ namespace SafeExamBrowser.Core.Behaviour
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.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour
@ -25,6 +26,7 @@ namespace SafeExamBrowser.Core.Behaviour
private ILogger logger;
private IMessageBox messageBox;
private INotificationInfo aboutInfo;
private IProcessMonitor processMonitor;
private ISettings settings;
private ISplashScreen splashScreen;
private ITaskbar taskbar;
@ -53,8 +55,8 @@ namespace SafeExamBrowser.Core.Behaviour
ILogger logger,
IMessageBox messageBox,
INotificationInfo aboutInfo,
IProcessMonitor processMonitor,
ISettings settings,
ISplashScreen splashScreen,
ITaskbar taskbar,
IText text,
IUiElementFactory uiFactory)
@ -64,8 +66,8 @@ namespace SafeExamBrowser.Core.Behaviour
this.logger = logger;
this.messageBox = messageBox;
this.aboutInfo = aboutInfo;
this.processMonitor = processMonitor;
this.settings = settings;
this.splashScreen = splashScreen;
this.taskbar = taskbar;
this.text = text;
this.uiFactory = uiFactory;
@ -112,62 +114,63 @@ namespace SafeExamBrowser.Core.Behaviour
private void InitializeSplashScreen()
{
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(StartupOperations.Count());
splashScreen.UpdateText(Key.SplashScreen_StartupProcedure);
splashScreen.InvokeShow();
}
private void HandleCommandLineArguments()
{
logger.Info("Parsing command line arguments.");
// TODO
}
private void DetectOperatingSystem()
{
logger.Info("Detecting operating system.");
// TODO
}
private void EstablishWcfServiceConnection()
{
logger.Info("Establishing connection to WCF service.");
// TODO
}
private void DeactivateWindowsFeatures()
{
logger.Info("Deactivating Windows Update.");
// TODO
logger.Info("Disabling lock screen options.");
// TODO
}
private void InitializeProcessMonitoring()
{
logger.Info("Initializing process monitoring.");
splashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring);
// TODO
processMonitor.StartMonitoringExplorer();
}
private void InitializeWorkArea()
{
logger.Info("Initializing work area.");
splashScreen.UpdateText(Key.SplashScreen_InitializeWorkArea);
// TODO
// - Killing explorer.exe
// - Minimizing all open windows
// - Emptying clipboard
splashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination);
splashScreen.StartBusyIndication();
processMonitor.CloseExplorerShell();
splashScreen.StopBusyIndication();
}
private void InitializeTaskbar()
{
logger.Info("Initializing taskbar.");
splashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar);
// TODO
var aboutNotification = uiFactory.CreateNotification(aboutInfo);
@ -179,6 +182,8 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("Initializing browser.");
splashScreen.UpdateText(Key.SplashScreen_InitializeBrowser);
// TODO
var browserButton = uiFactory.CreateApplicationButton(browserInfo);
browserController.RegisterApplicationButton(browserButton);
@ -188,6 +193,7 @@ namespace SafeExamBrowser.Core.Behaviour
private void FinishInitialization()
{
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_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle>
<Notification_AboutTooltip>About Safe Exam Browser</Notification_AboutTooltip>
<SplashScreen_InitializeBrowser>Initializing browser.</SplashScreen_InitializeBrowser>
<SplashScreen_StartupProcedure>Initiating startup procedure.</SplashScreen_StartupProcedure>
<SplashScreen_InitializeBrowser>Initializing browser</SplashScreen_InitializeBrowser>
<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>
</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.Content = IconResourceLoader.Load(info.IconResource);
Button.MouseEnter += (o, args) => InstancePopup.IsOpen = instances.Count > 1;
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.Opened += (o, args) => ActiveBar.Width = Double.NaN;
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)
{
Application.Current.Shutdown();
Application.Current.MainWindow.Close();
}
}
}

View file

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

View file

@ -6,7 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Threading;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.Controls;
@ -19,6 +21,31 @@ namespace SafeExamBrowser.UserInterface
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)
{
return new NotificationIcon(info);

View file

@ -7,6 +7,7 @@
*/
using System.ComponentModel;
using System.Timers;
namespace SafeExamBrowser.UserInterface.ViewModels
{
@ -15,6 +16,7 @@ namespace SafeExamBrowser.UserInterface.ViewModels
private int currentProgress;
private int maxProgress;
private string status;
private Timer busyTimer;
public event PropertyChangedEventHandler PropertyChanged;
@ -56,5 +58,41 @@ namespace SafeExamBrowser.UserInterface.ViewModels
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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Browser", "SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj", "{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Monitoring", "SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj", "{EF563531-4EB5-44B9-A5EC-D6D6F204469B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -26,7 +26,11 @@ namespace SafeExamBrowser
}
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
{
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);
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();
var success = instances.StartupController.TryInitializeApplication();
@ -95,14 +62,19 @@ namespace SafeExamBrowser
if (success)
{
MainWindow = instances.Taskbar;
MainWindow.Closing += (o, args) => ShutdownApplication();
MainWindow.Show();
}
else
{
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.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Core.Behaviour;
using SafeExamBrowser.Core.Configuration;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
using SafeExamBrowser.Monitoring.Processes;
using SafeExamBrowser.UserInterface;
namespace SafeExamBrowser
@ -24,23 +26,18 @@ namespace SafeExamBrowser
{
private IApplicationController browserController;
private IApplicationInfo browserInfo;
private ILogger logger;
private IMessageBox messageBox;
private INotificationInfo aboutInfo;
private ILogger logger;
private IProcessMonitor processMonitor;
private ISettings settings;
private IText text;
private IUiElementFactory uiFactory;
private ITextResource textResource;
public ISettings Settings { get; private set; }
public IShutdownController ShutdownController { get; private set; }
public IStartupController StartupController { get; private set; }
public SplashScreen SplashScreen { get; 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()
{
@ -48,14 +45,18 @@ namespace SafeExamBrowser
browserInfo = new BrowserApplicationInfo();
logger = new Logger();
messageBox = new WpfMessageBox();
settings = new Settings();
Taskbar = new Taskbar();
textResource = new XmlTextResource();
uiFactory = new UiElementFactory();
logger.Subscribe(new LogFileWriter(Settings));
logger.Subscribe(new LogFileWriter(settings));
aboutInfo = new AboutNotificationInfo(Text);
ShutdownController = new ShutdownController(logger, messageBox, Text);
StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, Settings, SplashScreen, Taskbar, Text, uiFactory);
text = new Text(textResource);
aboutInfo = new AboutNotificationInfo(text);
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>
<Name>SafeExamBrowser.Core</Name>
</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">
<Project>{e1be031a-4354-41e7-83e8-843ded4489ff}</Project>
<Name>SafeExamBrowser.UserInterface</Name>