SEBWIN-219: Implemented basic shutdown mechanism.

This commit is contained in:
dbuechel 2018-02-20 15:15:26 +01:00
parent ea97930033
commit 2424f2f1ed
28 changed files with 343 additions and 59 deletions

View file

@ -26,6 +26,8 @@ namespace SafeExamBrowser.Client.Behaviour
private ITaskbar taskbar;
private IWindowMonitor windowMonitor;
public IClientHost ClientHost { private get; set; }
public ClientController(
IDisplayMonitor displayMonitor,
ILogger logger,
@ -44,34 +46,46 @@ namespace SafeExamBrowser.Client.Behaviour
this.windowMonitor = windowMonitor;
}
public bool TryStart()
{
var success = operations.TryPerform();
// TODO
if (success)
{
RegisterEvents();
runtime.InformClientReady();
}
return success;
}
public void Terminate()
{
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
windowMonitor.WindowChanged -= WindowMonitor_WindowChanged;
DeregisterEvents();
// TODO
operations.TryRevert();
}
public bool TryStart()
private void RegisterEvents()
{
ClientHost.Shutdown += ClientHost_Shutdown;
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
taskbar.QuitButtonClicked += Taskbar_QuitButtonClicked;
windowMonitor.WindowChanged += WindowMonitor_WindowChanged;
}
// TODO
var success = operations.TryPerform();
if (success)
{
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
windowMonitor.WindowChanged += WindowMonitor_WindowChanged;
runtime.InformClientReady();
}
return success;
private void DeregisterEvents()
{
ClientHost.Shutdown -= ClientHost_Shutdown;
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
taskbar.QuitButtonClicked -= Taskbar_QuitButtonClicked;
windowMonitor.WindowChanged -= WindowMonitor_WindowChanged;
}
private void DisplayMonitor_DisplaySettingsChanged()
@ -94,6 +108,24 @@ namespace SafeExamBrowser.Client.Behaviour
logger.Info("Desktop successfully restored.");
}
private void ClientHost_Shutdown()
{
// TODO: Better use callback to Application.Shutdown() as in runtime?
taskbar.Close();
}
private void Taskbar_QuitButtonClicked()
{
// TODO: MessageBox asking whether user really wants to quit -> args.Cancel
var acknowledged = runtime.RequestShutdown();
if (!acknowledged)
{
logger.Warn("The runtime did not acknowledge the shutdown request!");
}
}
private void WindowMonitor_WindowChanged(IntPtr window)
{
var allowed = processMonitor.BelongsToAllowedProcess(window);

View file

@ -21,6 +21,8 @@ namespace SafeExamBrowser.Client.Communication
public Guid StartupToken { private get; set; }
public event CommunicationEventHandler Shutdown;
public ClientHost(string address, ILogger logger, int processId) : base(address, logger)
{
this.processId = processId;
@ -49,6 +51,9 @@ namespace SafeExamBrowser.Client.Communication
{
case SimpleMessagePurport.Authenticate:
return new AuthenticationResponse { ProcessId = processId };
case SimpleMessagePurport.Shutdown:
Shutdown?.Invoke();
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);

View file

@ -102,6 +102,7 @@ namespace SafeExamBrowser.Client
internal void LogShutdownInformation()
{
logger?.Log(string.Empty);
logger?.Log($"# Client instance terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
@ -153,6 +154,7 @@ namespace SafeExamBrowser.Client
var operation = new CommunicationOperation(host, logger);
host.StartupToken = startupToken;
ClientController.ClientHost = host;
return operation;
}

View file

@ -73,6 +73,12 @@ namespace SafeExamBrowser.Configuration
};
settings.Browser.StartUrl = "https://www.duckduckgo.com";
settings.Browser.AllowAddressBar = true;
settings.Browser.AllowBackwardNavigation = true;
settings.Browser.AllowDeveloperConsole = true;
settings.Browser.AllowForwardNavigation = true;
settings.Browser.AllowReloading = true;
settings.Taskbar.AllowApplicationLog = true;
settings.Taskbar.AllowKeyboardLayout = true;
settings.Taskbar.AllowWirelessNetwork = true;

View file

@ -6,11 +6,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Communication;
namespace SafeExamBrowser.Contracts.Behaviour
{
// TODO: Interface really needed?!
public interface IClientController
{
/// <summary>
/// The client host used for communication handling.
/// </summary>
IClientHost ClientHost { set; }
/// <summary>
/// Reverts any changes, releases all used resources and terminates the client.
/// </summary>

View file

@ -16,5 +16,10 @@ namespace SafeExamBrowser.Contracts.Communication
/// The startup token used for initial authentication.
/// </summary>
Guid StartupToken { set; }
/// <summary>
/// Event fired when the runtime commands the client to shutdown.
/// </summary>
event CommunicationEventHandler Shutdown;
}
}

View file

@ -12,6 +12,11 @@ namespace SafeExamBrowser.Contracts.Communication
{
public interface IClientProxy : ICommunicationProxy
{
/// <summary>
/// Instructs the client to initiate its shutdown procedure.
/// </summary>
void InitiateShutdown();
/// <summary>
/// Instructs the client to submit its authentication data.
/// </summary>

View file

@ -17,9 +17,19 @@ namespace SafeExamBrowser.Contracts.Communication
/// </summary>
Guid StartupToken { set; }
/// <summary>
/// Event fired when the client disconnected from the runtime.
/// </summary>
event CommunicationEventHandler ClientDisconnected;
/// <summary>
/// Event fired once the client has signaled that it is ready to operate.
/// </summary>
event CommunicationEventHandler ClientReady;
/// <summary>
/// Event fired when the client requests to shut down the application.
/// </summary>
event CommunicationEventHandler ShutdownRequested;
}
}

View file

@ -15,11 +15,18 @@ namespace SafeExamBrowser.Contracts.Communication
/// <summary>
/// Retrieves the application configuration from the runtime.
/// </summary>
/// <exception cref="System.ServiceModel.CommunicationException">If the configuration could not be retrieved.</exception>
ClientConfiguration GetConfiguration();
/// <summary>
/// Informs the runtime that the client is ready.
/// </summary>
/// <exception cref="System.ServiceModel.CommunicationException">If the runtime did not acknowledge the status update.</exception>
void InformClientReady();
/// <summary>
/// Requests the runtime to shut down the application. Returns <c>true</c> if the request was acknowledged, otherwise <c>false</c>.
/// </summary>
bool RequestShutdown();
}
}

View file

@ -27,5 +27,15 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
/// Sent from the client to the runtime to ask for the client configuration.
/// </summary>
ConfigurationNeeded,
/// <summary>
/// Sent from the client to the runtime to request shutting down the application.
/// </summary>
RequestShutdown,
/// <summary>
/// Sent form the runtime to the client to command the latter to shut itself down.
/// </summary>
Shutdown
}
}

View file

@ -8,8 +8,15 @@
namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
{
public delegate void QuitButtonClickedEventHandler();
public interface ITaskbar
{
/// <summary>
/// Event fired when the user clicked the quit button in the taskbar.
/// </summary>
event QuitButtonClickedEventHandler QuitButtonClicked;
/// <summary>
/// Adds the given application button to the taskbar.
/// </summary>
@ -25,6 +32,11 @@ namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
/// </summary>
void AddSystemControl(ISystemControl control);
/// <summary>
/// Closes the taskbar.
/// </summary>
void Close();
/// <summary>
/// Returns the absolute height of the taskbar (i.e. in physical pixels).
/// </summary>

View file

@ -8,11 +8,28 @@
namespace SafeExamBrowser.Contracts.WindowsApi
{
public delegate void ProcessTerminatedEventHandler(int exitCode);
public interface IProcess
{
/// <summary>
/// Indicates whether the process has been terminated.
/// </summary>
bool HasTerminated { get; }
/// <summary>
/// The process identifier.
/// </summary>
int Id { get; }
/// <summary>
/// Event fired when the process has terminated.
/// </summary>
event ProcessTerminatedEventHandler Terminated;
/// <summary>
/// Immediately terminates the process.
/// </summary>
void Kill();
}
}

View file

@ -97,9 +97,7 @@ namespace SafeExamBrowser.Core.Communication
{
lock (@lock)
{
Response response = null;
logger.Debug($"Received message '{ToString(message)}'.");
var response = default(Response);
if (IsAuthorized(message?.CommunicationToken))
{
@ -113,7 +111,7 @@ namespace SafeExamBrowser.Core.Communication
}
}
logger.Debug($"Sending response '{ToString(response)}'.");
logger.Debug($"Received message '{ToString(message)}', sending response '{ToString(response)}'.");
return response;
}

View file

@ -80,6 +80,16 @@ namespace SafeExamBrowser.Core.Communication
return Send(new SimpleMessage(purport));
}
protected string ToString(Message message)
{
return message != null ? message.ToString() : "<null>";
}
protected string ToString(Response response)
{
return response != null ? response.ToString() : "<null>";
}
private void BaseProxy_Closed(object sender, EventArgs e)
{
Logger.Debug("Communication channel has been closed.");
@ -122,15 +132,5 @@ namespace SafeExamBrowser.Core.Communication
{
return channel == null ? "null" : $"in state '{(channel as ICommunicationObject).State}'";
}
private string ToString(Message message)
{
return message != null ? message.ToString() : "<null>";
}
private string ToString(Response response)
{
return response != null ? response.ToString() : "<null>";
}
}
}

View file

@ -19,6 +19,16 @@ namespace SafeExamBrowser.Core.Communication
{
}
public void InitiateShutdown()
{
var response = Send(SimpleMessagePurport.Shutdown);
if (response is SimpleResponse simpleMessage && simpleMessage.Purport == SimpleResponsePurport.Acknowledged)
{
// TODO
}
}
public AuthenticationResponse RequestAuthentication()
{
var response = Send(SimpleMessagePurport.Authenticate);

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.ServiceModel;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
@ -22,12 +23,37 @@ namespace SafeExamBrowser.Core.Communication
public ClientConfiguration GetConfiguration()
{
return ((ConfigurationResponse) Send(SimpleMessagePurport.ConfigurationNeeded)).Configuration;
var response = Send(SimpleMessagePurport.ConfigurationNeeded);
if (response is ConfigurationResponse configurationResponse)
{
return configurationResponse.Configuration;
}
throw new CommunicationException($"Could not retrieve client configuration! Received: {ToString(response)}.");
}
public void InformClientReady()
{
Send(SimpleMessagePurport.ClientIsReady);
var response = Send(SimpleMessagePurport.ClientIsReady);
if (!IsAcknowledgeResponse(response))
{
throw new CommunicationException($"Runtime did not acknowledge that client is ready! Response: {ToString(response)}.");
}
}
public bool RequestShutdown()
{
var response = Send(SimpleMessagePurport.RequestShutdown);
var acknowledged = IsAcknowledgeResponse(response);
return acknowledged;
}
private bool IsAcknowledgeResponse(Response response)
{
return response is SimpleResponse simpleResponse && simpleResponse.Purport == SimpleResponsePurport.Acknowledged;
}
}
}

View file

@ -58,7 +58,7 @@ namespace SafeExamBrowser.Core.Communication
return;
}
// TODO
// TODO: Send(new StopSessionMessage { SessionId = sessionId });
}
private bool IgnoreOperation(string operationName)

View file

@ -20,6 +20,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
internal abstract class SessionSequenceOperation : IOperation
{
private const int TEN_SECONDS = 10000;
private bool sessionRunning;
private IClientProxy client;
private IConfigurationRepository configuration;
@ -68,7 +70,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
}
catch (Exception e)
{
service.StopSession(session.Id);
logger.Error("Failed to start client!", e);
}
@ -80,6 +81,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
Abort = true;
logger.Info($"Failed to start new session! Aborting...");
service.StopSession(session.Id);
}
}
@ -92,8 +94,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
service.StopSession(session.Id);
// TODO:
// - Terminate client (or does it terminate itself?)
try
{
StopClient();
}
catch (Exception e)
{
logger.Error("Failed to terminate client!", e);
}
sessionRunning = false;
logger.Info($"Successfully stopped session with identifier '{session.Id}'.");
@ -102,9 +110,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private void StartClient()
{
const int TEN_SECONDS = 10000;
var clientStarted = false;
var clientReady = false;
var clientReadyEvent = new AutoResetEvent(false);
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath;
@ -113,13 +119,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
var token = session.StartupToken.ToString("D");
runtimeHost.ClientReady += clientReadyEventHandler;
session.ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token);
clientStarted = clientReadyEvent.WaitOne(TEN_SECONDS);
session.ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token);
clientReady = clientReadyEvent.WaitOne(TEN_SECONDS);
runtimeHost.ClientReady -= clientReadyEventHandler;
// TODO: Check if client process alive!
if (clientStarted)
if (clientReady)
{
if (client.Connect(session.StartupToken))
{
@ -145,5 +152,71 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Error($"Failed to start client within {TEN_SECONDS / 1000} seconds!");
}
}
private void StopClient()
{
var disconnected = false;
var disconnectedEvent = new AutoResetEvent(false);
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
var terminated = false;
var terminatedEvent = new AutoResetEvent(false);
var terminatedEventHandler = new ProcessTerminatedEventHandler((_) => terminatedEvent.Set());
runtimeHost.ClientDisconnected += disconnectedEventHandler;
session.ClientProcess.Terminated += terminatedEventHandler;
client.InitiateShutdown();
client.Disconnect();
disconnected = disconnectedEvent.WaitOne(TEN_SECONDS);
terminated = terminatedEvent.WaitOne(TEN_SECONDS);
runtimeHost.ClientDisconnected -= disconnectedEventHandler;
session.ClientProcess.Terminated -= terminatedEventHandler;
if (disconnected && terminated)
{
logger.Info("Client has been successfully terminated.");
}
else
{
if (!disconnected)
{
logger.Error($"Client failed to disconnect within {TEN_SECONDS / 1000} seconds!");
}
else if (!terminated)
{
logger.Error($"Client failed to terminate within {TEN_SECONDS / 1000} seconds!");
}
KillClient();
}
}
private void KillClient(int attempt = 0)
{
const int MAX_ATTEMPTS = 5;
if (attempt == MAX_ATTEMPTS)
{
logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
return;
}
logger.Info($"Killing client process with ID = {session.ClientProcess.Id}.");
session.ClientProcess.Kill();
if (session.ClientProcess.HasTerminated)
{
logger.Info("Client process has terminated.");
}
else
{
logger.Warn("Failed to kill client process. Trying again...");
KillClient(attempt++);
}
}
}
}

View file

@ -80,6 +80,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
logger.Subscribe(runtimeWindow);
splashScreen.Hide();
runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested;
StartSession(true);
}
@ -169,5 +170,11 @@ namespace SafeExamBrowser.Runtime.Behaviour
// TODO
}
}
private void RuntimeHost_ShutdownRequested()
{
logger.Info("Received shutdown request from client application.");
shutdown.Invoke();
}
}
}

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
@ -22,7 +23,9 @@ namespace SafeExamBrowser.Runtime.Communication
public Guid StartupToken { private get; set; }
public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger)
{
@ -36,7 +39,7 @@ namespace SafeExamBrowser.Runtime.Communication
protected override void OnDisconnect()
{
// TODO
Task.Run(() => ClientDisconnected?.Invoke());
}
protected override Response OnReceive(Message message)
@ -54,6 +57,9 @@ namespace SafeExamBrowser.Runtime.Communication
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case SimpleMessagePurport.ConfigurationNeeded:
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
case SimpleMessagePurport.RequestShutdown:
ShutdownRequested?.Invoke();
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);

View file

@ -39,7 +39,7 @@ namespace SafeExamBrowser.Runtime
var args = Environment.GetCommandLineArgs();
var configuration = new ConfigurationRepository();
var nativeMethods = new NativeMethods();
Action shutdown = Application.Current.Shutdown;
void shutdown() => Application.Current.Dispatcher.BeginInvoke(new Action(Application.Current.Shutdown));
logger = new Logger();
runtimeInfo = configuration.RuntimeInfo;
@ -75,12 +75,10 @@ namespace SafeExamBrowser.Runtime
internal void LogStartupInformation()
{
var titleLine = $"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}{Environment.NewLine}";
var copyrightLine = $"/* {runtimeInfo.ProgramCopyright}{Environment.NewLine}";
var emptyLine = $"/* {Environment.NewLine}";
var githubLine = $"/* Please visit https://www.github.com/SafeExamBrowser for more information.";
logger.Log($"{titleLine}{copyrightLine}{emptyLine}{githubLine}");
logger.Log($"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}");
logger.Log($"/* {runtimeInfo.ProgramCopyright}");
logger.Log($"/* ");
logger.Log($"/* Please visit https://www.github.com/SafeExamBrowser for more information.");
logger.Log(string.Empty);
logger.Log($"# Application started at {runtimeInfo.ApplicationStartTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log($"# Running on {systemInfo.OperatingSystemInfo}");

View file

@ -9,12 +9,15 @@
using System;
using System.Windows;
using System.Windows.Controls;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.UserInterface.Classic.Utilities;
namespace SafeExamBrowser.UserInterface.Classic.Controls
{
public partial class QuitButton : UserControl
{
public QuitButtonClickedEventHandler Clicked;
public QuitButton()
{
InitializeComponent();
@ -23,7 +26,7 @@ namespace SafeExamBrowser.UserInterface.Classic.Controls
private void Button_Click(object sender, RoutedEventArgs e)
{
Application.Current.MainWindow.Close();
Clicked?.Invoke();
}
private void LoadIcon()

View file

@ -25,6 +25,6 @@
<local:DateTimeControl Grid.Column="1" Foreground="DimGray" />
<StackPanel Grid.Column="2" x:Name="NotificationStackPanel" Margin="5,0,0,0" Orientation="Horizontal" VerticalAlignment="Stretch" />
<StackPanel Grid.Column="3" x:Name="SystemControlStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />
<local:QuitButton Grid.Column="4" />
<local:QuitButton Grid.Column="4" x:Name="QuitButton" />
</Grid>
</Window>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.ComponentModel;
using System.Windows;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
@ -17,14 +18,17 @@ namespace SafeExamBrowser.UserInterface.Classic
{
private ILogger logger;
public event QuitButtonClickedEventHandler QuitButtonClicked;
public Taskbar(ILogger logger)
{
InitializeComponent();
this.logger = logger;
Loaded += (o, args) => InitializeBounds();
Closing += Taskbar_Closing;
Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += () => QuitButtonClicked?.Invoke();
}
public void AddApplication(IApplicationButton button)
@ -51,6 +55,11 @@ namespace SafeExamBrowser.UserInterface.Classic
}
}
public new void Close()
{
Dispatcher.Invoke(base.Close);
}
public int GetAbsoluteHeight()
{
return Dispatcher.Invoke(() =>
@ -78,7 +87,7 @@ namespace SafeExamBrowser.UserInterface.Classic
});
}
private void Taskbar_Closing(object sender, System.ComponentModel.CancelEventArgs e)
private void Taskbar_Closing(object sender, CancelEventArgs e)
{
foreach (var child in SystemControlStackPanel.Children)
{

View file

@ -8,11 +8,14 @@
using System.Windows;
using System.Windows.Controls;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
namespace SafeExamBrowser.UserInterface.Windows10.Controls
{
public partial class QuitButton : UserControl
{
public event QuitButtonClickedEventHandler Clicked;
public QuitButton()
{
InitializeComponent();
@ -20,7 +23,7 @@ namespace SafeExamBrowser.UserInterface.Windows10.Controls
private void Button_Click(object sender, RoutedEventArgs e)
{
Application.Current.MainWindow.Close();
Clicked?.Invoke();
}
}
}

View file

@ -29,7 +29,7 @@
<StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Margin="5,0,0,0" Orientation="Horizontal" VerticalAlignment="Stretch" />
<StackPanel Grid.Column="2" x:Name="SystemControlStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />
<local:DateTimeControl Grid.Column="3" />
<local:QuitButton Grid.Column="4" />
<local:QuitButton Grid.Column="4" x:Name="QuitButton" />
</Grid>
</Border>
</Window>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.ComponentModel;
using System.Windows;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
@ -17,6 +18,8 @@ namespace SafeExamBrowser.UserInterface.Windows10
{
private ILogger logger;
public event QuitButtonClickedEventHandler QuitButtonClicked;
public Taskbar(ILogger logger)
{
this.logger = logger;
@ -25,6 +28,7 @@ namespace SafeExamBrowser.UserInterface.Windows10
Loaded += (o, args) => InitializeBounds();
Closing += Taskbar_Closing;
QuitButtonClicked += Taskbar_QuitButtonClicked;
}
public void AddApplication(IApplicationButton button)
@ -51,6 +55,11 @@ namespace SafeExamBrowser.UserInterface.Windows10
}
}
public new void Close()
{
Dispatcher.Invoke(base.Close);
}
public int GetAbsoluteHeight()
{
return Dispatcher.Invoke(() =>
@ -78,7 +87,12 @@ namespace SafeExamBrowser.UserInterface.Windows10
});
}
private void Taskbar_Closing(object sender, System.ComponentModel.CancelEventArgs e)
private void Taskbar_QuitButtonClicked()
{
QuitButtonClicked?.Invoke();
}
private void Taskbar_Closing(object sender, CancelEventArgs e)
{
foreach (var child in SystemControlStackPanel.Children)
{

View file

@ -14,14 +14,33 @@ namespace SafeExamBrowser.WindowsApi
{
private System.Diagnostics.Process process;
public event ProcessTerminatedEventHandler Terminated;
public int Id
{
get { return process.Id; }
}
public bool HasTerminated
{
get { return process.HasExited; }
}
public Process(int id)
{
process = System.Diagnostics.Process.GetProcessById(id);
process.Exited += Process_Exited;
process.EnableRaisingEvents = true;
}
public void Kill()
{
process.Kill();
}
private void Process_Exited(object sender, System.EventArgs e)
{
Terminated?.Invoke(process.ExitCode);
}
}
}