SEBWIN-219: Implemented basic shutdown mechanism.
This commit is contained in:
parent
ea97930033
commit
2424f2f1ed
28 changed files with 343 additions and 59 deletions
|
@ -26,6 +26,8 @@ namespace SafeExamBrowser.Client.Behaviour
|
||||||
private ITaskbar taskbar;
|
private ITaskbar taskbar;
|
||||||
private IWindowMonitor windowMonitor;
|
private IWindowMonitor windowMonitor;
|
||||||
|
|
||||||
|
public IClientHost ClientHost { private get; set; }
|
||||||
|
|
||||||
public ClientController(
|
public ClientController(
|
||||||
IDisplayMonitor displayMonitor,
|
IDisplayMonitor displayMonitor,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
@ -44,34 +46,46 @@ namespace SafeExamBrowser.Client.Behaviour
|
||||||
this.windowMonitor = windowMonitor;
|
this.windowMonitor = windowMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryStart()
|
||||||
|
{
|
||||||
|
var success = operations.TryPerform();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
RegisterEvents();
|
||||||
|
runtime.InformClientReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
public void Terminate()
|
public void Terminate()
|
||||||
{
|
{
|
||||||
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
|
DeregisterEvents();
|
||||||
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
|
|
||||||
windowMonitor.WindowChanged -= WindowMonitor_WindowChanged;
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
operations.TryRevert();
|
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
|
private void DeregisterEvents()
|
||||||
|
{
|
||||||
var success = operations.TryPerform();
|
ClientHost.Shutdown -= ClientHost_Shutdown;
|
||||||
|
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
|
||||||
if (success)
|
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
|
||||||
{
|
taskbar.QuitButtonClicked -= Taskbar_QuitButtonClicked;
|
||||||
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
|
windowMonitor.WindowChanged -= WindowMonitor_WindowChanged;
|
||||||
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
|
|
||||||
windowMonitor.WindowChanged += WindowMonitor_WindowChanged;
|
|
||||||
|
|
||||||
runtime.InformClientReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisplayMonitor_DisplaySettingsChanged()
|
private void DisplayMonitor_DisplaySettingsChanged()
|
||||||
|
@ -94,6 +108,24 @@ namespace SafeExamBrowser.Client.Behaviour
|
||||||
logger.Info("Desktop successfully restored.");
|
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)
|
private void WindowMonitor_WindowChanged(IntPtr window)
|
||||||
{
|
{
|
||||||
var allowed = processMonitor.BelongsToAllowedProcess(window);
|
var allowed = processMonitor.BelongsToAllowedProcess(window);
|
||||||
|
|
|
@ -21,6 +21,8 @@ namespace SafeExamBrowser.Client.Communication
|
||||||
|
|
||||||
public Guid StartupToken { private get; set; }
|
public Guid StartupToken { private get; set; }
|
||||||
|
|
||||||
|
public event CommunicationEventHandler Shutdown;
|
||||||
|
|
||||||
public ClientHost(string address, ILogger logger, int processId) : base(address, logger)
|
public ClientHost(string address, ILogger logger, int processId) : base(address, logger)
|
||||||
{
|
{
|
||||||
this.processId = processId;
|
this.processId = processId;
|
||||||
|
@ -49,6 +51,9 @@ namespace SafeExamBrowser.Client.Communication
|
||||||
{
|
{
|
||||||
case SimpleMessagePurport.Authenticate:
|
case SimpleMessagePurport.Authenticate:
|
||||||
return new AuthenticationResponse { ProcessId = processId };
|
return new AuthenticationResponse { ProcessId = processId };
|
||||||
|
case SimpleMessagePurport.Shutdown:
|
||||||
|
Shutdown?.Invoke();
|
||||||
|
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
||||||
|
|
|
@ -102,6 +102,7 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
internal void LogShutdownInformation()
|
internal void LogShutdownInformation()
|
||||||
{
|
{
|
||||||
|
logger?.Log(string.Empty);
|
||||||
logger?.Log($"# Client instance terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
|
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);
|
var operation = new CommunicationOperation(host, logger);
|
||||||
|
|
||||||
host.StartupToken = startupToken;
|
host.StartupToken = startupToken;
|
||||||
|
ClientController.ClientHost = host;
|
||||||
|
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,12 @@ namespace SafeExamBrowser.Configuration
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.Browser.StartUrl = "https://www.duckduckgo.com";
|
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.AllowApplicationLog = true;
|
||||||
settings.Taskbar.AllowKeyboardLayout = true;
|
settings.Taskbar.AllowKeyboardLayout = true;
|
||||||
settings.Taskbar.AllowWirelessNetwork = true;
|
settings.Taskbar.AllowWirelessNetwork = true;
|
||||||
|
|
|
@ -6,11 +6,18 @@
|
||||||
* 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 SafeExamBrowser.Contracts.Communication;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Contracts.Behaviour
|
namespace SafeExamBrowser.Contracts.Behaviour
|
||||||
{
|
{
|
||||||
// TODO: Interface really needed?!
|
// TODO: Interface really needed?!
|
||||||
public interface IClientController
|
public interface IClientController
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The client host used for communication handling.
|
||||||
|
/// </summary>
|
||||||
|
IClientHost ClientHost { set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reverts any changes, releases all used resources and terminates the client.
|
/// Reverts any changes, releases all used resources and terminates the client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -16,5 +16,10 @@ namespace SafeExamBrowser.Contracts.Communication
|
||||||
/// The startup token used for initial authentication.
|
/// The startup token used for initial authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Guid StartupToken { set; }
|
Guid StartupToken { set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the runtime commands the client to shutdown.
|
||||||
|
/// </summary>
|
||||||
|
event CommunicationEventHandler Shutdown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,11 @@ namespace SafeExamBrowser.Contracts.Communication
|
||||||
{
|
{
|
||||||
public interface IClientProxy : ICommunicationProxy
|
public interface IClientProxy : ICommunicationProxy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instructs the client to initiate its shutdown procedure.
|
||||||
|
/// </summary>
|
||||||
|
void InitiateShutdown();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instructs the client to submit its authentication data.
|
/// Instructs the client to submit its authentication data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -17,9 +17,19 @@ namespace SafeExamBrowser.Contracts.Communication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Guid StartupToken { set; }
|
Guid StartupToken { set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the client disconnected from the runtime.
|
||||||
|
/// </summary>
|
||||||
|
event CommunicationEventHandler ClientDisconnected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired once the client has signaled that it is ready to operate.
|
/// Event fired once the client has signaled that it is ready to operate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event CommunicationEventHandler ClientReady;
|
event CommunicationEventHandler ClientReady;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the client requests to shut down the application.
|
||||||
|
/// </summary>
|
||||||
|
event CommunicationEventHandler ShutdownRequested;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,18 @@ namespace SafeExamBrowser.Contracts.Communication
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the application configuration from the runtime.
|
/// Retrieves the application configuration from the runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <exception cref="System.ServiceModel.CommunicationException">If the configuration could not be retrieved.</exception>
|
||||||
ClientConfiguration GetConfiguration();
|
ClientConfiguration GetConfiguration();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Informs the runtime that the client is ready.
|
/// Informs the runtime that the client is ready.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <exception cref="System.ServiceModel.CommunicationException">If the runtime did not acknowledge the status update.</exception>
|
||||||
void InformClientReady();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,5 +27,15 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
|
||||||
/// Sent from the client to the runtime to ask for the client configuration.
|
/// Sent from the client to the runtime to ask for the client configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ConfigurationNeeded,
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,15 @@
|
||||||
|
|
||||||
namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
|
namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
|
||||||
{
|
{
|
||||||
|
public delegate void QuitButtonClickedEventHandler();
|
||||||
|
|
||||||
public interface ITaskbar
|
public interface ITaskbar
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the user clicked the quit button in the taskbar.
|
||||||
|
/// </summary>
|
||||||
|
event QuitButtonClickedEventHandler QuitButtonClicked;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the given application button to the taskbar.
|
/// Adds the given application button to the taskbar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -25,6 +32,11 @@ namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void AddSystemControl(ISystemControl control);
|
void AddSystemControl(ISystemControl control);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the taskbar.
|
||||||
|
/// </summary>
|
||||||
|
void Close();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the absolute height of the taskbar (i.e. in physical pixels).
|
/// Returns the absolute height of the taskbar (i.e. in physical pixels).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -8,11 +8,28 @@
|
||||||
|
|
||||||
namespace SafeExamBrowser.Contracts.WindowsApi
|
namespace SafeExamBrowser.Contracts.WindowsApi
|
||||||
{
|
{
|
||||||
|
public delegate void ProcessTerminatedEventHandler(int exitCode);
|
||||||
|
|
||||||
public interface IProcess
|
public interface IProcess
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the process has been terminated.
|
||||||
|
/// </summary>
|
||||||
|
bool HasTerminated { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The process identifier.
|
/// The process identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int Id { get; }
|
int Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the process has terminated.
|
||||||
|
/// </summary>
|
||||||
|
event ProcessTerminatedEventHandler Terminated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Immediately terminates the process.
|
||||||
|
/// </summary>
|
||||||
|
void Kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,9 +97,7 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
{
|
{
|
||||||
lock (@lock)
|
lock (@lock)
|
||||||
{
|
{
|
||||||
Response response = null;
|
var response = default(Response);
|
||||||
|
|
||||||
logger.Debug($"Received message '{ToString(message)}'.");
|
|
||||||
|
|
||||||
if (IsAuthorized(message?.CommunicationToken))
|
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;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,16 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
return Send(new SimpleMessage(purport));
|
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)
|
private void BaseProxy_Closed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Logger.Debug("Communication channel has been closed.");
|
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}'";
|
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>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
public AuthenticationResponse RequestAuthentication()
|
||||||
{
|
{
|
||||||
var response = Send(SimpleMessagePurport.Authenticate);
|
var response = Send(SimpleMessagePurport.Authenticate);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* 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.ServiceModel;
|
||||||
using SafeExamBrowser.Contracts.Communication;
|
using SafeExamBrowser.Contracts.Communication;
|
||||||
using SafeExamBrowser.Contracts.Communication.Messages;
|
using SafeExamBrowser.Contracts.Communication.Messages;
|
||||||
using SafeExamBrowser.Contracts.Communication.Responses;
|
using SafeExamBrowser.Contracts.Communication.Responses;
|
||||||
|
@ -22,12 +23,37 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
|
|
||||||
public ClientConfiguration GetConfiguration()
|
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()
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO: Send(new StopSessionMessage { SessionId = sessionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IgnoreOperation(string operationName)
|
private bool IgnoreOperation(string operationName)
|
||||||
|
|
|
@ -20,6 +20,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
|
||||||
{
|
{
|
||||||
internal abstract class SessionSequenceOperation : IOperation
|
internal abstract class SessionSequenceOperation : IOperation
|
||||||
{
|
{
|
||||||
|
private const int TEN_SECONDS = 10000;
|
||||||
|
|
||||||
private bool sessionRunning;
|
private bool sessionRunning;
|
||||||
private IClientProxy client;
|
private IClientProxy client;
|
||||||
private IConfigurationRepository configuration;
|
private IConfigurationRepository configuration;
|
||||||
|
@ -68,7 +70,6 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
service.StopSession(session.Id);
|
|
||||||
logger.Error("Failed to start client!", e);
|
logger.Error("Failed to start client!", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +81,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
|
||||||
{
|
{
|
||||||
Abort = true;
|
Abort = true;
|
||||||
logger.Info($"Failed to start new session! Aborting...");
|
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);
|
service.StopSession(session.Id);
|
||||||
|
|
||||||
// TODO:
|
try
|
||||||
// - Terminate client (or does it terminate itself?)
|
{
|
||||||
|
StopClient();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error("Failed to terminate client!", e);
|
||||||
|
}
|
||||||
|
|
||||||
sessionRunning = false;
|
sessionRunning = false;
|
||||||
logger.Info($"Successfully stopped session with identifier '{session.Id}'.");
|
logger.Info($"Successfully stopped session with identifier '{session.Id}'.");
|
||||||
|
@ -102,9 +110,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
|
||||||
|
|
||||||
private void StartClient()
|
private void StartClient()
|
||||||
{
|
{
|
||||||
const int TEN_SECONDS = 10000;
|
var clientReady = false;
|
||||||
|
|
||||||
var clientStarted = false;
|
|
||||||
var clientReadyEvent = new AutoResetEvent(false);
|
var clientReadyEvent = new AutoResetEvent(false);
|
||||||
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
|
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
|
||||||
var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath;
|
var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath;
|
||||||
|
@ -113,13 +119,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
|
||||||
var token = session.StartupToken.ToString("D");
|
var token = session.StartupToken.ToString("D");
|
||||||
|
|
||||||
runtimeHost.ClientReady += clientReadyEventHandler;
|
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;
|
runtimeHost.ClientReady -= clientReadyEventHandler;
|
||||||
|
|
||||||
// TODO: Check if client process alive!
|
// TODO: Check if client process alive!
|
||||||
if (clientStarted)
|
if (clientReady)
|
||||||
{
|
{
|
||||||
if (client.Connect(session.StartupToken))
|
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!");
|
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++);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
|
||||||
logger.Subscribe(runtimeWindow);
|
logger.Subscribe(runtimeWindow);
|
||||||
|
|
||||||
splashScreen.Hide();
|
splashScreen.Hide();
|
||||||
|
runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested;
|
||||||
|
|
||||||
StartSession(true);
|
StartSession(true);
|
||||||
}
|
}
|
||||||
|
@ -169,5 +170,11 @@ namespace SafeExamBrowser.Runtime.Behaviour
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RuntimeHost_ShutdownRequested()
|
||||||
|
{
|
||||||
|
logger.Info("Received shutdown request from client application.");
|
||||||
|
shutdown.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using SafeExamBrowser.Contracts.Communication;
|
using SafeExamBrowser.Contracts.Communication;
|
||||||
using SafeExamBrowser.Contracts.Communication.Messages;
|
using SafeExamBrowser.Contracts.Communication.Messages;
|
||||||
using SafeExamBrowser.Contracts.Communication.Responses;
|
using SafeExamBrowser.Contracts.Communication.Responses;
|
||||||
|
@ -22,7 +23,9 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
|
|
||||||
public Guid StartupToken { private get; set; }
|
public Guid StartupToken { private get; set; }
|
||||||
|
|
||||||
|
public event CommunicationEventHandler ClientDisconnected;
|
||||||
public event CommunicationEventHandler ClientReady;
|
public event CommunicationEventHandler ClientReady;
|
||||||
|
public event CommunicationEventHandler ShutdownRequested;
|
||||||
|
|
||||||
public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger)
|
public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger)
|
||||||
{
|
{
|
||||||
|
@ -36,7 +39,7 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
|
|
||||||
protected override void OnDisconnect()
|
protected override void OnDisconnect()
|
||||||
{
|
{
|
||||||
// TODO
|
Task.Run(() => ClientDisconnected?.Invoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Response OnReceive(Message message)
|
protected override Response OnReceive(Message message)
|
||||||
|
@ -54,6 +57,9 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||||
case SimpleMessagePurport.ConfigurationNeeded:
|
case SimpleMessagePurport.ConfigurationNeeded:
|
||||||
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
|
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
|
||||||
|
case SimpleMessagePurport.RequestShutdown:
|
||||||
|
ShutdownRequested?.Invoke();
|
||||||
|
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace SafeExamBrowser.Runtime
|
||||||
var args = Environment.GetCommandLineArgs();
|
var args = Environment.GetCommandLineArgs();
|
||||||
var configuration = new ConfigurationRepository();
|
var configuration = new ConfigurationRepository();
|
||||||
var nativeMethods = new NativeMethods();
|
var nativeMethods = new NativeMethods();
|
||||||
Action shutdown = Application.Current.Shutdown;
|
void shutdown() => Application.Current.Dispatcher.BeginInvoke(new Action(Application.Current.Shutdown));
|
||||||
|
|
||||||
logger = new Logger();
|
logger = new Logger();
|
||||||
runtimeInfo = configuration.RuntimeInfo;
|
runtimeInfo = configuration.RuntimeInfo;
|
||||||
|
@ -75,12 +75,10 @@ namespace SafeExamBrowser.Runtime
|
||||||
|
|
||||||
internal void LogStartupInformation()
|
internal void LogStartupInformation()
|
||||||
{
|
{
|
||||||
var titleLine = $"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}{Environment.NewLine}";
|
logger.Log($"/* {runtimeInfo.ProgramTitle}, Version {runtimeInfo.ProgramVersion}");
|
||||||
var copyrightLine = $"/* {runtimeInfo.ProgramCopyright}{Environment.NewLine}";
|
logger.Log($"/* {runtimeInfo.ProgramCopyright}");
|
||||||
var emptyLine = $"/* {Environment.NewLine}";
|
logger.Log($"/* ");
|
||||||
var githubLine = $"/* Please visit https://www.github.com/SafeExamBrowser for more information.";
|
logger.Log($"/* Please visit https://www.github.com/SafeExamBrowser for more information.");
|
||||||
|
|
||||||
logger.Log($"{titleLine}{copyrightLine}{emptyLine}{githubLine}");
|
|
||||||
logger.Log(string.Empty);
|
logger.Log(string.Empty);
|
||||||
logger.Log($"# Application started at {runtimeInfo.ApplicationStartTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
|
logger.Log($"# Application started at {runtimeInfo.ApplicationStartTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
|
||||||
logger.Log($"# Running on {systemInfo.OperatingSystemInfo}");
|
logger.Log($"# Running on {systemInfo.OperatingSystemInfo}");
|
||||||
|
|
|
@ -9,12 +9,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
|
||||||
using SafeExamBrowser.UserInterface.Classic.Utilities;
|
using SafeExamBrowser.UserInterface.Classic.Utilities;
|
||||||
|
|
||||||
namespace SafeExamBrowser.UserInterface.Classic.Controls
|
namespace SafeExamBrowser.UserInterface.Classic.Controls
|
||||||
{
|
{
|
||||||
public partial class QuitButton : UserControl
|
public partial class QuitButton : UserControl
|
||||||
{
|
{
|
||||||
|
public QuitButtonClickedEventHandler Clicked;
|
||||||
|
|
||||||
public QuitButton()
|
public QuitButton()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -23,7 +26,7 @@ namespace SafeExamBrowser.UserInterface.Classic.Controls
|
||||||
|
|
||||||
private void Button_Click(object sender, RoutedEventArgs e)
|
private void Button_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Application.Current.MainWindow.Close();
|
Clicked?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadIcon()
|
private void LoadIcon()
|
||||||
|
|
|
@ -25,6 +25,6 @@
|
||||||
<local:DateTimeControl Grid.Column="1" Foreground="DimGray" />
|
<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="2" x:Name="NotificationStackPanel" Margin="5,0,0,0" Orientation="Horizontal" VerticalAlignment="Stretch" />
|
||||||
<StackPanel Grid.Column="3" x:Name="SystemControlStackPanel" 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>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* 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.ComponentModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using SafeExamBrowser.Contracts.Logging;
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
|
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
|
||||||
|
@ -17,14 +18,17 @@ namespace SafeExamBrowser.UserInterface.Classic
|
||||||
{
|
{
|
||||||
private ILogger logger;
|
private ILogger logger;
|
||||||
|
|
||||||
|
public event QuitButtonClickedEventHandler QuitButtonClicked;
|
||||||
|
|
||||||
public Taskbar(ILogger logger)
|
public Taskbar(ILogger logger)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
Loaded += (o, args) => InitializeBounds();
|
|
||||||
Closing += Taskbar_Closing;
|
Closing += Taskbar_Closing;
|
||||||
|
Loaded += (o, args) => InitializeBounds();
|
||||||
|
QuitButton.Clicked += () => QuitButtonClicked?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddApplication(IApplicationButton button)
|
public void AddApplication(IApplicationButton button)
|
||||||
|
@ -51,6 +55,11 @@ namespace SafeExamBrowser.UserInterface.Classic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new void Close()
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(base.Close);
|
||||||
|
}
|
||||||
|
|
||||||
public int GetAbsoluteHeight()
|
public int GetAbsoluteHeight()
|
||||||
{
|
{
|
||||||
return Dispatcher.Invoke(() =>
|
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)
|
foreach (var child in SystemControlStackPanel.Children)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,11 +8,14 @@
|
||||||
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
|
||||||
|
|
||||||
namespace SafeExamBrowser.UserInterface.Windows10.Controls
|
namespace SafeExamBrowser.UserInterface.Windows10.Controls
|
||||||
{
|
{
|
||||||
public partial class QuitButton : UserControl
|
public partial class QuitButton : UserControl
|
||||||
{
|
{
|
||||||
|
public event QuitButtonClickedEventHandler Clicked;
|
||||||
|
|
||||||
public QuitButton()
|
public QuitButton()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -20,7 +23,7 @@ namespace SafeExamBrowser.UserInterface.Windows10.Controls
|
||||||
|
|
||||||
private void Button_Click(object sender, RoutedEventArgs e)
|
private void Button_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Application.Current.MainWindow.Close();
|
Clicked?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Margin="5,0,0,0" Orientation="Horizontal" VerticalAlignment="Stretch" />
|
<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" />
|
<StackPanel Grid.Column="2" x:Name="SystemControlStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />
|
||||||
<local:DateTimeControl Grid.Column="3" />
|
<local:DateTimeControl Grid.Column="3" />
|
||||||
<local:QuitButton Grid.Column="4" />
|
<local:QuitButton Grid.Column="4" x:Name="QuitButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Window>
|
</Window>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* 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.ComponentModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using SafeExamBrowser.Contracts.Logging;
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
|
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
|
||||||
|
@ -17,6 +18,8 @@ namespace SafeExamBrowser.UserInterface.Windows10
|
||||||
{
|
{
|
||||||
private ILogger logger;
|
private ILogger logger;
|
||||||
|
|
||||||
|
public event QuitButtonClickedEventHandler QuitButtonClicked;
|
||||||
|
|
||||||
public Taskbar(ILogger logger)
|
public Taskbar(ILogger logger)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
@ -25,6 +28,7 @@ namespace SafeExamBrowser.UserInterface.Windows10
|
||||||
|
|
||||||
Loaded += (o, args) => InitializeBounds();
|
Loaded += (o, args) => InitializeBounds();
|
||||||
Closing += Taskbar_Closing;
|
Closing += Taskbar_Closing;
|
||||||
|
QuitButtonClicked += Taskbar_QuitButtonClicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddApplication(IApplicationButton button)
|
public void AddApplication(IApplicationButton button)
|
||||||
|
@ -51,6 +55,11 @@ namespace SafeExamBrowser.UserInterface.Windows10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new void Close()
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(base.Close);
|
||||||
|
}
|
||||||
|
|
||||||
public int GetAbsoluteHeight()
|
public int GetAbsoluteHeight()
|
||||||
{
|
{
|
||||||
return Dispatcher.Invoke(() =>
|
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)
|
foreach (var child in SystemControlStackPanel.Children)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,14 +14,33 @@ namespace SafeExamBrowser.WindowsApi
|
||||||
{
|
{
|
||||||
private System.Diagnostics.Process process;
|
private System.Diagnostics.Process process;
|
||||||
|
|
||||||
|
public event ProcessTerminatedEventHandler Terminated;
|
||||||
|
|
||||||
public int Id
|
public int Id
|
||||||
{
|
{
|
||||||
get { return process.Id; }
|
get { return process.Id; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasTerminated
|
||||||
|
{
|
||||||
|
get { return process.HasExited; }
|
||||||
|
}
|
||||||
|
|
||||||
public Process(int id)
|
public Process(int id)
|
||||||
{
|
{
|
||||||
process = System.Diagnostics.Process.GetProcessById(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue