Skip to content

Commit

Permalink
SEBSP-26: Implemented capturing and transmission interval.
Browse files Browse the repository at this point in the history
  • Loading branch information
dbuechel committed Feb 6, 2024
1 parent 456894e commit cb81906
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 29 deletions.
2 changes: 1 addition & 1 deletion SafeExamBrowser.Client/CompositionRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private IOperation BuildMouseInterceptorOperation()

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);

return operation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ internal override void Map(string key, object value, AppSettings settings)
case Keys.Proctoring.ScreenProctoring.ClientSecret:
MapClientSecret(settings, value);
break;
case Keys.Proctoring.ScreenProctoring.Enabled:
MapScreenProctoringEnabled(settings, value);
break;
case Keys.Proctoring.ScreenProctoring.GroupId:
MapGroupId(settings, value);
break;
Expand All @@ -108,9 +111,6 @@ internal override void Map(string key, object value, AppSettings settings)
case Keys.Proctoring.ScreenProctoring.MinInterval:
MapMinInterval(settings, value);
break;
case Keys.Proctoring.ScreenProctoring.Enabled:
MapScreenProctoringEnabled(settings, value);
break;
case Keys.Proctoring.ScreenProctoring.ServiceUrl:
MapServiceUrl(settings, value);
break;
Expand Down
4 changes: 3 additions & 1 deletion SafeExamBrowser.Proctoring/ProctoringController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;

namespace SafeExamBrowser.Proctoring
{
Expand All @@ -40,14 +41,15 @@ public ProctoringController(
AppConfig appConfig,
IFileSystem fileSystem,
IModuleLogger logger,
INativeMethods nativeMethods,
IServerProxy server,
IText text,
IUserInterfaceFactory uiFactory)
{
this.logger = logger;
this.server = server;

factory = new ProctoringFactory(appConfig, fileSystem, logger, text, uiFactory);
factory = new ProctoringFactory(appConfig, fileSystem, logger, nativeMethods, text, uiFactory);
implementations = new List<ProctoringImplementation>();
}

Expand Down
13 changes: 11 additions & 2 deletions SafeExamBrowser.Proctoring/ProctoringFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;

namespace SafeExamBrowser.Proctoring
{
Expand All @@ -24,14 +25,22 @@ internal class ProctoringFactory
private readonly AppConfig appConfig;
private readonly IFileSystem fileSystem;
private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly IText text;
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.fileSystem = fileSystem;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.text = text;
this.uiFactory = uiFactory;
}
Expand All @@ -52,7 +61,7 @@ internal IEnumerable<ProctoringImplementation> CreateAllActive(ProctoringSetting
var logger = this.logger.CloneFor(nameof(ScreenProctoring));
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;
Expand Down
4 changes: 4 additions & 0 deletions SafeExamBrowser.Proctoring/SafeExamBrowser.Proctoring.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
</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>
<EmbeddedResource Include="JitsiMeet\index.html" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ public void Dispose()

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()
{
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
*/

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Input;
using SafeExamBrowser.Core.Contracts.Notifications.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
Expand All @@ -16,24 +19,42 @@
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
using SafeExamBrowser.Server.Contracts.Events.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
{
internal class ScreenProctoringImplementation : ProctoringImplementation
{
private readonly object @lock = new object();

private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly ServiceProxy service;
private readonly ScreenProctoringSettings settings;
private readonly IText text;
private readonly Timer timer;

private DateTime last;
private Guid? keyboardHookId;
private Guid? mouseHookId;

internal override string Name => nameof(ScreenProctoring);

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.nativeMethods = nativeMethods;
this.service = service;
this.settings = settings.ScreenProctoring;
this.text = text;
Expand Down Expand Up @@ -62,7 +83,6 @@ internal override void Initialize()
else
{
ShowNotificationInactive();

logger.Info($"Initialized proctoring: Not all settings are valid or a server session is active, not starting automatically.");
}
}
Expand Down Expand Up @@ -96,9 +116,12 @@ internal override void ProctoringInstructionReceived(InstructionEventArgs args)
logger.Info("Successfully processed instruction.");
}
}

internal override void Start()
{
last = DateTime.Now;
keyboardHookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback);
mouseHookId = nativeMethods.RegisterMouseHook(MouseHookCallback);

timer.Elapsed += Timer_Elapsed;
timer.Start();

Expand All @@ -109,6 +132,19 @@ internal override void Start()

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.Stop();

Expand All @@ -120,11 +156,7 @@ internal override void Stop()

internal override void Terminate()
{
if (timer.Enabled)
{
Stop();
}

Stop();
TerminateNotification();

logger.Info("Terminated proctoring.");
Expand Down Expand Up @@ -160,9 +192,28 @@ private void Connect(string sessionId = default)
}
}

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()
{
// 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") };
Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip);
NotificationChanged?.Invoke();
Expand All @@ -187,21 +238,41 @@ private void TerminateServiceSession()

private void Timer_Elapsed(object sender, ElapsedEventArgs args)
{
try
TryExecute();
}

private void TryExecute()
{
if (MinimumIntervalElapsed() && Monitor.TryEnter(@lock))
{
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
last = DateTime.Now;
timer.Stop();

Task.Run(() =>
{
screenShot.Take();
screenShot.Compress();
service.SendScreenShot(screenShot);
}
}
catch (Exception e)
{
logger.Error("Failed to process screen shot!", e);
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);
}
}

timer.Start();
private bool MinimumIntervalElapsed()
{
return DateTime.Now.Subtract(last) >= new TimeSpan(0, 0, 0, 0, settings.MinInterval);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ internal ServiceResponse CreateSession(string groupId)
return new ServiceResponse(success, message);
}

internal ServiceResponse SendScreenShot(ScreenShot screenShot)
internal ServiceResponse Send(ScreenShot screenShot)
{
var request = new ScreenShotRequest(api, httpClient, logger, parser);
var success = request.TryExecute(screenShot, SessionId, out var message);
Expand Down

0 comments on commit cb81906

Please sign in to comment.