diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs index 9383700d..8ca72a5f 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs @@ -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; diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs index 899f464f..37d2a90c 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs @@ -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"; diff --git a/SafeExamBrowser.Proctoring/JitsiMeet/index.html b/SafeExamBrowser.Proctoring/JitsiMeet/index.html index 94532f2e..fa6faae1 100644 --- a/SafeExamBrowser.Proctoring/JitsiMeet/index.html +++ b/SafeExamBrowser.Proctoring/JitsiMeet/index.html @@ -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_%%", diff --git a/SafeExamBrowser.Proctoring/ProctoringControl.cs b/SafeExamBrowser.Proctoring/ProctoringControl.cs index b01fb253..c446fada 100644 --- a/SafeExamBrowser.Proctoring/ProctoringControl.cs +++ b/SafeExamBrowser.Proctoring/ProctoringControl.cs @@ -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); - } - } } } diff --git a/SafeExamBrowser.Proctoring/ProctoringController.cs b/SafeExamBrowser.Proctoring/ProctoringController.cs index a62f2374..abd5ac07 100644 --- a/SafeExamBrowser.Proctoring/ProctoringController.cs +++ b/SafeExamBrowser.Proctoring/ProctoringController.cs @@ -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); } diff --git a/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs b/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs index cb14ccb8..ae816069 100644 --- a/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs +++ b/SafeExamBrowser.Settings/Proctoring/ProctoringSettings.cs @@ -26,6 +26,11 @@ namespace SafeExamBrowser.Settings.Proctoring /// public JitsiMeetSettings JitsiMeet { get; set; } + /// + /// Determines the visibility of the proctoring window. + /// + public WindowVisibility WindowVisibility { get; set; } + /// /// All settings for remote proctoring with Zoom. /// diff --git a/SafeExamBrowser.Settings/Proctoring/WindowVisibility.cs b/SafeExamBrowser.Settings/Proctoring/WindowVisibility.cs new file mode 100644 index 00000000..82c85b78 --- /dev/null +++ b/SafeExamBrowser.Settings/Proctoring/WindowVisibility.cs @@ -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 +{ + /// + /// Defines all possible visibility states for the proctoring window. + /// + public enum WindowVisibility + { + /// + /// The proctoring window is hidden and cannot be shown by the user. + /// + Hidden, + + /// + /// The proctoring window is initially hidden but may be shown by the user. + /// + AllowToShow, + + /// + /// The proctoring window is initially visible but may be hidden by the user. + /// + AllowToHide, + + /// + /// The proctoring window is always visible and cannot be hidden by the user. + /// + Visible + } +} diff --git a/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj b/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj index 6780fbb9..afd1c6c3 100644 --- a/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj +++ b/SafeExamBrowser.Settings/SafeExamBrowser.Settings.csproj @@ -73,6 +73,7 @@ + diff --git a/SafeExamBrowser.UserInterface.Contracts/Proctoring/Events/FullScreenChangedEventHandler.cs b/SafeExamBrowser.UserInterface.Contracts/Proctoring/Events/FullScreenChangedEventHandler.cs new file mode 100644 index 00000000..110c3126 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Proctoring/Events/FullScreenChangedEventHandler.cs @@ -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 +{ + /// + /// Indicates that the full screen state has changed. + /// + public delegate void FullScreenChangedEventHandler(bool fullScreen); +} diff --git a/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringControl.cs b/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringControl.cs index c6717b54..0720f1c8 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringControl.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringControl.cs @@ -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 { /// - /// + /// Defines the functionality of a proctoring control, i.e. a web view running a WebRTC-enabled web application, which normally is embedded in + /// a . /// public interface IProctoringControl { - + /// + /// Event fired when the full screen state changed. + /// + event FullScreenChangedEventHandler FullScreenChanged; } } diff --git a/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringWindow.cs b/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringWindow.cs index d90e8dee..cb1e8ce9 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringWindow.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Proctoring/IProctoringWindow.cs @@ -11,10 +11,18 @@ using SafeExamBrowser.UserInterface.Contracts.Windows; namespace SafeExamBrowser.UserInterface.Contracts.Proctoring { /// - /// + /// Defines the functionality of a proctoring window. /// public interface IProctoringWindow : IWindow { + /// + /// Sets the window title to the given value. + /// + void SetTitle(string title); + /// + /// Toggles the window visibility. + /// + void Toggle(); } } diff --git a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj index f9ea9dc9..eaa90849 100644 --- a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj +++ b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj @@ -67,6 +67,7 @@ + diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml b/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml index 7a62d514..6433deaa 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml +++ b/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml @@ -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"> diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml.cs index 81713305..5594de78 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Windows/ProctoringWindow.xaml.cs @@ -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(); } } } diff --git a/SafeExamBrowser.UserInterface.Shared/Utilities/WindowExtensions.cs b/SafeExamBrowser.UserInterface.Shared/Utilities/WindowExtensions.cs index c93c0a54..6258af4b 100644 --- a/SafeExamBrowser.UserInterface.Shared/Utilities/WindowExtensions.cs +++ b/SafeExamBrowser.UserInterface.Shared/Utilities/WindowExtensions.cs @@ -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); } }