SEBWIN-450: Completed basic implementation of proctoring window.

This commit is contained in:
Damian Büchel 2021-03-23 21:12:47 +01:00
parent a3a5d42f98
commit 31a16caa87
15 changed files with 205 additions and 28 deletions

View file

@ -16,6 +16,7 @@ using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Proxy; using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.Settings.Security; using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.Settings.Service; using SafeExamBrowser.Settings.Service;
using SafeExamBrowser.Settings.UserInterface; using SafeExamBrowser.Settings.UserInterface;
@ -174,6 +175,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Mouse.AllowMiddleButton = false; settings.Mouse.AllowMiddleButton = false;
settings.Mouse.AllowRightButton = true; settings.Mouse.AllowRightButton = true;
settings.Proctoring.Enabled = false;
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
settings.Security.AllowApplicationLogAccess = false; settings.Security.AllowApplicationLogAccess = false;
settings.Security.AllowTermination = true; settings.Security.AllowTermination = true;
settings.Security.AllowReconfiguration = false; settings.Security.AllowReconfiguration = false;

View file

@ -216,6 +216,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal static class Proctoring internal static class Proctoring
{ {
internal const string WindowVisibility = "remoteProctoringViewShow";
internal static class JitsiMeet internal static class JitsiMeet
{ {
internal const string Enabled = "jitsiMeetEnable"; internal const string Enabled = "jitsiMeetEnable";

View file

@ -9,12 +9,23 @@
var domain = "%%_DOMAIN_%%"; var domain = "%%_DOMAIN_%%";
var options = { var options = {
configOverwrite: { configOverwrite: {
disable1On1Mode: true,
startAudioOnly: false, startAudioOnly: false,
startWithAudioMuted: true, startWithAudioMuted: true,
startWithVideoMuted: false, startWithVideoMuted: false
disable1On1Mode: true
}, },
height: "100%", height: "100%",
interfaceConfigOverwrite: {
JITSI_WATERMARK_LINK: '',
SHOW_JITSI_WATERMARK: false,
TOOLBAR_BUTTONS: [
'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
'livestreaming', 'etherpad', /*'sharedvideo',*/ 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
]
},
jwt: "%%_TOKEN_%%", jwt: "%%_TOKEN_%%",
parentNode: document.querySelector('#placeholder'), parentNode: document.querySelector('#placeholder'),
roomName: "%%_ROOM_NAME_%%", roomName: "%%_ROOM_NAME_%%",

View file

@ -10,6 +10,7 @@ using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf; using Microsoft.Web.WebView2.Wpf;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Proctoring; using SafeExamBrowser.UserInterface.Contracts.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Proctoring.Events;
namespace SafeExamBrowser.Proctoring namespace SafeExamBrowser.Proctoring
{ {
@ -17,12 +18,34 @@ namespace SafeExamBrowser.Proctoring
{ {
private readonly ILogger logger; private readonly ILogger logger;
public event FullScreenChangedEventHandler FullScreenChanged;
internal ProctoringControl(ILogger logger) internal ProctoringControl(ILogger logger)
{ {
this.logger = logger; this.logger = logger;
CoreWebView2InitializationCompleted += ProctoringControl_CoreWebView2InitializationCompleted; CoreWebView2InitializationCompleted += ProctoringControl_CoreWebView2InitializationCompleted;
} }
private void ProctoringControl_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
CoreWebView2.ContainsFullScreenElementChanged += CoreWebView2_ContainsFullScreenElementChanged;
CoreWebView2.PermissionRequested += CoreWebView2_PermissionRequested;
logger.Info("Successfully initialized.");
}
else
{
logger.Error("Failed to initialize!", e.InitializationException);
}
}
private void CoreWebView2_ContainsFullScreenElementChanged(object sender, object e)
{
FullScreenChanged?.Invoke(CoreWebView2.ContainsFullScreenElement);
logger.Debug($"Full screen ${(CoreWebView2.ContainsFullScreenElement ? "activated" : "deactivated")}.");
}
private void CoreWebView2_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs e) private void CoreWebView2_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs e)
{ {
if (e.PermissionKind == CoreWebView2PermissionKind.Camera || e.PermissionKind == CoreWebView2PermissionKind.Microphone) if (e.PermissionKind == CoreWebView2PermissionKind.Camera || e.PermissionKind == CoreWebView2PermissionKind.Microphone)
@ -35,18 +58,5 @@ namespace SafeExamBrowser.Proctoring
logger.Info($"Denied access to {e.PermissionKind}."); logger.Info($"Denied access to {e.PermissionKind}.");
} }
} }
private void ProctoringControl_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
CoreWebView2.PermissionRequested += CoreWebView2_PermissionRequested;
logger.Info("Successfully initialized.");
}
else
{
logger.Error("Failed to initialize!", e.InitializationException);
}
}
} }
} }

View file

@ -30,6 +30,7 @@ namespace SafeExamBrowser.Proctoring
private string filePath; private string filePath;
private IProctoringWindow window; private IProctoringWindow window;
private ProctoringSettings settings;
public string Tooltip => "TODO!!!"; public string Tooltip => "TODO!!!";
public IconResource IconResource => new XamlIconResource(); public IconResource IconResource => new XamlIconResource();
@ -44,11 +45,20 @@ namespace SafeExamBrowser.Proctoring
public void Activate() public void Activate()
{ {
window?.Show(); if (settings.WindowVisibility == WindowVisibility.Visible)
{
window?.BringToForeground();
}
else if (settings.WindowVisibility == WindowVisibility.AllowToHide || settings.WindowVisibility == WindowVisibility.AllowToShow)
{
window.Toggle();
}
} }
public void Initialize(ProctoringSettings settings) public void Initialize(ProctoringSettings settings)
{ {
this.settings = settings;
if (settings.JitsiMeet.Enabled || settings.Zoom.Enabled) if (settings.JitsiMeet.Enabled || settings.Zoom.Enabled)
{ {
var content = LoadContent(settings); var content = LoadContent(settings);
@ -63,7 +73,12 @@ namespace SafeExamBrowser.Proctoring
}); });
window = uiFactory.CreateProctoringWindow(control); window = uiFactory.CreateProctoringWindow(control);
window.Show();
if (settings.WindowVisibility == WindowVisibility.AllowToHide || settings.WindowVisibility == WindowVisibility.Visible)
{
window.SetTitle(settings.JitsiMeet.Enabled ? settings.JitsiMeet.Subject : settings.Zoom.UserName);
window.Show();
}
logger.Info($"Initialized proctoring with {(settings.JitsiMeet.Enabled ? "Jitsi Meet" : "Zoom")}."); logger.Info($"Initialized proctoring with {(settings.JitsiMeet.Enabled ? "Jitsi Meet" : "Zoom")}.");
} }
@ -73,9 +88,13 @@ namespace SafeExamBrowser.Proctoring
} }
} }
public void Terminate() void INotification.Terminate()
{ {
window?.Close(); window?.Close();
}
void IProctoringController.Terminate()
{
fileSystem.Delete(filePath); fileSystem.Delete(filePath);
} }

View file

@ -26,6 +26,11 @@ namespace SafeExamBrowser.Settings.Proctoring
/// </summary> /// </summary>
public JitsiMeetSettings JitsiMeet { get; set; } public JitsiMeetSettings JitsiMeet { get; set; }
/// <summary>
/// Determines the visibility of the proctoring window.
/// </summary>
public WindowVisibility WindowVisibility { get; set; }
/// <summary> /// <summary>
/// All settings for remote proctoring with Zoom. /// All settings for remote proctoring with Zoom.
/// </summary> /// </summary>

View file

@ -0,0 +1,36 @@
/*
* 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.Settings.Proctoring
{
/// <summary>
/// Defines all possible visibility states for the proctoring window.
/// </summary>
public enum WindowVisibility
{
/// <summary>
/// The proctoring window is hidden and cannot be shown by the user.
/// </summary>
Hidden,
/// <summary>
/// The proctoring window is initially hidden but may be shown by the user.
/// </summary>
AllowToShow,
/// <summary>
/// The proctoring window is initially visible but may be hidden by the user.
/// </summary>
AllowToHide,
/// <summary>
/// The proctoring window is always visible and cannot be hidden by the user.
/// </summary>
Visible
}
}

View file

@ -73,6 +73,7 @@
<Compile Include="ConfigurationMode.cs" /> <Compile Include="ConfigurationMode.cs" />
<Compile Include="Proctoring\JitsiMeetSettings.cs" /> <Compile Include="Proctoring\JitsiMeetSettings.cs" />
<Compile Include="Proctoring\ProctoringSettings.cs" /> <Compile Include="Proctoring\ProctoringSettings.cs" />
<Compile Include="Proctoring\WindowVisibility.cs" />
<Compile Include="Proctoring\ZoomSettings.cs" /> <Compile Include="Proctoring\ZoomSettings.cs" />
<Compile Include="SessionMode.cs" /> <Compile Include="SessionMode.cs" />
<Compile Include="Security\KioskMode.cs" /> <Compile Include="Security\KioskMode.cs" />

View file

@ -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.UserInterface.Contracts.Proctoring.Events
{
/// <summary>
/// Indicates that the full screen state has changed.
/// </summary>
public delegate void FullScreenChangedEventHandler(bool fullScreen);
}

View file

@ -6,13 +6,19 @@
* 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.UserInterface.Contracts.Proctoring.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Proctoring namespace SafeExamBrowser.UserInterface.Contracts.Proctoring
{ {
/// <summary> /// <summary>
/// /// Defines the functionality of a proctoring control, i.e. a web view running a WebRTC-enabled web application, which normally is embedded in
/// a <see cref="IProctoringWindow"/>.
/// </summary> /// </summary>
public interface IProctoringControl public interface IProctoringControl
{ {
/// <summary>
/// Event fired when the full screen state changed.
/// </summary>
event FullScreenChangedEventHandler FullScreenChanged;
} }
} }

View file

@ -11,10 +11,18 @@ using SafeExamBrowser.UserInterface.Contracts.Windows;
namespace SafeExamBrowser.UserInterface.Contracts.Proctoring namespace SafeExamBrowser.UserInterface.Contracts.Proctoring
{ {
/// <summary> /// <summary>
/// /// Defines the functionality of a proctoring window.
/// </summary> /// </summary>
public interface IProctoringWindow : IWindow public interface IProctoringWindow : IWindow
{ {
/// <summary>
/// Sets the window title to the given value.
/// </summary>
void SetTitle(string title);
/// <summary>
/// Toggles the window visibility.
/// </summary>
void Toggle();
} }
} }

View file

@ -67,6 +67,7 @@
<Compile Include="FileSystemDialog\FileSystemElement.cs" /> <Compile Include="FileSystemDialog\FileSystemElement.cs" />
<Compile Include="FileSystemDialog\FileSystemOperation.cs" /> <Compile Include="FileSystemDialog\FileSystemOperation.cs" />
<Compile Include="FileSystemDialog\IFileSystemDialog.cs" /> <Compile Include="FileSystemDialog\IFileSystemDialog.cs" />
<Compile Include="Proctoring\Events\FullScreenChangedEventHandler.cs" />
<Compile Include="Proctoring\IProctoringControl.cs" /> <Compile Include="Proctoring\IProctoringControl.cs" />
<Compile Include="Proctoring\IProctoringWindow.cs" /> <Compile Include="Proctoring\IProctoringWindow.cs" />
<Compile Include="IProgressIndicator.cs" /> <Compile Include="IProgressIndicator.cs" />

View file

@ -4,5 +4,5 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Windows" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Windows"
mc:Ignorable="d" Height="250" Width="350" Topmost="True" WindowStyle="None"> mc:Ignorable="d" Height="250" Width="350" MinHeight="250" MinWidth="250" Topmost="True" WindowStyle="ToolWindow">
</Window> </Window>

View file

@ -11,6 +11,7 @@ using System.Windows;
using SafeExamBrowser.UserInterface.Contracts.Proctoring; using SafeExamBrowser.UserInterface.Contracts.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events; using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Windows namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
@ -47,6 +48,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
Closing -= ProctoringWindow_Closing;
closing?.Invoke(); closing?.Invoke();
base.Close(); base.Close();
}); });
@ -57,26 +59,67 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Dispatcher.Invoke(base.Hide); Dispatcher.Invoke(base.Hide);
} }
public void SetTitle(string title)
{
Dispatcher.Invoke(() => Title = title);
}
public new void Show() public new void Show()
{ {
Dispatcher.Invoke(base.Show); Dispatcher.Invoke(base.Show);
} }
private void ProctoringWindow_Closing(object sender, CancelEventArgs e) public void Toggle()
{ {
closing?.Invoke(); Dispatcher.Invoke(() =>
{
if (Visibility == Visibility.Visible)
{
base.Hide();
}
else
{
base.Show();
}
});
} }
private void InitializeWindow(object control) private void InitializeWindow(IProctoringControl control)
{ {
if (control is UIElement element) if (control is UIElement element)
{ {
Content = element; Content = element;
control.FullScreenChanged += Control_FullScreenChanged;
} }
Closing += ProctoringWindow_Closing; Closing += ProctoringWindow_Closing;
Top = SystemParameters.WorkArea.Height - Height; Loaded += ProctoringWindow_Loaded;
Left = SystemParameters.WorkArea.Width - Width; Top = SystemParameters.WorkArea.Height - Height - 15;
Left = SystemParameters.WorkArea.Width - Width - 20;
}
private void Control_FullScreenChanged(bool fullScreen)
{
if (fullScreen)
{
WindowState = WindowState.Maximized;
WindowStyle = WindowStyle.None;
}
else
{
WindowState = WindowState.Normal;
WindowStyle = WindowStyle.ToolWindow;
}
}
private void ProctoringWindow_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
}
private void ProctoringWindow_Loaded(object sender, RoutedEventArgs e)
{
this.HideCloseButton();
} }
} }
} }

View file

@ -15,10 +15,12 @@ namespace SafeExamBrowser.UserInterface.Shared.Utilities
{ {
public static class WindowExtensions public static class WindowExtensions
{ {
private const int GWL_STYLE = -16;
private const uint MF_BYCOMMAND = 0x00000000; private const uint MF_BYCOMMAND = 0x00000000;
private const uint MF_GRAYED = 0x00000001; private const uint MF_GRAYED = 0x00000001;
private const uint MF_ENABLED = 0x00000000; private const uint MF_ENABLED = 0x00000000;
private const uint SC_CLOSE = 0xF060; private const uint SC_CLOSE = 0xF060;
private const int WS_SYSMENU = 0x80000;
public static void DisableCloseButton(this Window window) public static void DisableCloseButton(this Window window)
{ {
@ -31,10 +33,24 @@ namespace SafeExamBrowser.UserInterface.Shared.Utilities
} }
} }
public static void HideCloseButton(this Window window)
{
var helper = new WindowInteropHelper(window);
var style = GetWindowLong(helper.Handle, GWL_STYLE) & ~WS_SYSMENU;
SetWindowLong(helper.Handle, GWL_STYLE, style);
}
[DllImport("user32.dll")]
private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
[DllImport("user32.dll")] [DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")] [DllImport("user32.dll")]
private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
} }
} }