SEBWIN-516: Implemented raise hand feature.
This commit is contained in:
parent
bda36647f3
commit
8a3039ec16
36 changed files with 992 additions and 4 deletions
|
@ -114,7 +114,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
public void MustFilterContentRequests()
|
||||
{
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "www.test.org";
|
||||
var url = "http://www.test.org";
|
||||
|
||||
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
|
||||
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
|
||||
|
|
|
@ -12,6 +12,7 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
|||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
|
@ -56,6 +57,12 @@ namespace SafeExamBrowser.Client.Operations
|
|||
controller.Initialize(Context.Settings.Proctoring);
|
||||
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.ActionCenter));
|
||||
|
||||
if (Context.Settings.SessionMode == SessionMode.Server && Context.Settings.Proctoring.ShowRaiseHandNotification)
|
||||
{
|
||||
actionCenter.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.ActionCenter, Context.Settings.Proctoring));
|
||||
taskbar.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.Taskbar, Context.Settings.Proctoring));
|
||||
}
|
||||
|
||||
if (Context.Settings.Proctoring.ShowTaskbarNotification)
|
||||
{
|
||||
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.Taskbar));
|
||||
|
|
|
@ -36,7 +36,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
|
|||
|
||||
raw.Add(Keys.Browser.ShowReloadButton, true);
|
||||
settings.Browser.AdditionalWindow.AllowReloading = false;
|
||||
settings.Browser.AdditionalWindow.ShowReloadButton = false;
|
||||
settings.Browser.MainWindow.AllowReloading = false;
|
||||
settings.Browser.MainWindow.ShowReloadButton = false;
|
||||
|
||||
sut.Process(raw, settings);
|
||||
|
||||
|
@ -44,7 +46,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
|
|||
Assert.IsFalse(settings.Browser.MainWindow.ShowToolbar);
|
||||
|
||||
settings.Browser.AdditionalWindow.AllowReloading = true;
|
||||
settings.Browser.AdditionalWindow.ShowReloadButton = true;
|
||||
settings.Browser.MainWindow.AllowReloading = true;
|
||||
settings.Browser.MainWindow.ShowReloadButton = true;
|
||||
|
||||
sut.Process(raw, settings);
|
||||
|
||||
|
|
|
@ -219,6 +219,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
|||
settings.Mouse.AllowRightButton = true;
|
||||
|
||||
settings.Proctoring.Enabled = false;
|
||||
settings.Proctoring.ForceRaiseHandMessage = false;
|
||||
settings.Proctoring.JitsiMeet.AllowChat = false;
|
||||
settings.Proctoring.JitsiMeet.AllowClosedCaptions = false;
|
||||
settings.Proctoring.JitsiMeet.AllowRaiseHand = false;
|
||||
|
@ -233,6 +234,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
|||
settings.Proctoring.JitsiMeet.SendVideo = true;
|
||||
settings.Proctoring.JitsiMeet.ShowMeetingName = false;
|
||||
settings.Proctoring.JitsiMeet.VideoMuted = false;
|
||||
settings.Proctoring.ShowRaiseHandNotification = true;
|
||||
settings.Proctoring.ShowTaskbarNotification = true;
|
||||
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
|
||||
settings.Proctoring.Zoom.AllowChat = false;
|
||||
|
|
|
@ -137,7 +137,11 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
Notification_AboutTooltip,
|
||||
Notification_LogTooltip,
|
||||
Notification_ProctoringActiveTooltip,
|
||||
Notification_ProctoringHandLowered,
|
||||
Notification_ProctoringHandRaised,
|
||||
Notification_ProctoringInactiveTooltip,
|
||||
Notification_ProctoringLowerHand,
|
||||
Notification_ProctoringRaiseHand,
|
||||
OperationStatus_CloseRuntimeConnection,
|
||||
OperationStatus_EmptyClipboard,
|
||||
OperationStatus_FinalizeApplications,
|
||||
|
|
|
@ -369,9 +369,21 @@
|
|||
<Entry key="Notification_ProctoringActiveTooltip">
|
||||
Fernüberwachung ist aktiv
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandLowered">
|
||||
Hand ist gesenkt
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandRaised">
|
||||
Hand ist erhoben
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||
Fernüberwachung ist nicht aktiv
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringLowerHand">
|
||||
Hand senken
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringRaiseHand">
|
||||
Hand erheben
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||
Schliesse Verbindung zur Runtime
|
||||
</Entry>
|
||||
|
|
|
@ -369,9 +369,21 @@
|
|||
<Entry key="Notification_ProctoringActiveTooltip">
|
||||
Remote proctoring is active
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandLowered">
|
||||
Hand is lowered
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandRaised">
|
||||
Hand is raised
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||
Remote proctoring is inactive
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringLowerHand">
|
||||
Lower hand
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringRaiseHand">
|
||||
Raise hand
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||
Closing runtime connection
|
||||
</Entry>
|
||||
|
|
|
@ -369,9 +369,21 @@
|
|||
<Entry key="Notification_ProctoringActiveTooltip">
|
||||
La surveillance à distance est active
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandLowered">
|
||||
La main est baissée
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandRaised">
|
||||
La main est levée
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||
La surveillance à distance est inactive
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringLowerHand">
|
||||
Baisser la main
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringRaiseHand">
|
||||
Lever la main
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||
Fermeture de la connexion
|
||||
</Entry>
|
||||
|
|
|
@ -369,9 +369,21 @@
|
|||
<Entry key="Notification_ProctoringActiveTooltip">
|
||||
Il proctoring remoto è attivo
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandLowered">
|
||||
La mano è abbassata
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandRaised">
|
||||
La mano è alzata
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||
Il proctoring remoto è inattivo
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringLowerHand">
|
||||
Abbassa la mano
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringRaiseHand">
|
||||
Alzi la mano
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||
Chiusura della connessione runtime
|
||||
</Entry>
|
||||
|
|
|
@ -333,9 +333,21 @@
|
|||
<Entry key="Notification_ProctoringActiveTooltip">
|
||||
远程监理处于活动状态
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandLowered">
|
||||
手被放下
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringHandRaised">
|
||||
举手了
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||
远程监理处于不活动状态
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringLowerHand">
|
||||
把手放低
|
||||
</Entry>
|
||||
<Entry key="Notification_ProctoringRaiseHand">
|
||||
举手
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||
关闭运行时连接
|
||||
</Entry>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.Proctoring.Contracts.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// The default event handler for proctoring events.
|
||||
/// </summary>
|
||||
public delegate void ProctoringEventHandler();
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
|
||||
namespace SafeExamBrowser.Proctoring.Contracts
|
||||
|
@ -15,11 +16,36 @@ namespace SafeExamBrowser.Proctoring.Contracts
|
|||
/// </summary>
|
||||
public interface IProctoringController
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the hand is currently raised.
|
||||
/// </summary>
|
||||
bool IsHandRaised { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the hand has been lowered.
|
||||
/// </summary>
|
||||
event ProctoringEventHandler HandLowered;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the hand has been raised.
|
||||
/// </summary>
|
||||
event ProctoringEventHandler HandRaised;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the given settings and starts the proctoring if the settings are valid.
|
||||
/// </summary>
|
||||
void Initialize(ProctoringSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// Lowers the hand.
|
||||
/// </summary>
|
||||
void LowerHand();
|
||||
|
||||
/// <summary>
|
||||
/// Raises the hand, optionally with the given message.
|
||||
/// </summary>
|
||||
void RaiseHand(string message = default(string));
|
||||
|
||||
/// <summary>
|
||||
/// Stops the proctoring functionality.
|
||||
/// </summary>
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Events\ProctoringEventHandler.cs" />
|
||||
<Compile Include="IProctoringController.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -19,6 +19,7 @@ using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
|||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Events;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
|
@ -44,8 +45,11 @@ namespace SafeExamBrowser.Proctoring
|
|||
private WindowVisibility windowVisibility;
|
||||
|
||||
public IconResource IconResource { get; set; }
|
||||
public bool IsHandRaised { get; private set; }
|
||||
public string Tooltip { get; set; }
|
||||
|
||||
public event ProctoringEventHandler HandLowered;
|
||||
public event ProctoringEventHandler HandRaised;
|
||||
public event NotificationChangedEventHandler NotificationChanged;
|
||||
|
||||
public ProctoringController(
|
||||
|
@ -86,6 +90,7 @@ namespace SafeExamBrowser.Proctoring
|
|||
this.settings = settings;
|
||||
this.windowVisibility = settings.WindowVisibility;
|
||||
|
||||
server.HandConfirmed += Server_HandConfirmed;
|
||||
server.ProctoringConfigurationReceived += Server_ProctoringConfigurationReceived;
|
||||
server.ProctoringInstructionReceived += Server_ProctoringInstructionReceived;
|
||||
|
||||
|
@ -110,11 +115,51 @@ namespace SafeExamBrowser.Proctoring
|
|||
}
|
||||
}
|
||||
|
||||
public void LowerHand()
|
||||
{
|
||||
var response = server.LowerHand();
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
IsHandRaised = false;
|
||||
HandLowered?.Invoke();
|
||||
logger.Info("Hand lowered.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to send lower hand notification to server! Message: {response.Message}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void RaiseHand(string message = null)
|
||||
{
|
||||
var response = server.RaiseHand(message);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
IsHandRaised = true;
|
||||
HandRaised?.Invoke();
|
||||
logger.Info("Hand raised.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to send raise hand notification to server! Message: {response.Message}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
StopProctoring();
|
||||
}
|
||||
|
||||
private void Server_HandConfirmed()
|
||||
{
|
||||
logger.Info("Hand confirmation received.");
|
||||
|
||||
IsHandRaised = false;
|
||||
HandLowered?.Invoke();
|
||||
}
|
||||
|
||||
private void Server_ProctoringInstructionReceived(ProctoringInstructionEventArgs args)
|
||||
{
|
||||
logger.Info("Proctoring instruction received.");
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.Server.Contracts.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// The default event handler for server events.
|
||||
/// </summary>
|
||||
public delegate void ServerEventHandler();
|
||||
}
|
|
@ -20,17 +20,22 @@ namespace SafeExamBrowser.Server.Contracts
|
|||
public interface IServerProxy
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired when the server receives new proctoring configuration values.
|
||||
/// Event fired when the proxy receives a confirmation for a raise hand notification.
|
||||
/// </summary>
|
||||
event ServerEventHandler HandConfirmed;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the proxy receives new proctoring configuration values.
|
||||
/// </summary>
|
||||
event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the server receives a proctoring instruction.
|
||||
/// Event fired when the proxy receives a proctoring instruction.
|
||||
/// </summary>
|
||||
event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the server detects an instruction to terminate SEB.
|
||||
/// Event fired when the proxy detects an instruction to terminate SEB.
|
||||
/// </summary>
|
||||
event TerminationRequestedEventHandler TerminationRequested;
|
||||
|
||||
|
@ -69,6 +74,11 @@ namespace SafeExamBrowser.Server.Contracts
|
|||
/// </summary>
|
||||
void Initialize(string api, string connectionToken, string examId, string oauth2Token, ServerSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a lower hand notification to the server.
|
||||
/// </summary>
|
||||
ServerResponse LowerHand();
|
||||
|
||||
/// <summary>
|
||||
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
|
||||
/// </summary>
|
||||
|
@ -83,5 +93,10 @@ namespace SafeExamBrowser.Server.Contracts
|
|||
/// Stops sending ping and log data to the server.
|
||||
/// </summary>
|
||||
void StopConnectivity();
|
||||
|
||||
/// <summary>
|
||||
/// Sends a raise hand notification to the server.
|
||||
/// </summary>
|
||||
ServerResponse RaiseHand(string message = default(string));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
<Compile Include="Events\ProctoringConfigurationReceivedEventHandler.cs" />
|
||||
<Compile Include="Events\ProctoringInstructionEventArgs.cs" />
|
||||
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />
|
||||
<Compile Include="Events\ServerEventHandler.cs" />
|
||||
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
|
||||
<Compile Include="IServerProxy.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -13,9 +13,11 @@ namespace SafeExamBrowser.Server.Data
|
|||
internal class Attributes
|
||||
{
|
||||
internal bool AllowChat { get; set; }
|
||||
internal int Id { get; set; }
|
||||
internal ProctoringInstructionEventArgs Instruction { get; set; }
|
||||
internal bool ReceiveAudio { get; set; }
|
||||
internal bool ReceiveVideo { get; set; }
|
||||
internal string Type { get; set; }
|
||||
|
||||
internal Attributes()
|
||||
{
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace SafeExamBrowser.Server.Data
|
|||
{
|
||||
internal sealed class Instructions
|
||||
{
|
||||
internal const string NOTIFICATION_CONFIRM = "NOTIFICATION_CONFIRM";
|
||||
internal const string PROCTORING = "SEB_PROCTORING";
|
||||
internal const string PROCTORING_RECONFIGURATION = "SEB_RECONFIGURE_SETTINGS";
|
||||
internal const string QUIT = "SEB_QUIT";
|
||||
|
|
|
@ -179,6 +179,9 @@ namespace SafeExamBrowser.Server
|
|||
|
||||
switch (instruction)
|
||||
{
|
||||
case Instructions.NOTIFICATION_CONFIRM:
|
||||
ParseNotificationConfirmation(attributes, attributesJson);
|
||||
break;
|
||||
case Instructions.PROCTORING:
|
||||
ParseProctoringInstruction(attributes, attributesJson);
|
||||
break;
|
||||
|
@ -190,6 +193,19 @@ namespace SafeExamBrowser.Server
|
|||
return attributes;
|
||||
}
|
||||
|
||||
private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson)
|
||||
{
|
||||
if (attributesJson.ContainsKey("id"))
|
||||
{
|
||||
attributes.Id = attributesJson["id"].Value<int>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("type"))
|
||||
{
|
||||
attributes.Type = attributesJson["type"].Value<string>();
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseProctoringInstruction(Attributes attributes, JObject attributesJson)
|
||||
{
|
||||
var provider = attributesJson["service-type"].Value<string>();
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace SafeExamBrowser.Server
|
|||
private int currentPowerSupplyValue;
|
||||
private int currentWlanValue;
|
||||
private string examId;
|
||||
private int handNotificationId;
|
||||
private HttpClient httpClient;
|
||||
private ConcurrentQueue<string> instructionConfirmations;
|
||||
private ILogger logger;
|
||||
|
@ -53,6 +54,7 @@ namespace SafeExamBrowser.Server
|
|||
private ServerSettings settings;
|
||||
private IWirelessAdapter wirelessAdapter;
|
||||
|
||||
public event ServerEventHandler HandConfirmed;
|
||||
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
||||
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
||||
public event TerminationRequestedEventHandler TerminationRequested;
|
||||
|
@ -234,11 +236,64 @@ namespace SafeExamBrowser.Server
|
|||
Initialize(settings);
|
||||
}
|
||||
|
||||
public ServerResponse LowerHand()
|
||||
{
|
||||
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"] = handNotificationId,
|
||||
};
|
||||
var content = json.ToString();
|
||||
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Successfully sent lower hand notification.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to send lower hand notification!");
|
||||
}
|
||||
|
||||
return new ServerResponse(success, response.ToLogString());
|
||||
}
|
||||
|
||||
public void Notify(ILogContent content)
|
||||
{
|
||||
logContent.Enqueue(content);
|
||||
}
|
||||
|
||||
public ServerResponse RaiseHand(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"] = ++handNotificationId,
|
||||
["text"] = $"<raisehand> {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 raise hand notification.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to send raise hand notification!");
|
||||
}
|
||||
|
||||
return new ServerResponse(success, response.ToLogString());
|
||||
}
|
||||
|
||||
public ServerResponse SendSessionIdentifier(string identifier)
|
||||
{
|
||||
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
||||
|
@ -362,6 +417,9 @@ namespace SafeExamBrowser.Server
|
|||
{
|
||||
switch (instruction)
|
||||
{
|
||||
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand" && attributes.Id == handNotificationId:
|
||||
Task.Run(() => HandConfirmed?.Invoke());
|
||||
break;
|
||||
case Instructions.PROCTORING:
|
||||
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
|
||||
break;
|
||||
|
|
|
@ -21,11 +21,21 @@ namespace SafeExamBrowser.Settings.Proctoring
|
|||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the message input for the raise hand notification will be forced.
|
||||
/// </summary>
|
||||
public bool ForceRaiseHandMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All settings for remote proctoring with Jitsi Meet.
|
||||
/// </summary>
|
||||
public JitsiMeetSettings JitsiMeet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the raise hand notification will be shown in the shell.
|
||||
/// </summary>
|
||||
public bool ShowRaiseHandNotification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the proctoring notification will be shown in the taskbar.
|
||||
/// </summary>
|
||||
|
|
|
@ -12,8 +12,10 @@ using SafeExamBrowser.Configuration.Contracts;
|
|||
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||
|
@ -101,6 +103,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
|
|||
/// </summary>
|
||||
IProctoringWindow CreateProctoringWindow(IProctoringControl control);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new notification control for the raise hand functionality of a remote proctoring session.
|
||||
/// </summary>
|
||||
INotificationControl CreateRaiseHandControl(IProctoringController controller, Location location, ProctoringSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new runtime window which runs on its own thread.
|
||||
/// </summary>
|
||||
|
|
|
@ -125,6 +125,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
|
||||
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
|
||||
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
|
||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.RaiseHandControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="Gray" BorderThickness="0" >
|
||||
<StackPanel>
|
||||
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
|
||||
<Grid>
|
||||
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="NotificationButton" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
public partial class RaiseHandControl : UserControl, INotificationControl
|
||||
{
|
||||
private readonly IProctoringController controller;
|
||||
private readonly ProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
|
||||
{
|
||||
this.controller = controller;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRaiseHandControl();
|
||||
}
|
||||
|
||||
private void InitializeRaiseHandControl()
|
||||
{
|
||||
var originalBrush = Grid.Background;
|
||||
|
||||
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
|
||||
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
|
||||
|
||||
HandButton.Click += RaiseHandButton_Click;
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
|
||||
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
|
||||
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
|
||||
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
|
||||
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
|
||||
Popup.Closed += (o, args) => Grid.Background = originalBrush;
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
|
||||
private void ToggleHand()
|
||||
{
|
||||
if (controller.IsHandRaised)
|
||||
{
|
||||
controller.LowerHand();
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.RaiseHand(Message.Text);
|
||||
Message.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowLowered()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
TextBlock.Text = "L";
|
||||
}
|
||||
|
||||
private void ShowRaised()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
TextBlock.Text = "R";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.RaiseHandControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0" >
|
||||
<StackPanel>
|
||||
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
|
||||
<Grid>
|
||||
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="NotificationButton" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
public partial class RaiseHandControl : UserControl, INotificationControl
|
||||
{
|
||||
private readonly IProctoringController controller;
|
||||
private readonly ProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
|
||||
{
|
||||
this.controller = controller;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRaiseHandControl();
|
||||
}
|
||||
|
||||
private void InitializeRaiseHandControl()
|
||||
{
|
||||
var originalBrush = NotificationButton.Background;
|
||||
|
||||
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
|
||||
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
|
||||
|
||||
HandButton.Click += RaiseHandButton_Click;
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
|
||||
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
|
||||
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
|
||||
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
Background = Brushes.LightGray;
|
||||
NotificationButton.Background = Brushes.LightGray;
|
||||
};
|
||||
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Background = originalBrush;
|
||||
NotificationButton.Background = originalBrush;
|
||||
};
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
|
||||
private void ToggleHand()
|
||||
{
|
||||
if (controller.IsHandRaised)
|
||||
{
|
||||
controller.LowerHand();
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.RaiseHand(Message.Text);
|
||||
Message.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowLowered()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
TextBlock.Text = "L";
|
||||
}
|
||||
|
||||
private void ShowRaised()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
TextBlock.Text = "R";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,6 +67,12 @@
|
|||
<Reference Include="WindowsFormsIntegration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Controls\ActionCenter\RaiseHandControl.xaml.cs">
|
||||
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\Taskbar\RaiseHandControl.xaml.cs">
|
||||
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Windows\AboutWindow.xaml.cs">
|
||||
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -193,6 +199,14 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Resource>
|
||||
<Page Include="Controls\ActionCenter\RaiseHandControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Controls\Taskbar\RaiseHandControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Windows\AboutWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
@ -503,6 +517,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
|
||||
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
|
||||
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
|
||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||
|
|
|
@ -16,8 +16,10 @@ using SafeExamBrowser.Configuration.Contracts;
|
|||
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||
|
@ -168,6 +170,18 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
return Application.Current.Dispatcher.Invoke(() => new ProctoringWindow(control));
|
||||
}
|
||||
|
||||
public INotificationControl CreateRaiseHandControl(IProctoringController controller, Location location, ProctoringSettings settings)
|
||||
{
|
||||
if (location == Location.ActionCenter)
|
||||
{
|
||||
return new Controls.ActionCenter.RaiseHandControl(controller, settings, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Controls.Taskbar.RaiseHandControl(controller, settings, text);
|
||||
}
|
||||
}
|
||||
|
||||
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
|
||||
{
|
||||
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter.RaiseHandControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="82" Margin="2">
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="Gray" BorderThickness="0" >
|
||||
<StackPanel>
|
||||
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
|
||||
<Grid>
|
||||
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="NotificationButton" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
|
||||
{
|
||||
public partial class RaiseHandControl : UserControl, INotificationControl
|
||||
{
|
||||
private readonly IProctoringController controller;
|
||||
private readonly ProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
|
||||
{
|
||||
this.controller = controller;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRaiseHandControl();
|
||||
}
|
||||
|
||||
private void InitializeRaiseHandControl()
|
||||
{
|
||||
var originalBrush = Grid.Background;
|
||||
|
||||
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
|
||||
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
|
||||
|
||||
HandButton.Click += RaiseHandButton_Click;
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
|
||||
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
|
||||
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
|
||||
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
|
||||
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
|
||||
Popup.Closed += (o, args) => Grid.Background = originalBrush;
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
|
||||
private void ToggleHand()
|
||||
{
|
||||
if (controller.IsHandRaised)
|
||||
{
|
||||
controller.LowerHand();
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.RaiseHand(Message.Text);
|
||||
Message.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowLowered()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
TextBlock.Text = "L";
|
||||
}
|
||||
|
||||
private void ShowRaised()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
TextBlock.Text = "R";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar.RaiseHandControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0" >
|
||||
<StackPanel>
|
||||
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
|
||||
<Grid>
|
||||
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="NotificationButton" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="60">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
|
||||
{
|
||||
public partial class RaiseHandControl : UserControl, INotificationControl
|
||||
{
|
||||
private readonly IProctoringController controller;
|
||||
private readonly ProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
|
||||
{
|
||||
this.controller = controller;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRaiseHandControl();
|
||||
}
|
||||
|
||||
private void InitializeRaiseHandControl()
|
||||
{
|
||||
var originalBrush = NotificationButton.Background;
|
||||
|
||||
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
|
||||
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
|
||||
|
||||
HandButton.Click += RaiseHandButton_Click;
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
|
||||
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
|
||||
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
|
||||
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
Background = Brushes.LightGray;
|
||||
NotificationButton.Background = Brushes.LightGray;
|
||||
};
|
||||
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Background = originalBrush;
|
||||
NotificationButton.Background = originalBrush;
|
||||
};
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
|
||||
private void ToggleHand()
|
||||
{
|
||||
if (controller.IsHandRaised)
|
||||
{
|
||||
controller.LowerHand();
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.RaiseHand(Message.Text);
|
||||
Message.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowLowered()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
TextBlock.Text = "L";
|
||||
}
|
||||
|
||||
private void ShowRaised()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
TextBlock.Text = "R";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,6 +68,12 @@
|
|||
<Reference Include="WindowsFormsIntegration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Controls\ActionCenter\RaiseHandControl.xaml.cs">
|
||||
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\Taskbar\RaiseHandControl.xaml.cs">
|
||||
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Windows\AboutWindow.xaml.cs">
|
||||
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -217,6 +223,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
|
||||
<Project>{8E52BD1C-0540-4F16-B181-6665D43F7A7B}</Project>
|
||||
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
|
||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||
|
@ -247,6 +257,14 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Resource>
|
||||
<Page Include="Controls\ActionCenter\RaiseHandControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Controls\Taskbar\RaiseHandControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Windows\AboutWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
|
|
@ -16,8 +16,10 @@ using SafeExamBrowser.Configuration.Contracts;
|
|||
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||
|
@ -168,6 +170,18 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
|||
return Application.Current.Dispatcher.Invoke(() => new ProctoringWindow(control));
|
||||
}
|
||||
|
||||
public INotificationControl CreateRaiseHandControl(IProctoringController controller, Location location, ProctoringSettings settings)
|
||||
{
|
||||
if (location == Location.ActionCenter)
|
||||
{
|
||||
return new Controls.ActionCenter.RaiseHandControl(controller, settings, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Controls.Taskbar.RaiseHandControl(controller, settings, text);
|
||||
}
|
||||
}
|
||||
|
||||
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
|
||||
{
|
||||
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));
|
||||
|
|
Loading…
Reference in a new issue