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

View file

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

View file

@ -9,12 +9,23 @@
var domain = "%%_DOMAIN_%%";
var options = {
configOverwrite: {
disable1On1Mode: true,
startAudioOnly: false,
startWithAudioMuted: true,
startWithVideoMuted: false,
disable1On1Mode: true
startWithVideoMuted: false
},
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_%%",
parentNode: document.querySelector('#placeholder'),
roomName: "%%_ROOM_NAME_%%",

View file

@ -10,6 +10,7 @@ using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Proctoring.Events;
namespace SafeExamBrowser.Proctoring
{
@ -17,12 +18,34 @@ namespace SafeExamBrowser.Proctoring
{
private readonly ILogger logger;
public event FullScreenChangedEventHandler FullScreenChanged;
internal ProctoringControl(ILogger logger)
{
this.logger = logger;
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)
{
if (e.PermissionKind == CoreWebView2PermissionKind.Camera || e.PermissionKind == CoreWebView2PermissionKind.Microphone)
@ -35,18 +58,5 @@ namespace SafeExamBrowser.Proctoring
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 IProctoringWindow window;
private ProctoringSettings settings;
public string Tooltip => "TODO!!!";
public IconResource IconResource => new XamlIconResource();
@ -44,11 +45,20 @@ namespace SafeExamBrowser.Proctoring
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)
{
this.settings = settings;
if (settings.JitsiMeet.Enabled || settings.Zoom.Enabled)
{
var content = LoadContent(settings);
@ -63,7 +73,12 @@ namespace SafeExamBrowser.Proctoring
});
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")}.");
}
@ -73,9 +88,13 @@ namespace SafeExamBrowser.Proctoring
}
}
public void Terminate()
void INotification.Terminate()
{
window?.Close();
}
void IProctoringController.Terminate()
{
fileSystem.Delete(filePath);
}

View file

@ -26,6 +26,11 @@ namespace SafeExamBrowser.Settings.Proctoring
/// </summary>
public JitsiMeetSettings JitsiMeet { get; set; }
/// <summary>
/// Determines the visibility of the proctoring window.
/// </summary>
public WindowVisibility WindowVisibility { get; set; }
/// <summary>
/// All settings for remote proctoring with Zoom.
/// </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="Proctoring\JitsiMeetSettings.cs" />
<Compile Include="Proctoring\ProctoringSettings.cs" />
<Compile Include="Proctoring\WindowVisibility.cs" />
<Compile Include="Proctoring\ZoomSettings.cs" />
<Compile Include="SessionMode.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/.
*/
using SafeExamBrowser.UserInterface.Contracts.Proctoring.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Proctoring
{
/// <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>
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
{
/// <summary>
///
/// Defines the functionality of a proctoring window.
/// </summary>
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\FileSystemOperation.cs" />
<Compile Include="FileSystemDialog\IFileSystemDialog.cs" />
<Compile Include="Proctoring\Events\FullScreenChangedEventHandler.cs" />
<Compile Include="Proctoring\IProctoringControl.cs" />
<Compile Include="Proctoring\IProctoringWindow.cs" />
<Compile Include="IProgressIndicator.cs" />

View file

@ -4,5 +4,5 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
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>

View file

@ -11,6 +11,7 @@ using System.Windows;
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
@ -47,6 +48,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
Dispatcher.Invoke(() =>
{
Closing -= ProctoringWindow_Closing;
closing?.Invoke();
base.Close();
});
@ -57,26 +59,67 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Dispatcher.Invoke(base.Hide);
}
public void SetTitle(string title)
{
Dispatcher.Invoke(() => Title = title);
}
public new void 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)
{
Content = element;
control.FullScreenChanged += Control_FullScreenChanged;
}
Closing += ProctoringWindow_Closing;
Top = SystemParameters.WorkArea.Height - Height;
Left = SystemParameters.WorkArea.Width - Width;
Loaded += ProctoringWindow_Loaded;
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
{
private const int GWL_STYLE = -16;
private const uint MF_BYCOMMAND = 0x00000000;
private const uint MF_GRAYED = 0x00000001;
private const uint MF_ENABLED = 0x00000000;
private const uint SC_CLOSE = 0xF060;
private const int WS_SYSMENU = 0x80000;
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")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[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);
}
}