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()
|
public void MustFilterContentRequests()
|
||||||
{
|
{
|
||||||
var request = new Mock<IRequest>();
|
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);
|
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
|
||||||
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
|
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
|
||||||
|
|
|
@ -12,6 +12,7 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
using SafeExamBrowser.Proctoring.Contracts;
|
using SafeExamBrowser.Proctoring.Contracts;
|
||||||
|
using SafeExamBrowser.Settings;
|
||||||
using SafeExamBrowser.UserInterface.Contracts;
|
using SafeExamBrowser.UserInterface.Contracts;
|
||||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||||
|
|
||||||
|
@ -56,6 +57,12 @@ namespace SafeExamBrowser.Client.Operations
|
||||||
controller.Initialize(Context.Settings.Proctoring);
|
controller.Initialize(Context.Settings.Proctoring);
|
||||||
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.ActionCenter));
|
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)
|
if (Context.Settings.Proctoring.ShowTaskbarNotification)
|
||||||
{
|
{
|
||||||
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.Taskbar));
|
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.Taskbar));
|
||||||
|
|
|
@ -36,7 +36,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
|
||||||
|
|
||||||
raw.Add(Keys.Browser.ShowReloadButton, true);
|
raw.Add(Keys.Browser.ShowReloadButton, true);
|
||||||
settings.Browser.AdditionalWindow.AllowReloading = false;
|
settings.Browser.AdditionalWindow.AllowReloading = false;
|
||||||
|
settings.Browser.AdditionalWindow.ShowReloadButton = false;
|
||||||
settings.Browser.MainWindow.AllowReloading = false;
|
settings.Browser.MainWindow.AllowReloading = false;
|
||||||
|
settings.Browser.MainWindow.ShowReloadButton = false;
|
||||||
|
|
||||||
sut.Process(raw, settings);
|
sut.Process(raw, settings);
|
||||||
|
|
||||||
|
@ -44,7 +46,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
|
||||||
Assert.IsFalse(settings.Browser.MainWindow.ShowToolbar);
|
Assert.IsFalse(settings.Browser.MainWindow.ShowToolbar);
|
||||||
|
|
||||||
settings.Browser.AdditionalWindow.AllowReloading = true;
|
settings.Browser.AdditionalWindow.AllowReloading = true;
|
||||||
|
settings.Browser.AdditionalWindow.ShowReloadButton = true;
|
||||||
settings.Browser.MainWindow.AllowReloading = true;
|
settings.Browser.MainWindow.AllowReloading = true;
|
||||||
|
settings.Browser.MainWindow.ShowReloadButton = true;
|
||||||
|
|
||||||
sut.Process(raw, settings);
|
sut.Process(raw, settings);
|
||||||
|
|
||||||
|
|
|
@ -219,6 +219,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||||
settings.Mouse.AllowRightButton = true;
|
settings.Mouse.AllowRightButton = true;
|
||||||
|
|
||||||
settings.Proctoring.Enabled = false;
|
settings.Proctoring.Enabled = false;
|
||||||
|
settings.Proctoring.ForceRaiseHandMessage = false;
|
||||||
settings.Proctoring.JitsiMeet.AllowChat = false;
|
settings.Proctoring.JitsiMeet.AllowChat = false;
|
||||||
settings.Proctoring.JitsiMeet.AllowClosedCaptions = false;
|
settings.Proctoring.JitsiMeet.AllowClosedCaptions = false;
|
||||||
settings.Proctoring.JitsiMeet.AllowRaiseHand = false;
|
settings.Proctoring.JitsiMeet.AllowRaiseHand = false;
|
||||||
|
@ -233,6 +234,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||||
settings.Proctoring.JitsiMeet.SendVideo = true;
|
settings.Proctoring.JitsiMeet.SendVideo = true;
|
||||||
settings.Proctoring.JitsiMeet.ShowMeetingName = false;
|
settings.Proctoring.JitsiMeet.ShowMeetingName = false;
|
||||||
settings.Proctoring.JitsiMeet.VideoMuted = false;
|
settings.Proctoring.JitsiMeet.VideoMuted = false;
|
||||||
|
settings.Proctoring.ShowRaiseHandNotification = true;
|
||||||
settings.Proctoring.ShowTaskbarNotification = true;
|
settings.Proctoring.ShowTaskbarNotification = true;
|
||||||
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
|
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
|
||||||
settings.Proctoring.Zoom.AllowChat = false;
|
settings.Proctoring.Zoom.AllowChat = false;
|
||||||
|
|
|
@ -137,7 +137,11 @@ namespace SafeExamBrowser.I18n.Contracts
|
||||||
Notification_AboutTooltip,
|
Notification_AboutTooltip,
|
||||||
Notification_LogTooltip,
|
Notification_LogTooltip,
|
||||||
Notification_ProctoringActiveTooltip,
|
Notification_ProctoringActiveTooltip,
|
||||||
|
Notification_ProctoringHandLowered,
|
||||||
|
Notification_ProctoringHandRaised,
|
||||||
Notification_ProctoringInactiveTooltip,
|
Notification_ProctoringInactiveTooltip,
|
||||||
|
Notification_ProctoringLowerHand,
|
||||||
|
Notification_ProctoringRaiseHand,
|
||||||
OperationStatus_CloseRuntimeConnection,
|
OperationStatus_CloseRuntimeConnection,
|
||||||
OperationStatus_EmptyClipboard,
|
OperationStatus_EmptyClipboard,
|
||||||
OperationStatus_FinalizeApplications,
|
OperationStatus_FinalizeApplications,
|
||||||
|
|
|
@ -369,9 +369,21 @@
|
||||||
<Entry key="Notification_ProctoringActiveTooltip">
|
<Entry key="Notification_ProctoringActiveTooltip">
|
||||||
Fernüberwachung ist aktiv
|
Fernüberwachung ist aktiv
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandLowered">
|
||||||
|
Hand ist gesenkt
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandRaised">
|
||||||
|
Hand ist erhoben
|
||||||
|
</Entry>
|
||||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||||
Fernüberwachung ist nicht aktiv
|
Fernüberwachung ist nicht aktiv
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringLowerHand">
|
||||||
|
Hand senken
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringRaiseHand">
|
||||||
|
Hand erheben
|
||||||
|
</Entry>
|
||||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||||
Schliesse Verbindung zur Runtime
|
Schliesse Verbindung zur Runtime
|
||||||
</Entry>
|
</Entry>
|
||||||
|
|
|
@ -369,9 +369,21 @@
|
||||||
<Entry key="Notification_ProctoringActiveTooltip">
|
<Entry key="Notification_ProctoringActiveTooltip">
|
||||||
Remote proctoring is active
|
Remote proctoring is active
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandLowered">
|
||||||
|
Hand is lowered
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandRaised">
|
||||||
|
Hand is raised
|
||||||
|
</Entry>
|
||||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||||
Remote proctoring is inactive
|
Remote proctoring is inactive
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringLowerHand">
|
||||||
|
Lower hand
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringRaiseHand">
|
||||||
|
Raise hand
|
||||||
|
</Entry>
|
||||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||||
Closing runtime connection
|
Closing runtime connection
|
||||||
</Entry>
|
</Entry>
|
||||||
|
|
|
@ -369,9 +369,21 @@
|
||||||
<Entry key="Notification_ProctoringActiveTooltip">
|
<Entry key="Notification_ProctoringActiveTooltip">
|
||||||
La surveillance à distance est active
|
La surveillance à distance est active
|
||||||
</Entry>
|
</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">
|
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||||
La surveillance à distance est inactive
|
La surveillance à distance est inactive
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringLowerHand">
|
||||||
|
Baisser la main
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringRaiseHand">
|
||||||
|
Lever la main
|
||||||
|
</Entry>
|
||||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||||
Fermeture de la connexion
|
Fermeture de la connexion
|
||||||
</Entry>
|
</Entry>
|
||||||
|
|
|
@ -369,9 +369,21 @@
|
||||||
<Entry key="Notification_ProctoringActiveTooltip">
|
<Entry key="Notification_ProctoringActiveTooltip">
|
||||||
Il proctoring remoto è attivo
|
Il proctoring remoto è attivo
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandLowered">
|
||||||
|
La mano è abbassata
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandRaised">
|
||||||
|
La mano è alzata
|
||||||
|
</Entry>
|
||||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||||
Il proctoring remoto è inattivo
|
Il proctoring remoto è inattivo
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringLowerHand">
|
||||||
|
Abbassa la mano
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringRaiseHand">
|
||||||
|
Alzi la mano
|
||||||
|
</Entry>
|
||||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||||
Chiusura della connessione runtime
|
Chiusura della connessione runtime
|
||||||
</Entry>
|
</Entry>
|
||||||
|
|
|
@ -333,9 +333,21 @@
|
||||||
<Entry key="Notification_ProctoringActiveTooltip">
|
<Entry key="Notification_ProctoringActiveTooltip">
|
||||||
远程监理处于活动状态
|
远程监理处于活动状态
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandLowered">
|
||||||
|
手被放下
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringHandRaised">
|
||||||
|
举手了
|
||||||
|
</Entry>
|
||||||
<Entry key="Notification_ProctoringInactiveTooltip">
|
<Entry key="Notification_ProctoringInactiveTooltip">
|
||||||
远程监理处于不活动状态
|
远程监理处于不活动状态
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringLowerHand">
|
||||||
|
把手放低
|
||||||
|
</Entry>
|
||||||
|
<Entry key="Notification_ProctoringRaiseHand">
|
||||||
|
举手
|
||||||
|
</Entry>
|
||||||
<Entry key="OperationStatus_CloseRuntimeConnection">
|
<Entry key="OperationStatus_CloseRuntimeConnection">
|
||||||
关闭运行时连接
|
关闭运行时连接
|
||||||
</Entry>
|
</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/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||||
using SafeExamBrowser.Settings.Proctoring;
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Proctoring.Contracts
|
namespace SafeExamBrowser.Proctoring.Contracts
|
||||||
|
@ -15,11 +16,36 @@ namespace SafeExamBrowser.Proctoring.Contracts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IProctoringController
|
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>
|
/// <summary>
|
||||||
/// Initializes the given settings and starts the proctoring if the settings are valid.
|
/// Initializes the given settings and starts the proctoring if the settings are valid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Initialize(ProctoringSettings settings);
|
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>
|
/// <summary>
|
||||||
/// Stops the proctoring functionality.
|
/// Stops the proctoring functionality.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Events\ProctoringEventHandler.cs" />
|
||||||
<Compile Include="IProctoringController.cs" />
|
<Compile Include="IProctoringController.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -19,6 +19,7 @@ using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
using SafeExamBrowser.Proctoring.Contracts;
|
using SafeExamBrowser.Proctoring.Contracts;
|
||||||
|
using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||||
using SafeExamBrowser.Server.Contracts;
|
using SafeExamBrowser.Server.Contracts;
|
||||||
using SafeExamBrowser.Server.Contracts.Events;
|
using SafeExamBrowser.Server.Contracts.Events;
|
||||||
using SafeExamBrowser.Settings.Proctoring;
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
|
@ -44,8 +45,11 @@ namespace SafeExamBrowser.Proctoring
|
||||||
private WindowVisibility windowVisibility;
|
private WindowVisibility windowVisibility;
|
||||||
|
|
||||||
public IconResource IconResource { get; set; }
|
public IconResource IconResource { get; set; }
|
||||||
|
public bool IsHandRaised { get; private set; }
|
||||||
public string Tooltip { get; set; }
|
public string Tooltip { get; set; }
|
||||||
|
|
||||||
|
public event ProctoringEventHandler HandLowered;
|
||||||
|
public event ProctoringEventHandler HandRaised;
|
||||||
public event NotificationChangedEventHandler NotificationChanged;
|
public event NotificationChangedEventHandler NotificationChanged;
|
||||||
|
|
||||||
public ProctoringController(
|
public ProctoringController(
|
||||||
|
@ -86,6 +90,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.windowVisibility = settings.WindowVisibility;
|
this.windowVisibility = settings.WindowVisibility;
|
||||||
|
|
||||||
|
server.HandConfirmed += Server_HandConfirmed;
|
||||||
server.ProctoringConfigurationReceived += Server_ProctoringConfigurationReceived;
|
server.ProctoringConfigurationReceived += Server_ProctoringConfigurationReceived;
|
||||||
server.ProctoringInstructionReceived += Server_ProctoringInstructionReceived;
|
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()
|
public void Terminate()
|
||||||
{
|
{
|
||||||
StopProctoring();
|
StopProctoring();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Server_HandConfirmed()
|
||||||
|
{
|
||||||
|
logger.Info("Hand confirmation received.");
|
||||||
|
|
||||||
|
IsHandRaised = false;
|
||||||
|
HandLowered?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
private void Server_ProctoringInstructionReceived(ProctoringInstructionEventArgs args)
|
private void Server_ProctoringInstructionReceived(ProctoringInstructionEventArgs args)
|
||||||
{
|
{
|
||||||
logger.Info("Proctoring instruction received.");
|
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
|
public interface IServerProxy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the server receives a proctoring instruction.
|
/// Event fired when the proxy receives a proctoring instruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the server detects an instruction to terminate SEB.
|
/// Event fired when the proxy detects an instruction to terminate SEB.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event TerminationRequestedEventHandler TerminationRequested;
|
event TerminationRequestedEventHandler TerminationRequested;
|
||||||
|
|
||||||
|
@ -69,6 +74,11 @@ namespace SafeExamBrowser.Server.Contracts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Initialize(string api, string connectionToken, string examId, string oauth2Token, ServerSettings settings);
|
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>
|
/// <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>
|
||||||
|
@ -83,5 +93,10 @@ namespace SafeExamBrowser.Server.Contracts
|
||||||
/// Stops sending ping and log data to the server.
|
/// Stops sending ping and log data to the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void StopConnectivity();
|
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\ProctoringConfigurationReceivedEventHandler.cs" />
|
||||||
<Compile Include="Events\ProctoringInstructionEventArgs.cs" />
|
<Compile Include="Events\ProctoringInstructionEventArgs.cs" />
|
||||||
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />
|
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />
|
||||||
|
<Compile Include="Events\ServerEventHandler.cs" />
|
||||||
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
|
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
|
||||||
<Compile Include="IServerProxy.cs" />
|
<Compile Include="IServerProxy.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
|
|
@ -13,9 +13,11 @@ namespace SafeExamBrowser.Server.Data
|
||||||
internal class Attributes
|
internal class Attributes
|
||||||
{
|
{
|
||||||
internal bool AllowChat { get; set; }
|
internal bool AllowChat { get; set; }
|
||||||
|
internal int Id { get; set; }
|
||||||
internal ProctoringInstructionEventArgs Instruction { get; set; }
|
internal ProctoringInstructionEventArgs Instruction { get; set; }
|
||||||
internal bool ReceiveAudio { get; set; }
|
internal bool ReceiveAudio { get; set; }
|
||||||
internal bool ReceiveVideo { get; set; }
|
internal bool ReceiveVideo { get; set; }
|
||||||
|
internal string Type { get; set; }
|
||||||
|
|
||||||
internal Attributes()
|
internal Attributes()
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace SafeExamBrowser.Server.Data
|
||||||
{
|
{
|
||||||
internal sealed class Instructions
|
internal sealed class Instructions
|
||||||
{
|
{
|
||||||
|
internal const string NOTIFICATION_CONFIRM = "NOTIFICATION_CONFIRM";
|
||||||
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";
|
||||||
|
|
|
@ -179,6 +179,9 @@ namespace SafeExamBrowser.Server
|
||||||
|
|
||||||
switch (instruction)
|
switch (instruction)
|
||||||
{
|
{
|
||||||
|
case Instructions.NOTIFICATION_CONFIRM:
|
||||||
|
ParseNotificationConfirmation(attributes, attributesJson);
|
||||||
|
break;
|
||||||
case Instructions.PROCTORING:
|
case Instructions.PROCTORING:
|
||||||
ParseProctoringInstruction(attributes, attributesJson);
|
ParseProctoringInstruction(attributes, attributesJson);
|
||||||
break;
|
break;
|
||||||
|
@ -190,6 +193,19 @@ namespace SafeExamBrowser.Server
|
||||||
return attributes;
|
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)
|
private void ParseProctoringInstruction(Attributes attributes, JObject attributesJson)
|
||||||
{
|
{
|
||||||
var provider = attributesJson["service-type"].Value<string>();
|
var provider = attributesJson["service-type"].Value<string>();
|
||||||
|
|
|
@ -40,6 +40,7 @@ namespace SafeExamBrowser.Server
|
||||||
private int currentPowerSupplyValue;
|
private int currentPowerSupplyValue;
|
||||||
private int currentWlanValue;
|
private int currentWlanValue;
|
||||||
private string examId;
|
private string examId;
|
||||||
|
private int handNotificationId;
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
private ConcurrentQueue<string> instructionConfirmations;
|
private ConcurrentQueue<string> instructionConfirmations;
|
||||||
private ILogger logger;
|
private ILogger logger;
|
||||||
|
@ -53,6 +54,7 @@ namespace SafeExamBrowser.Server
|
||||||
private ServerSettings settings;
|
private ServerSettings settings;
|
||||||
private IWirelessAdapter wirelessAdapter;
|
private IWirelessAdapter wirelessAdapter;
|
||||||
|
|
||||||
|
public event ServerEventHandler HandConfirmed;
|
||||||
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
|
||||||
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
|
||||||
public event TerminationRequestedEventHandler TerminationRequested;
|
public event TerminationRequestedEventHandler TerminationRequested;
|
||||||
|
@ -234,11 +236,64 @@ namespace SafeExamBrowser.Server
|
||||||
Initialize(settings);
|
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)
|
public void Notify(ILogContent content)
|
||||||
{
|
{
|
||||||
logContent.Enqueue(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)
|
public ServerResponse SendSessionIdentifier(string identifier)
|
||||||
{
|
{
|
||||||
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
||||||
|
@ -362,6 +417,9 @@ namespace SafeExamBrowser.Server
|
||||||
{
|
{
|
||||||
switch (instruction)
|
switch (instruction)
|
||||||
{
|
{
|
||||||
|
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand" && attributes.Id == handNotificationId:
|
||||||
|
Task.Run(() => HandConfirmed?.Invoke());
|
||||||
|
break;
|
||||||
case Instructions.PROCTORING:
|
case Instructions.PROCTORING:
|
||||||
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
|
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -21,11 +21,21 @@ namespace SafeExamBrowser.Settings.Proctoring
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Enabled { get; set; }
|
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>
|
/// <summary>
|
||||||
/// All settings for remote proctoring with Jitsi Meet.
|
/// All settings for remote proctoring with Jitsi Meet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JitsiMeetSettings JitsiMeet { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Determines whether the proctoring notification will be shown in the taskbar.
|
/// Determines whether the proctoring notification will be shown in the taskbar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -12,8 +12,10 @@ using SafeExamBrowser.Configuration.Contracts;
|
||||||
using SafeExamBrowser.Core.Contracts.Notifications;
|
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Proctoring.Contracts;
|
||||||
using SafeExamBrowser.Server.Contracts.Data;
|
using SafeExamBrowser.Server.Contracts.Data;
|
||||||
using SafeExamBrowser.Settings.Browser;
|
using SafeExamBrowser.Settings.Browser;
|
||||||
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||||
|
@ -101,6 +103,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IProctoringWindow CreateProctoringWindow(IProctoringControl control);
|
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>
|
/// <summary>
|
||||||
/// Creates a new runtime window which runs on its own thread.
|
/// Creates a new runtime window which runs on its own thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -125,6 +125,10 @@
|
||||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||||
</ProjectReference>
|
</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">
|
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||||
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
|
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
|
||||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
<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" />
|
<Reference Include="WindowsFormsIntegration" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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">
|
<Compile Include="Windows\AboutWindow.xaml.cs">
|
||||||
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -193,6 +199,14 @@
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</Resource>
|
</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">
|
<Page Include="Windows\AboutWindow.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
@ -503,6 +517,10 @@
|
||||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||||
</ProjectReference>
|
</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">
|
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||||
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
|
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
|
||||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||||
|
|
|
@ -16,8 +16,10 @@ using SafeExamBrowser.Configuration.Contracts;
|
||||||
using SafeExamBrowser.Core.Contracts.Notifications;
|
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Proctoring.Contracts;
|
||||||
using SafeExamBrowser.Server.Contracts.Data;
|
using SafeExamBrowser.Server.Contracts.Data;
|
||||||
using SafeExamBrowser.Settings.Browser;
|
using SafeExamBrowser.Settings.Browser;
|
||||||
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||||
|
@ -168,6 +170,18 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
||||||
return Application.Current.Dispatcher.Invoke(() => new ProctoringWindow(control));
|
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)
|
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
|
||||||
{
|
{
|
||||||
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));
|
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" />
|
<Reference Include="WindowsFormsIntegration" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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">
|
<Compile Include="Windows\AboutWindow.xaml.cs">
|
||||||
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -217,6 +223,10 @@
|
||||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||||
</ProjectReference>
|
</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">
|
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||||
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
|
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
|
||||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||||
|
@ -247,6 +257,14 @@
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</Resource>
|
</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">
|
<Page Include="Windows\AboutWindow.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
|
|
|
@ -16,8 +16,10 @@ using SafeExamBrowser.Configuration.Contracts;
|
||||||
using SafeExamBrowser.Core.Contracts.Notifications;
|
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Proctoring.Contracts;
|
||||||
using SafeExamBrowser.Server.Contracts.Data;
|
using SafeExamBrowser.Server.Contracts.Data;
|
||||||
using SafeExamBrowser.Settings.Browser;
|
using SafeExamBrowser.Settings.Browser;
|
||||||
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||||
|
@ -168,6 +170,18 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
||||||
return Application.Current.Dispatcher.Invoke(() => new ProctoringWindow(control));
|
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)
|
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
|
||||||
{
|
{
|
||||||
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));
|
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));
|
||||||
|
|
Loading…
Reference in a new issue