SEBWIN-477 implementation
This commit is contained in:
parent
4bb46c0d7a
commit
c13b2d2ac7
12 changed files with 190 additions and 8 deletions
|
@ -68,6 +68,7 @@ namespace SafeExamBrowser.Client
|
||||||
private IServerProxy Server => context.Server;
|
private IServerProxy Server => context.Server;
|
||||||
private AppSettings Settings => context.Settings;
|
private AppSettings Settings => context.Settings;
|
||||||
|
|
||||||
|
private ILockScreen lockScreen;
|
||||||
private bool sessionLocked;
|
private bool sessionLocked;
|
||||||
|
|
||||||
internal ClientController(
|
internal ClientController(
|
||||||
|
@ -215,10 +216,25 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
if (Server != null)
|
if (Server != null)
|
||||||
{
|
{
|
||||||
|
Server.LockScreenConfirmed += Server_LockScreenConfirmed;
|
||||||
Server.TerminationRequested += Server_TerminationRequested;
|
Server.TerminationRequested += Server_TerminationRequested;
|
||||||
|
Server.LockScreenRequested += Server_LockScreenRequested;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Server_LockScreenRequested(string message)
|
||||||
|
{
|
||||||
|
logger.Info("Received lock screen event from SEB Server.");
|
||||||
|
var title = text.Get(TextKey.LockScreen_Title);
|
||||||
|
ShowLockScreen(message, title, Enumerable.Empty<LockScreenOption>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Server_LockScreenConfirmed()
|
||||||
|
{
|
||||||
|
logger.Info("Closing lock screen as requested by the server...");
|
||||||
|
lockScreen?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
private void Taskbar_LoseFocusRequested(bool forward)
|
private void Taskbar_LoseFocusRequested(bool forward)
|
||||||
{
|
{
|
||||||
Browser.Focus(forward);
|
Browser.Focus(forward);
|
||||||
|
@ -260,7 +276,9 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
if (Server != null)
|
if (Server != null)
|
||||||
{
|
{
|
||||||
|
Server.LockScreenConfirmed -= Server_LockScreenConfirmed;
|
||||||
Server.TerminationRequested -= Server_TerminationRequested;
|
Server.TerminationRequested -= Server_TerminationRequested;
|
||||||
|
Server.LockScreenRequested -= Server_LockScreenRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var activator in context.Activators.OfType<ITerminationActivator>())
|
foreach (var activator in context.Activators.OfType<ITerminationActivator>())
|
||||||
|
@ -769,18 +787,33 @@ namespace SafeExamBrowser.Client
|
||||||
private LockScreenResult ShowLockScreen(string message, string title, IEnumerable<LockScreenOption> options)
|
private LockScreenResult ShowLockScreen(string message, string title, IEnumerable<LockScreenOption> options)
|
||||||
{
|
{
|
||||||
var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash);
|
var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash);
|
||||||
var lockScreen = uiFactory.CreateLockScreen(message, title, options);
|
lockScreen = uiFactory.CreateLockScreen(message, title, options);
|
||||||
var result = default(LockScreenResult);
|
var result = default(LockScreenResult);
|
||||||
|
|
||||||
logger.Info("Showing lock screen...");
|
logger.Info("Showing lock screen...");
|
||||||
PauseActivators();
|
PauseActivators();
|
||||||
lockScreen.Show();
|
lockScreen.Show();
|
||||||
|
|
||||||
|
if (Settings.SessionMode == SessionMode.Server)
|
||||||
|
{
|
||||||
|
var response = Server.LockScreen(message);
|
||||||
|
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to send lock screen notification to server! Message: {response.Message}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var unlocked = false; !unlocked;)
|
for (var unlocked = false; !unlocked;)
|
||||||
{
|
{
|
||||||
result = lockScreen.WaitForResult();
|
result = lockScreen.WaitForResult();
|
||||||
|
|
||||||
if (hasQuitPassword)
|
if (result.Canceled)
|
||||||
|
{
|
||||||
|
logger.Info("The lock screen has been automaticaly canceled.");
|
||||||
|
unlocked = true;
|
||||||
|
}
|
||||||
|
else if (hasQuitPassword)
|
||||||
{
|
{
|
||||||
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
|
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
|
||||||
var isCorrect = Settings.Security.QuitPasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase);
|
var isCorrect = Settings.Security.QuitPasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase);
|
||||||
|
@ -807,6 +840,16 @@ namespace SafeExamBrowser.Client
|
||||||
ResumeActivators();
|
ResumeActivators();
|
||||||
logger.Info("Closed lock screen.");
|
logger.Info("Closed lock screen.");
|
||||||
|
|
||||||
|
if (Settings.SessionMode == SessionMode.Server)
|
||||||
|
{
|
||||||
|
var response = Server.ConfirmLockScreen();
|
||||||
|
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to send lock screen confirm notification to server! Message: {response.Message}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Server.Contracts.Events
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event handler used to handle a requested lock screen from SEB Server
|
||||||
|
/// </summary>
|
||||||
|
public delegate void LockScreenRequestedEventHandler(String message);
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,16 @@ namespace SafeExamBrowser.Server.Contracts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ServerEventHandler HandConfirmed;
|
event ServerEventHandler HandConfirmed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when the proxy receives a confirmation for a lock screen notification.
|
||||||
|
/// </summary>
|
||||||
|
event ServerEventHandler LockScreenConfirmed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when proxy receives a lock screen instruction.
|
||||||
|
/// </summary>
|
||||||
|
event LockScreenRequestedEventHandler LockScreenRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the proxy receives new proctoring configuration values.
|
/// Event fired when the proxy receives new proctoring configuration values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -79,6 +89,11 @@ namespace SafeExamBrowser.Server.Contracts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ServerResponse LowerHand();
|
ServerResponse LowerHand();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a lock screen confirm notification to the server.
|
||||||
|
/// </summary>
|
||||||
|
ServerResponse ConfirmLockScreen();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
|
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -98,5 +113,10 @@ namespace SafeExamBrowser.Server.Contracts
|
||||||
/// Sends a raise hand notification to the server.
|
/// Sends a raise hand notification to the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ServerResponse RaiseHand(string message = default(string));
|
ServerResponse RaiseHand(string message = default(string));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a lock screen notification to the server.
|
||||||
|
/// </summary>
|
||||||
|
ServerResponse LockScreen(string message = default(string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Data\ConnectionInfo.cs" />
|
<Compile Include="Data\ConnectionInfo.cs" />
|
||||||
<Compile Include="Data\Exam.cs" />
|
<Compile Include="Data\Exam.cs" />
|
||||||
|
<Compile Include="Events\LockScreenRequestedEventHandler.cs" />
|
||||||
<Compile Include="Events\ProctoringConfigurationReceivedEventHandler.cs" />
|
<Compile Include="Events\ProctoringConfigurationReceivedEventHandler.cs" />
|
||||||
<Compile Include="Events\ProctoringInstructionEventArgs.cs" />
|
<Compile Include="Events\ProctoringInstructionEventArgs.cs" />
|
||||||
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />
|
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />
|
||||||
|
|
|
@ -19,6 +19,8 @@ namespace SafeExamBrowser.Server.Data
|
||||||
internal bool ReceiveVideo { get; set; }
|
internal bool ReceiveVideo { get; set; }
|
||||||
internal string Type { get; set; }
|
internal string Type { get; set; }
|
||||||
|
|
||||||
|
internal string Message { get; set; }
|
||||||
|
|
||||||
internal Attributes()
|
internal Attributes()
|
||||||
{
|
{
|
||||||
Instruction = new ProctoringInstructionEventArgs();
|
Instruction = new ProctoringInstructionEventArgs();
|
||||||
|
|
|
@ -14,5 +14,6 @@ namespace SafeExamBrowser.Server.Data
|
||||||
internal const string PROCTORING = "SEB_PROCTORING";
|
internal const string PROCTORING = "SEB_PROCTORING";
|
||||||
internal const string PROCTORING_RECONFIGURATION = "SEB_RECONFIGURE_SETTINGS";
|
internal const string PROCTORING_RECONFIGURATION = "SEB_RECONFIGURE_SETTINGS";
|
||||||
internal const string QUIT = "SEB_QUIT";
|
internal const string QUIT = "SEB_QUIT";
|
||||||
|
internal const string LOCK_SCREEN = "SEB_FORCE_LOCK_SCREEN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@ namespace SafeExamBrowser.Server
|
||||||
instructionConfirmation = attributesJson["instruction-confirm"].Value<string>();
|
instructionConfirmation = attributesJson["instruction-confirm"].Value<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes = ParseProctoringAttributes(attributesJson, instruction);
|
attributes = ParseAttributes(attributesJson, instruction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ namespace SafeExamBrowser.Server
|
||||||
return oauth2Token != default;
|
return oauth2Token != default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Attributes ParseProctoringAttributes(JObject attributesJson, string instruction)
|
private Attributes ParseAttributes(JObject attributesJson, string instruction)
|
||||||
{
|
{
|
||||||
var attributes = new Attributes();
|
var attributes = new Attributes();
|
||||||
|
|
||||||
|
@ -225,11 +225,22 @@ namespace SafeExamBrowser.Server
|
||||||
case Instructions.PROCTORING_RECONFIGURATION:
|
case Instructions.PROCTORING_RECONFIGURATION:
|
||||||
ParseReconfigurationInstruction(attributes, attributesJson);
|
ParseReconfigurationInstruction(attributes, attributesJson);
|
||||||
break;
|
break;
|
||||||
|
case Instructions.LOCK_SCREEN:
|
||||||
|
ParseLockScreenInstruction(attributes, attributesJson);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ParseLockScreenInstruction(Attributes attributes, JObject attributesJson)
|
||||||
|
{
|
||||||
|
if (attributesJson.ContainsKey("message"))
|
||||||
|
{
|
||||||
|
attributes.Message = attributesJson["message"].Value<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson)
|
private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson)
|
||||||
{
|
{
|
||||||
if (attributesJson.ContainsKey("id"))
|
if (attributesJson.ContainsKey("id"))
|
||||||
|
|
|
@ -54,16 +54,21 @@ namespace SafeExamBrowser.Server
|
||||||
private bool connectedToPowergrid;
|
private bool connectedToPowergrid;
|
||||||
private int currentWlanValue;
|
private int currentWlanValue;
|
||||||
private string examId;
|
private string examId;
|
||||||
private int handNotificationId;
|
private int notificationId;
|
||||||
|
private int currentRaisHandId;
|
||||||
|
private int currentLockScreenId;
|
||||||
|
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
private string oauth2Token;
|
private string oauth2Token;
|
||||||
private int pingNumber;
|
private int pingNumber;
|
||||||
private ServerSettings settings;
|
private ServerSettings settings;
|
||||||
|
|
||||||
public event ServerEventHandler HandConfirmed;
|
public event ServerEventHandler HandConfirmed;
|
||||||
|
public event ServerEventHandler LockScreenConfirmed;
|
||||||
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
||||||
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
||||||
public event TerminationRequestedEventHandler TerminationRequested;
|
public event TerminationRequestedEventHandler TerminationRequested;
|
||||||
|
public event LockScreenRequestedEventHandler LockScreenRequested;
|
||||||
|
|
||||||
public ServerProxy(
|
public ServerProxy(
|
||||||
AppConfig appConfig,
|
AppConfig appConfig,
|
||||||
|
@ -242,7 +247,7 @@ namespace SafeExamBrowser.Server
|
||||||
{
|
{
|
||||||
["type"] = "NOTIFICATION_CONFIRMED",
|
["type"] = "NOTIFICATION_CONFIRMED",
|
||||||
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
|
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
|
||||||
["numericValue"] = handNotificationId,
|
["numericValue"] = currentRaisHandId,
|
||||||
};
|
};
|
||||||
var content = json.ToString();
|
var content = json.ToString();
|
||||||
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
|
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
|
||||||
|
@ -259,6 +264,32 @@ namespace SafeExamBrowser.Server
|
||||||
return new ServerResponse(success, response.ToLogString());
|
return new ServerResponse(success, response.ToLogString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerResponse ConfirmLockScreen()
|
||||||
|
{
|
||||||
|
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
||||||
|
var contentType = "application/json;charset=UTF-8";
|
||||||
|
var token = ("SEBConnectionToken", connectionToken);
|
||||||
|
var json = new JObject
|
||||||
|
{
|
||||||
|
["type"] = "NOTIFICATION_CONFIRMED",
|
||||||
|
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
|
||||||
|
["numericValue"] = currentLockScreenId,
|
||||||
|
};
|
||||||
|
var content = json.ToString();
|
||||||
|
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
logger.Info("Successfully sent lock screen confirm notification.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Error("Failed to send lock screen confirm notification!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ServerResponse(success, response.ToLogString());
|
||||||
|
}
|
||||||
|
|
||||||
public void Notify(ILogContent content)
|
public void Notify(ILogContent content)
|
||||||
{
|
{
|
||||||
logContent.Enqueue(content);
|
logContent.Enqueue(content);
|
||||||
|
@ -273,7 +304,7 @@ namespace SafeExamBrowser.Server
|
||||||
{
|
{
|
||||||
["type"] = "NOTIFICATION",
|
["type"] = "NOTIFICATION",
|
||||||
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
|
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
|
||||||
["numericValue"] = ++handNotificationId,
|
["numericValue"] = currentRaisHandId = ++notificationId,
|
||||||
["text"] = $"<raisehand> {message}"
|
["text"] = $"<raisehand> {message}"
|
||||||
};
|
};
|
||||||
var content = json.ToString();
|
var content = json.ToString();
|
||||||
|
@ -291,6 +322,33 @@ namespace SafeExamBrowser.Server
|
||||||
return new ServerResponse(success, response.ToLogString());
|
return new ServerResponse(success, response.ToLogString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerResponse LockScreen(string message = null)
|
||||||
|
{
|
||||||
|
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
||||||
|
var contentType = "application/json;charset=UTF-8";
|
||||||
|
var token = ("SEBConnectionToken", connectionToken);
|
||||||
|
var json = new JObject
|
||||||
|
{
|
||||||
|
["type"] = "NOTIFICATION",
|
||||||
|
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
|
||||||
|
["numericValue"] = currentLockScreenId = ++notificationId,
|
||||||
|
["text"] = $"<lockscreen> {message}"
|
||||||
|
};
|
||||||
|
var content = json.ToString();
|
||||||
|
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
logger.Info("Successfully sent lock screen notification.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Error("Failed to send lock screen notification!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ServerResponse(success, response.ToLogString());
|
||||||
|
}
|
||||||
|
|
||||||
public ServerResponse SendSessionIdentifier(string identifier)
|
public ServerResponse SendSessionIdentifier(string identifier)
|
||||||
{
|
{
|
||||||
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
||||||
|
@ -414,9 +472,12 @@ namespace SafeExamBrowser.Server
|
||||||
{
|
{
|
||||||
switch (instruction)
|
switch (instruction)
|
||||||
{
|
{
|
||||||
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand" && attributes.Id == handNotificationId:
|
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand":
|
||||||
Task.Run(() => HandConfirmed?.Invoke());
|
Task.Run(() => HandConfirmed?.Invoke());
|
||||||
break;
|
break;
|
||||||
|
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "lockscreen":
|
||||||
|
Task.Run(() => LockScreenConfirmed?.Invoke());
|
||||||
|
break;
|
||||||
case Instructions.PROCTORING:
|
case Instructions.PROCTORING:
|
||||||
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
|
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
|
||||||
break;
|
break;
|
||||||
|
@ -426,6 +487,9 @@ namespace SafeExamBrowser.Server
|
||||||
case Instructions.QUIT:
|
case Instructions.QUIT:
|
||||||
Task.Run(() => TerminationRequested?.Invoke());
|
Task.Run(() => TerminationRequested?.Invoke());
|
||||||
break;
|
break;
|
||||||
|
case Instructions.LOCK_SCREEN:
|
||||||
|
Task.Run(() => LockScreenRequested?.Invoke(attributes.Message));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instructionConfirmation != default)
|
if (instructionConfirmation != default)
|
||||||
|
|
|
@ -15,6 +15,12 @@ namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LockScreenResult
|
public class LockScreenResult
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is been set if the lock screen was canceled from another process (E.g.: from SEB Server instruction)
|
||||||
|
/// </summary>
|
||||||
|
public bool Canceled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The identifier of the option selected by the user, if available.
|
/// The identifier of the option selected by the user, if available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -19,5 +19,9 @@ namespace SafeExamBrowser.UserInterface.Contracts.Windows
|
||||||
/// Waits for the user to provide the required input to unlock the application.
|
/// Waits for the user to provide the required input to unlock the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LockScreenResult WaitForResult();
|
LockScreenResult WaitForResult();
|
||||||
|
/// <summary>
|
||||||
|
/// This cancel a waiting thread for LockScreenResult and force the lock screen to close
|
||||||
|
/// </summary>
|
||||||
|
void Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
|
||||||
{
|
{
|
||||||
private readonly AutoResetEvent autoResetEvent;
|
private readonly AutoResetEvent autoResetEvent;
|
||||||
private readonly IText text;
|
private readonly IText text;
|
||||||
|
private bool canceled = false;
|
||||||
|
|
||||||
private IList<Window> windows;
|
private IList<Window> windows;
|
||||||
|
|
||||||
|
@ -85,6 +86,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
result.Canceled = canceled;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,5 +174,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
|
||||||
autoResetEvent.Set();
|
autoResetEvent.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
canceled = true;
|
||||||
|
autoResetEvent.Set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||||
{
|
{
|
||||||
private readonly AutoResetEvent autoResetEvent;
|
private readonly AutoResetEvent autoResetEvent;
|
||||||
private readonly IText text;
|
private readonly IText text;
|
||||||
|
private bool canceled = false;
|
||||||
|
|
||||||
private IList<Window> windows;
|
private IList<Window> windows;
|
||||||
|
|
||||||
|
@ -85,6 +86,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
result.Canceled = canceled;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,5 +174,11 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||||
autoResetEvent.Set();
|
autoResetEvent.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
canceled = true;
|
||||||
|
autoResetEvent.Set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue