SEBSP-26: Implemented capturing and transmission interval.
This commit is contained in:
parent
456894edb9
commit
cb81906945
8 changed files with 118 additions and 32 deletions
|
@ -288,7 +288,7 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
private IOperation BuildProctoringOperation()
|
private IOperation BuildProctoringOperation()
|
||||||
{
|
{
|
||||||
var controller = new ProctoringController(context.AppConfig, new FileSystem(), ModuleLogger(nameof(ProctoringController)), context.Server, text, uiFactory);
|
var controller = new ProctoringController(context.AppConfig, new FileSystem(), ModuleLogger(nameof(ProctoringController)), nativeMethods, context.Server, text, uiFactory);
|
||||||
var operation = new ProctoringOperation(actionCenter, context, controller, logger, taskbar, uiFactory);
|
var operation = new ProctoringOperation(actionCenter, context, controller, logger, taskbar, uiFactory);
|
||||||
|
|
||||||
return operation;
|
return operation;
|
||||||
|
|
|
@ -90,6 +90,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||||
case Keys.Proctoring.ScreenProctoring.ClientSecret:
|
case Keys.Proctoring.ScreenProctoring.ClientSecret:
|
||||||
MapClientSecret(settings, value);
|
MapClientSecret(settings, value);
|
||||||
break;
|
break;
|
||||||
|
case Keys.Proctoring.ScreenProctoring.Enabled:
|
||||||
|
MapScreenProctoringEnabled(settings, value);
|
||||||
|
break;
|
||||||
case Keys.Proctoring.ScreenProctoring.GroupId:
|
case Keys.Proctoring.ScreenProctoring.GroupId:
|
||||||
MapGroupId(settings, value);
|
MapGroupId(settings, value);
|
||||||
break;
|
break;
|
||||||
|
@ -108,9 +111,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||||
case Keys.Proctoring.ScreenProctoring.MinInterval:
|
case Keys.Proctoring.ScreenProctoring.MinInterval:
|
||||||
MapMinInterval(settings, value);
|
MapMinInterval(settings, value);
|
||||||
break;
|
break;
|
||||||
case Keys.Proctoring.ScreenProctoring.Enabled:
|
|
||||||
MapScreenProctoringEnabled(settings, value);
|
|
||||||
break;
|
|
||||||
case Keys.Proctoring.ScreenProctoring.ServiceUrl:
|
case Keys.Proctoring.ScreenProctoring.ServiceUrl:
|
||||||
MapServiceUrl(settings, value);
|
MapServiceUrl(settings, value);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -19,6 +19,7 @@ using SafeExamBrowser.Server.Contracts.Events.Proctoring;
|
||||||
using SafeExamBrowser.Settings.Proctoring;
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts;
|
using SafeExamBrowser.SystemComponents.Contracts;
|
||||||
using SafeExamBrowser.UserInterface.Contracts;
|
using SafeExamBrowser.UserInterface.Contracts;
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Proctoring
|
namespace SafeExamBrowser.Proctoring
|
||||||
{
|
{
|
||||||
|
@ -40,6 +41,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
AppConfig appConfig,
|
AppConfig appConfig,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IModuleLogger logger,
|
IModuleLogger logger,
|
||||||
|
INativeMethods nativeMethods,
|
||||||
IServerProxy server,
|
IServerProxy server,
|
||||||
IText text,
|
IText text,
|
||||||
IUserInterfaceFactory uiFactory)
|
IUserInterfaceFactory uiFactory)
|
||||||
|
@ -47,7 +49,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
||||||
factory = new ProctoringFactory(appConfig, fileSystem, logger, text, uiFactory);
|
factory = new ProctoringFactory(appConfig, fileSystem, logger, nativeMethods, text, uiFactory);
|
||||||
implementations = new List<ProctoringImplementation>();
|
implementations = new List<ProctoringImplementation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
||||||
using SafeExamBrowser.Settings.Proctoring;
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
using SafeExamBrowser.SystemComponents.Contracts;
|
using SafeExamBrowser.SystemComponents.Contracts;
|
||||||
using SafeExamBrowser.UserInterface.Contracts;
|
using SafeExamBrowser.UserInterface.Contracts;
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Proctoring
|
namespace SafeExamBrowser.Proctoring
|
||||||
{
|
{
|
||||||
|
@ -24,14 +25,22 @@ namespace SafeExamBrowser.Proctoring
|
||||||
private readonly AppConfig appConfig;
|
private readonly AppConfig appConfig;
|
||||||
private readonly IFileSystem fileSystem;
|
private readonly IFileSystem fileSystem;
|
||||||
private readonly IModuleLogger logger;
|
private readonly IModuleLogger logger;
|
||||||
|
private readonly INativeMethods nativeMethods;
|
||||||
private readonly IText text;
|
private readonly IText text;
|
||||||
private readonly IUserInterfaceFactory uiFactory;
|
private readonly IUserInterfaceFactory uiFactory;
|
||||||
|
|
||||||
public ProctoringFactory(AppConfig appConfig, IFileSystem fileSystem, IModuleLogger logger, IText text, IUserInterfaceFactory uiFactory)
|
public ProctoringFactory(
|
||||||
|
AppConfig appConfig,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
IModuleLogger logger,
|
||||||
|
INativeMethods nativeMethods,
|
||||||
|
IText text,
|
||||||
|
IUserInterfaceFactory uiFactory)
|
||||||
{
|
{
|
||||||
this.appConfig = appConfig;
|
this.appConfig = appConfig;
|
||||||
this.fileSystem = fileSystem;
|
this.fileSystem = fileSystem;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
this.nativeMethods = nativeMethods;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.uiFactory = uiFactory;
|
this.uiFactory = uiFactory;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +61,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
var logger = this.logger.CloneFor(nameof(ScreenProctoring));
|
var logger = this.logger.CloneFor(nameof(ScreenProctoring));
|
||||||
var service = new ServiceProxy(logger.CloneFor(nameof(ServiceProxy)));
|
var service = new ServiceProxy(logger.CloneFor(nameof(ServiceProxy)));
|
||||||
|
|
||||||
implementations.Add(new ScreenProctoringImplementation(logger, service, settings, text));
|
implementations.Add(new ScreenProctoringImplementation(logger, nativeMethods, service, settings, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
return implementations;
|
return implementations;
|
||||||
|
|
|
@ -145,6 +145,10 @@
|
||||||
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
|
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
|
||||||
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
|
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
|
||||||
|
<Project>{7016F080-9AA5-41B2-A225-385AD877C171}</Project>
|
||||||
|
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="JitsiMeet\index.html" />
|
<EmbeddedResource Include="JitsiMeet\index.html" />
|
||||||
|
|
|
@ -46,12 +46,12 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Imaging
|
||||||
|
|
||||||
public string ToReducedString()
|
public string ToReducedString()
|
||||||
{
|
{
|
||||||
return $"{Width}x{Height}, {Data.Length / 1000:N0} kB, {Format.ToString().ToUpper()}";
|
return $"{Width}x{Height}, {Data.Length / 1000:N0}kB, {Format.ToString().ToUpper()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"resolution: {Width}x{Height}, size: {Data.Length / 1000:N0} kB, format: {Format.ToString().ToUpper()}";
|
return $"resolution: {Width}x{Height}, size: {Data.Length / 1000:N0}kB, format: {Format.ToString().ToUpper()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Compress()
|
internal void Compress()
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
using System.Windows.Input;
|
||||||
using SafeExamBrowser.Core.Contracts.Notifications.Events;
|
using SafeExamBrowser.Core.Contracts.Notifications.Events;
|
||||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
|
@ -16,24 +19,42 @@ using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
||||||
using SafeExamBrowser.Server.Contracts.Events.Proctoring;
|
using SafeExamBrowser.Server.Contracts.Events.Proctoring;
|
||||||
using SafeExamBrowser.Settings.Proctoring;
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||||
|
using MouseButton = SafeExamBrowser.WindowsApi.Contracts.Events.MouseButton;
|
||||||
|
using MouseButtonState = SafeExamBrowser.WindowsApi.Contracts.Events.MouseButtonState;
|
||||||
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
{
|
{
|
||||||
internal class ScreenProctoringImplementation : ProctoringImplementation
|
internal class ScreenProctoringImplementation : ProctoringImplementation
|
||||||
{
|
{
|
||||||
|
private readonly object @lock = new object();
|
||||||
|
|
||||||
private readonly IModuleLogger logger;
|
private readonly IModuleLogger logger;
|
||||||
|
private readonly INativeMethods nativeMethods;
|
||||||
private readonly ServiceProxy service;
|
private readonly ServiceProxy service;
|
||||||
private readonly ScreenProctoringSettings settings;
|
private readonly ScreenProctoringSettings settings;
|
||||||
private readonly IText text;
|
private readonly IText text;
|
||||||
private readonly Timer timer;
|
private readonly Timer timer;
|
||||||
|
|
||||||
|
private DateTime last;
|
||||||
|
private Guid? keyboardHookId;
|
||||||
|
private Guid? mouseHookId;
|
||||||
|
|
||||||
internal override string Name => nameof(ScreenProctoring);
|
internal override string Name => nameof(ScreenProctoring);
|
||||||
|
|
||||||
public override event NotificationChangedEventHandler NotificationChanged;
|
public override event NotificationChangedEventHandler NotificationChanged;
|
||||||
|
|
||||||
internal ScreenProctoringImplementation(IModuleLogger logger, ServiceProxy service, ProctoringSettings settings, IText text)
|
internal ScreenProctoringImplementation(
|
||||||
|
IModuleLogger logger,
|
||||||
|
INativeMethods nativeMethods,
|
||||||
|
ServiceProxy service,
|
||||||
|
ProctoringSettings settings,
|
||||||
|
IText text)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
this.nativeMethods = nativeMethods;
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.settings = settings.ScreenProctoring;
|
this.settings = settings.ScreenProctoring;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
@ -62,7 +83,6 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ShowNotificationInactive();
|
ShowNotificationInactive();
|
||||||
|
|
||||||
logger.Info($"Initialized proctoring: Not all settings are valid or a server session is active, not starting automatically.");
|
logger.Info($"Initialized proctoring: Not all settings are valid or a server session is active, not starting automatically.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,9 +116,12 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
logger.Info("Successfully processed instruction.");
|
logger.Info("Successfully processed instruction.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Start()
|
internal override void Start()
|
||||||
{
|
{
|
||||||
|
last = DateTime.Now;
|
||||||
|
keyboardHookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback);
|
||||||
|
mouseHookId = nativeMethods.RegisterMouseHook(MouseHookCallback);
|
||||||
|
|
||||||
timer.Elapsed += Timer_Elapsed;
|
timer.Elapsed += Timer_Elapsed;
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
|
||||||
|
@ -109,6 +132,19 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
|
|
||||||
internal override void Stop()
|
internal override void Stop()
|
||||||
{
|
{
|
||||||
|
if (keyboardHookId.HasValue)
|
||||||
|
{
|
||||||
|
nativeMethods.DeregisterKeyboardHook(keyboardHookId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseHookId.HasValue)
|
||||||
|
{
|
||||||
|
nativeMethods.DeregisterMouseHook(mouseHookId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboardHookId = default;
|
||||||
|
mouseHookId = default;
|
||||||
|
|
||||||
timer.Elapsed -= Timer_Elapsed;
|
timer.Elapsed -= Timer_Elapsed;
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
|
|
||||||
|
@ -120,11 +156,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
|
|
||||||
internal override void Terminate()
|
internal override void Terminate()
|
||||||
{
|
{
|
||||||
if (timer.Enabled)
|
Stop();
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
TerminateNotification();
|
TerminateNotification();
|
||||||
|
|
||||||
logger.Info("Terminated proctoring.");
|
logger.Info("Terminated proctoring.");
|
||||||
|
@ -160,9 +192,28 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
|
||||||
|
{
|
||||||
|
var key = KeyInterop.KeyFromVirtualKey(keyCode);
|
||||||
|
|
||||||
|
TryExecute();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
|
||||||
|
{
|
||||||
|
var isTouch = info.IsTouch;
|
||||||
|
|
||||||
|
TryExecute();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowNotificationActive()
|
private void ShowNotificationActive()
|
||||||
{
|
{
|
||||||
// TODO: Replace with actual icon!
|
// TODO: Replace with actual icon!
|
||||||
|
// TODO: Extend INotification with IsEnabled or CanActivate, as the screen proctoring notification does not have any action or window!
|
||||||
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ProctoringNotification_Active.xaml") };
|
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ProctoringNotification_Active.xaml") };
|
||||||
Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip);
|
Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip);
|
||||||
NotificationChanged?.Invoke();
|
NotificationChanged?.Invoke();
|
||||||
|
@ -187,21 +238,41 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
|
|
||||||
private void Timer_Elapsed(object sender, ElapsedEventArgs args)
|
private void Timer_Elapsed(object sender, ElapsedEventArgs args)
|
||||||
{
|
{
|
||||||
try
|
TryExecute();
|
||||||
{
|
}
|
||||||
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
|
|
||||||
{
|
|
||||||
screenShot.Take();
|
|
||||||
screenShot.Compress();
|
|
||||||
service.SendScreenShot(screenShot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.Error("Failed to process screen shot!", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.Start();
|
private void TryExecute()
|
||||||
|
{
|
||||||
|
if (MinimumIntervalElapsed() && Monitor.TryEnter(@lock))
|
||||||
|
{
|
||||||
|
last = DateTime.Now;
|
||||||
|
timer.Stop();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
|
||||||
|
{
|
||||||
|
screenShot.Take();
|
||||||
|
screenShot.Compress();
|
||||||
|
service.Send(screenShot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error("Failed to process screen shot!", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timer.Start();
|
||||||
|
Monitor.Exit(@lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MinimumIntervalElapsed()
|
||||||
|
{
|
||||||
|
return DateTime.Now.Subtract(last) >= new TimeSpan(0, 0, 0, 0, settings.MinInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
|
||||||
return new ServiceResponse(success, message);
|
return new ServiceResponse(success, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServiceResponse SendScreenShot(ScreenShot screenShot)
|
internal ServiceResponse Send(ScreenShot screenShot)
|
||||||
{
|
{
|
||||||
var request = new ScreenShotRequest(api, httpClient, logger, parser);
|
var request = new ScreenShotRequest(api, httpClient, logger, parser);
|
||||||
var success = request.TryExecute(screenShot, SessionId, out var message);
|
var success = request.TryExecute(screenShot, SessionId, out var message);
|
||||||
|
|
Loading…
Reference in a new issue