SEBSP-61: Implemented basic metadata collection & transmission.
This commit is contained in:
parent
cb81906945
commit
731a748552
46 changed files with 557 additions and 132 deletions
|
@ -16,7 +16,7 @@ namespace SafeExamBrowser.Applications.Contracts
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls the lifetime and functionality of an application.
|
/// Controls the lifetime and functionality of an application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IApplication
|
public interface IApplication<out TWindow> where TWindow : IApplicationWindow
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether the application should be automatically started.
|
/// Indicates whether the application should be automatically started.
|
||||||
|
@ -51,7 +51,7 @@ namespace SafeExamBrowser.Applications.Contracts
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all windows of the application.
|
/// Returns all windows of the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IEnumerable<IApplicationWindow> GetWindows();
|
IEnumerable<TWindow> GetWindows();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs any initialization work, if necessary.
|
/// Performs any initialization work, if necessary.
|
||||||
|
|
|
@ -18,6 +18,6 @@ namespace SafeExamBrowser.Applications.Contracts
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to create an application according to the given settings.
|
/// Attempts to create an application according to the given settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FactoryResult TryCreate(WhitelistApplication settings, out IApplication application);
|
FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||||
namespace SafeExamBrowser.Applications.Contracts
|
namespace SafeExamBrowser.Applications.Contracts
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a window of an <see cref="IApplication"/>.
|
/// Defines a window of an <see cref="IApplication{TWindow}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IApplicationWindow
|
public interface IApplicationWindow
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace SafeExamBrowser.Applications
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication application)
|
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
|
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ namespace SafeExamBrowser.Applications
|
||||||
return FactoryResult.Error;
|
return FactoryResult.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
|
private IApplication<IApplicationWindow> BuildApplication(string executablePath, WhitelistApplication settings)
|
||||||
{
|
{
|
||||||
const int ONE_SECOND = 1000;
|
const int ONE_SECOND = 1000;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Applications
|
namespace SafeExamBrowser.Applications
|
||||||
{
|
{
|
||||||
internal class ExternalApplication : IApplication
|
internal class ExternalApplication : IApplication<IApplicationWindow>
|
||||||
{
|
{
|
||||||
private readonly object @lock = new object();
|
private readonly object @lock = new object();
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace SafeExamBrowser.Browser.Contracts
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls the lifetime and functionality of the browser application.
|
/// Controls the lifetime and functionality of the browser application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBrowserApplication : IApplication
|
public interface IBrowserApplication : IApplication<IBrowserWindow>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the browser application detects a download request for an application configuration file.
|
/// Event fired when the browser application detects a download request for an application configuration file.
|
||||||
|
|
28
SafeExamBrowser.Browser.Contracts/IBrowserWindow.cs
Normal file
28
SafeExamBrowser.Browser.Contracts/IBrowserWindow.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using SafeExamBrowser.Applications.Contracts;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Browser.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a window of the <see cref="IBrowserApplication"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBrowserWindow : IApplicationWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the window is the main browser window.
|
||||||
|
/// </summary>
|
||||||
|
bool IsMainWindow { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently loaded URL, or <c>default(string)</c> in case no navigation has happened yet.
|
||||||
|
/// </summary>
|
||||||
|
string Url { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,6 +67,7 @@
|
||||||
<Compile Include="Filters\IRuleFactory.cs" />
|
<Compile Include="Filters\IRuleFactory.cs" />
|
||||||
<Compile Include="Filters\Request.cs" />
|
<Compile Include="Filters\Request.cs" />
|
||||||
<Compile Include="IBrowserApplication.cs" />
|
<Compile Include="IBrowserApplication.cs" />
|
||||||
|
<Compile Include="IBrowserWindow.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -14,7 +14,6 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using SafeExamBrowser.Applications.Contracts;
|
|
||||||
using SafeExamBrowser.Applications.Contracts.Events;
|
using SafeExamBrowser.Applications.Contracts.Events;
|
||||||
using SafeExamBrowser.Browser.Contracts;
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
using SafeExamBrowser.Browser.Contracts.Events;
|
using SafeExamBrowser.Browser.Contracts.Events;
|
||||||
|
@ -99,9 +98,9 @@ namespace SafeExamBrowser.Browser
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IApplicationWindow> GetWindows()
|
public IEnumerable<IBrowserWindow> GetWindows()
|
||||||
{
|
{
|
||||||
return new List<IApplicationWindow>(windows);
|
return new List<IBrowserWindow>(windows);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
|
|
|
@ -13,7 +13,6 @@ using System.Threading.Tasks;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms.Handler;
|
using CefSharp.WinForms.Handler;
|
||||||
using CefSharp.WinForms.Host;
|
using CefSharp.WinForms.Host;
|
||||||
using SafeExamBrowser.Applications.Contracts;
|
|
||||||
using SafeExamBrowser.Applications.Contracts.Events;
|
using SafeExamBrowser.Applications.Contracts.Events;
|
||||||
using SafeExamBrowser.Browser.Contracts.Events;
|
using SafeExamBrowser.Browser.Contracts.Events;
|
||||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||||
|
@ -43,7 +42,7 @@ using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.T
|
||||||
|
|
||||||
namespace SafeExamBrowser.Browser
|
namespace SafeExamBrowser.Browser
|
||||||
{
|
{
|
||||||
internal class BrowserWindow : IApplicationWindow
|
internal class BrowserWindow : Contracts.IBrowserWindow
|
||||||
{
|
{
|
||||||
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
|
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
|
||||||
private const double ZOOM_FACTOR = 0.2;
|
private const double ZOOM_FACTOR = 0.2;
|
||||||
|
@ -52,7 +51,6 @@ namespace SafeExamBrowser.Browser
|
||||||
private readonly IFileSystemDialog fileSystemDialog;
|
private readonly IFileSystemDialog fileSystemDialog;
|
||||||
private readonly IHashAlgorithm hashAlgorithm;
|
private readonly IHashAlgorithm hashAlgorithm;
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
private readonly bool isMainWindow;
|
|
||||||
private readonly IKeyGenerator keyGenerator;
|
private readonly IKeyGenerator keyGenerator;
|
||||||
private readonly IModuleLogger logger;
|
private readonly IModuleLogger logger;
|
||||||
private readonly IMessageBox messageBox;
|
private readonly IMessageBox messageBox;
|
||||||
|
@ -69,7 +67,7 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
private WindowSettings WindowSettings
|
private WindowSettings WindowSettings
|
||||||
{
|
{
|
||||||
get { return isMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
|
get { return IsMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IBrowserControl Control { get; private set; }
|
internal IBrowserControl Control { get; private set; }
|
||||||
|
@ -77,7 +75,9 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
public IntPtr Handle { get; private set; }
|
public IntPtr Handle { get; private set; }
|
||||||
public IconResource Icon { get; private set; }
|
public IconResource Icon { get; private set; }
|
||||||
|
public bool IsMainWindow { get; private set; }
|
||||||
public string Title { get; private set; }
|
public string Title { get; private set; }
|
||||||
|
public string Url { get; private set; }
|
||||||
|
|
||||||
internal event WindowClosedEventHandler Closed;
|
internal event WindowClosedEventHandler Closed;
|
||||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||||
|
@ -110,7 +110,7 @@ namespace SafeExamBrowser.Browser
|
||||||
this.hashAlgorithm = hashAlgorithm;
|
this.hashAlgorithm = hashAlgorithm;
|
||||||
this.httpClient = new HttpClient();
|
this.httpClient = new HttpClient();
|
||||||
this.Id = id;
|
this.Id = id;
|
||||||
this.isMainWindow = isMainWindow;
|
this.IsMainWindow = isMainWindow;
|
||||||
this.keyGenerator = keyGenerator;
|
this.keyGenerator = keyGenerator;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.messageBox = messageBox;
|
this.messageBox = messageBox;
|
||||||
|
@ -162,7 +162,7 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
Icon = new BrowserIconResource();
|
Icon = new BrowserIconResource();
|
||||||
|
|
||||||
if (isMainWindow)
|
if (IsMainWindow)
|
||||||
{
|
{
|
||||||
cefSharpControl = new CefSharpBrowserControl(CreateLifeSpanHandlerForMainWindow(), startUrl);
|
cefSharpControl = new CefSharpBrowserControl(CreateLifeSpanHandlerForMainWindow(), startUrl);
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
internal void InitializeWindow()
|
internal void InitializeWindow()
|
||||||
{
|
{
|
||||||
window = uiFactory.CreateBrowserWindow(Control, settings, isMainWindow, this.logger);
|
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
|
||||||
window.AddressChanged += Window_AddressChanged;
|
window.AddressChanged += Window_AddressChanged;
|
||||||
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
||||||
window.Closed += Window_Closed;
|
window.Closed += Window_Closed;
|
||||||
|
@ -267,6 +267,8 @@ namespace SafeExamBrowser.Browser
|
||||||
private void Control_AddressChanged(string address)
|
private void Control_AddressChanged(string address)
|
||||||
{
|
{
|
||||||
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
|
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
|
||||||
|
|
||||||
|
Url = address;
|
||||||
window.UpdateAddress(address);
|
window.UpdateAddress(address);
|
||||||
|
|
||||||
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
|
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
|
||||||
|
@ -440,7 +442,7 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
private void HomeNavigationRequested()
|
private void HomeNavigationRequested()
|
||||||
{
|
{
|
||||||
if (isMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
|
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
|
||||||
{
|
{
|
||||||
var navigate = false;
|
var navigate = false;
|
||||||
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
|
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
|
||||||
|
|
|
@ -41,6 +41,7 @@ using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||||
using SafeExamBrowser.WindowsApi.Contracts;
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
using IWindow = SafeExamBrowser.UserInterface.Contracts.Windows.IWindow;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Client.UnitTests
|
namespace SafeExamBrowser.Client.UnitTests
|
||||||
{
|
{
|
||||||
|
@ -603,7 +604,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Operations_MustAskForApplicationPath()
|
public void Operations_MustAskForApplicationPath()
|
||||||
{
|
{
|
||||||
var args = new ApplicationNotFoundEventArgs(default(string), default(string));
|
var args = new ApplicationNotFoundEventArgs(default, default);
|
||||||
var result = new FileSystemDialogResult { FullPath = @"C:\Some\random\path\", Success = true };
|
var result = new FileSystemDialogResult { FullPath = @"C:\Some\random\path\", Success = true };
|
||||||
|
|
||||||
fileSystemDialog.Setup(d => d.Show(
|
fileSystemDialog.Setup(d => d.Show(
|
||||||
|
@ -627,7 +628,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Operations_MustAbortAskingForApplicationPath()
|
public void Operations_MustAbortAskingForApplicationPath()
|
||||||
{
|
{
|
||||||
var args = new ApplicationNotFoundEventArgs(default(string), default(string));
|
var args = new ApplicationNotFoundEventArgs(default, default);
|
||||||
var result = new FileSystemDialogResult { Success = false };
|
var result = new FileSystemDialogResult { Success = false };
|
||||||
|
|
||||||
fileSystemDialog.Setup(d => d.Show(
|
fileSystemDialog.Setup(d => d.Show(
|
||||||
|
@ -651,7 +652,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Operations_MustInformAboutFailedApplicationInitialization()
|
public void Operations_MustInformAboutFailedApplicationInitialization()
|
||||||
{
|
{
|
||||||
var args = new ApplicationInitializationFailedEventArgs(default(string), default(string), FactoryResult.NotFound);
|
var args = new ApplicationInitializationFailedEventArgs(default, default, FactoryResult.NotFound);
|
||||||
|
|
||||||
text.SetReturnsDefault(string.Empty);
|
text.SetReturnsDefault(string.Empty);
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
|
@ -1172,9 +1173,9 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Startup_MustAutoStartApplications()
|
public void Startup_MustAutoStartApplications()
|
||||||
{
|
{
|
||||||
var application1 = new Mock<IApplication>();
|
var application1 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application2 = new Mock<IApplication>();
|
var application2 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application3 = new Mock<IApplication>();
|
var application3 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
|
|
||||||
application1.SetupGet(a => a.AutoStart).Returns(true);
|
application1.SetupGet(a => a.AutoStart).Returns(true);
|
||||||
application2.SetupGet(a => a.AutoStart).Returns(false);
|
application2.SetupGet(a => a.AutoStart).Returns(false);
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
var initialization = new InitializationResult();
|
var initialization = new InitializationResult();
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
|
|
||||||
initialization.RunningApplications.Add(new RunningApplication(default(string)));
|
initialization.RunningApplications.Add(new RunningApplication(default));
|
||||||
monitor.Setup(m => m.Initialize(It.IsAny<ApplicationSettings>())).Returns(initialization);
|
monitor.Setup(m => m.Initialize(It.IsAny<ApplicationSettings>())).Returns(initialization);
|
||||||
sut.ActionRequired += (a) =>
|
sut.ActionRequired += (a) =>
|
||||||
{
|
{
|
||||||
|
@ -79,7 +79,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustAbortIfUserCancelsApplicationLocationSelection()
|
public void Perform_MustAbortIfUserCancelsApplicationLocationSelection()
|
||||||
{
|
{
|
||||||
var application = new Mock<IApplication>().Object;
|
var application = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var applicationSettings = new WhitelistApplication { AllowCustomPath = true };
|
var applicationSettings = new WhitelistApplication { AllowCustomPath = true };
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustAllowUserToChooseApplicationLocation()
|
public void Perform_MustAllowUserToChooseApplicationLocation()
|
||||||
{
|
{
|
||||||
var application = new Mock<IApplication>().Object;
|
var application = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var applicationSettings = new WhitelistApplication { AllowCustomPath = true };
|
var applicationSettings = new WhitelistApplication { AllowCustomPath = true };
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
var attempt = 0;
|
var attempt = 0;
|
||||||
|
@ -138,7 +138,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustDenyApplicationLocationSelection()
|
public void Perform_MustDenyApplicationLocationSelection()
|
||||||
{
|
{
|
||||||
var application = new Mock<IApplication>().Object;
|
var application = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var applicationSettings = new WhitelistApplication { AllowCustomPath = false };
|
var applicationSettings = new WhitelistApplication { AllowCustomPath = false };
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
var initialization = new InitializationResult();
|
var initialization = new InitializationResult();
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
|
|
||||||
initialization.FailedAutoTerminations.Add(new RunningApplication(default(string)));
|
initialization.FailedAutoTerminations.Add(new RunningApplication(default));
|
||||||
monitor.Setup(m => m.Initialize(It.IsAny<ApplicationSettings>())).Returns(initialization);
|
monitor.Setup(m => m.Initialize(It.IsAny<ApplicationSettings>())).Returns(initialization);
|
||||||
sut.ActionRequired += (a) => args = a;
|
sut.ActionRequired += (a) => args = a;
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustFailIfTerminationFails()
|
public void Perform_MustFailIfTerminationFails()
|
||||||
{
|
{
|
||||||
var application = new RunningApplication(default(string));
|
var application = new RunningApplication(default);
|
||||||
var initialization = new InitializationResult();
|
var initialization = new InitializationResult();
|
||||||
var args = new List<ActionRequiredEventArgs>();
|
var args = new List<ActionRequiredEventArgs>();
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustIndicateApplicationInitializationFailure()
|
public void Perform_MustIndicateApplicationInitializationFailure()
|
||||||
{
|
{
|
||||||
var application = new Mock<IApplication>().Object;
|
var application = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var applicationSettings = new WhitelistApplication();
|
var applicationSettings = new WhitelistApplication();
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
|
|
||||||
|
@ -235,9 +235,9 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustInitializeApplications()
|
public void Perform_MustInitializeApplications()
|
||||||
{
|
{
|
||||||
var application1 = new Mock<IApplication>().Object;
|
var application1 = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var application2 = new Mock<IApplication>().Object;
|
var application2 = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var application3 = new Mock<IApplication>().Object;
|
var application3 = new Mock<IApplication<IApplicationWindow>>().Object;
|
||||||
var application1Settings = new WhitelistApplication();
|
var application1Settings = new WhitelistApplication();
|
||||||
var application2Settings = new WhitelistApplication();
|
var application2Settings = new WhitelistApplication();
|
||||||
var application3Settings = new WhitelistApplication();
|
var application3Settings = new WhitelistApplication();
|
||||||
|
@ -297,9 +297,9 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustTerminateRunningApplications()
|
public void Perform_MustTerminateRunningApplications()
|
||||||
{
|
{
|
||||||
var application1 = new RunningApplication(default(string));
|
var application1 = new RunningApplication(default);
|
||||||
var application2 = new RunningApplication(default(string));
|
var application2 = new RunningApplication(default);
|
||||||
var application3 = new RunningApplication(default(string));
|
var application3 = new RunningApplication(default);
|
||||||
var initialization = new InitializationResult();
|
var initialization = new InitializationResult();
|
||||||
var args = default(ActionRequiredEventArgs);
|
var args = default(ActionRequiredEventArgs);
|
||||||
|
|
||||||
|
@ -362,9 +362,9 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Revert_MustTerminateApplications()
|
public void Revert_MustTerminateApplications()
|
||||||
{
|
{
|
||||||
var application1 = new Mock<IApplication>();
|
var application1 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application2 = new Mock<IApplication>();
|
var application2 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application3 = new Mock<IApplication>();
|
var application3 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
|
|
||||||
context.Applications.Add(application1.Object);
|
context.Applications.Add(application1.Object);
|
||||||
context.Applications.Add(application2.Object);
|
context.Applications.Add(application2.Object);
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
||||||
browser.Verify(c => c.Initialize(), Times.Once);
|
browser.Verify(c => c.Initialize(), Times.Once);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == context.Browser)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == context.Browser)), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -73,7 +73,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Never);
|
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Never);
|
||||||
browser.Verify(c => c.Initialize(), Times.Never);
|
browser.Verify(c => c.Initialize(), Times.Never);
|
||||||
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Never);
|
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Never);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == context.Browser)), Times.Never);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == context.Browser)), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
@ -116,11 +116,11 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustInitializeApplications()
|
public void Perform_MustInitializeApplications()
|
||||||
{
|
{
|
||||||
var application1 = new Mock<IApplication>();
|
var application1 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application1Settings = new WhitelistApplication { ShowInShell = true };
|
var application1Settings = new WhitelistApplication { ShowInShell = true };
|
||||||
var application2 = new Mock<IApplication>();
|
var application2 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application2Settings = new WhitelistApplication { ShowInShell = false };
|
var application2Settings = new WhitelistApplication { ShowInShell = false };
|
||||||
var application3 = new Mock<IApplication>();
|
var application3 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application3Settings = new WhitelistApplication { ShowInShell = true };
|
var application3Settings = new WhitelistApplication { ShowInShell = true };
|
||||||
|
|
||||||
application1.SetupGet(a => a.Id).Returns(application1Settings.Id);
|
application1.SetupGet(a => a.Id).Returns(application1Settings.Id);
|
||||||
|
@ -140,25 +140,25 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
|
|
||||||
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Exactly(2));
|
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Exactly(2));
|
||||||
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Exactly(2));
|
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Exactly(2));
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == application1.Object)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == application1.Object)), Times.Once);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == application2.Object)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == application2.Object)), Times.Once);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == application3.Object)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == application3.Object)), Times.Once);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application1.Object), Location.ActionCenter), Times.Once);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application1.Object), Location.ActionCenter), Times.Once);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application1.Object), Location.Taskbar), Times.Once);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application1.Object), Location.Taskbar), Times.Once);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application2.Object), Location.ActionCenter), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application2.Object), Location.ActionCenter), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application2.Object), Location.Taskbar), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application2.Object), Location.Taskbar), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application3.Object), Location.ActionCenter), Times.Once);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application3.Object), Location.ActionCenter), Times.Once);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application3.Object), Location.Taskbar), Times.Once);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application3.Object), Location.Taskbar), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Perform_MustNotAddApplicationsToShellIfNotEnabled()
|
public void Perform_MustNotAddApplicationsToShellIfNotEnabled()
|
||||||
{
|
{
|
||||||
var application1 = new Mock<IApplication>();
|
var application1 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application1Settings = new WhitelistApplication { ShowInShell = true };
|
var application1Settings = new WhitelistApplication { ShowInShell = true };
|
||||||
var application2 = new Mock<IApplication>();
|
var application2 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application2Settings = new WhitelistApplication { ShowInShell = true };
|
var application2Settings = new WhitelistApplication { ShowInShell = true };
|
||||||
var application3 = new Mock<IApplication>();
|
var application3 = new Mock<IApplication<IApplicationWindow>>();
|
||||||
var application3Settings = new WhitelistApplication { ShowInShell = true };
|
var application3Settings = new WhitelistApplication { ShowInShell = true };
|
||||||
|
|
||||||
application1.SetupGet(a => a.Id).Returns(application1Settings.Id);
|
application1.SetupGet(a => a.Id).Returns(application1Settings.Id);
|
||||||
|
@ -178,15 +178,15 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
||||||
|
|
||||||
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Never);
|
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Never);
|
||||||
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Never);
|
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), false), Times.Never);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == application1.Object)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == application1.Object)), Times.Once);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == application2.Object)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == application2.Object)), Times.Once);
|
||||||
taskview.Verify(t => t.Add(It.Is<IApplication>(a => a == application3.Object)), Times.Once);
|
taskview.Verify(t => t.Add(It.Is<IApplication<IApplicationWindow>>(a => a == application3.Object)), Times.Once);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application1.Object), Location.ActionCenter), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application1.Object), Location.ActionCenter), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application1.Object), Location.Taskbar), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application1.Object), Location.Taskbar), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application2.Object), Location.ActionCenter), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application2.Object), Location.ActionCenter), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application2.Object), Location.Taskbar), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application2.Object), Location.Taskbar), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application3.Object), Location.ActionCenter), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application3.Object), Location.ActionCenter), Times.Never);
|
||||||
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication>(a => a == application3.Object), Location.Taskbar), Times.Never);
|
uiFactory.Verify(f => f.CreateApplicationControl(It.Is<IApplication<IApplicationWindow>>(a => a == application3.Object), Location.Taskbar), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace SafeExamBrowser.Client
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All applications allowed for the current session.
|
/// All applications allowed for the current session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IList<IApplication> Applications { get; }
|
internal IList<IApplication<IApplicationWindow>> Applications { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The global application configuration.
|
/// The global application configuration.
|
||||||
|
@ -72,7 +72,7 @@ namespace SafeExamBrowser.Client
|
||||||
internal ClientContext()
|
internal ClientContext()
|
||||||
{
|
{
|
||||||
Activators = new List<IActivator>();
|
Activators = new List<IActivator>();
|
||||||
Applications = new List<IApplication>();
|
Applications = new List<IApplication<IApplicationWindow>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ namespace SafeExamBrowser.Client
|
||||||
private UserInterfaceMode uiMode;
|
private UserInterfaceMode uiMode;
|
||||||
|
|
||||||
private IActionCenter actionCenter;
|
private IActionCenter actionCenter;
|
||||||
|
private ApplicationMonitor applicationMonitor;
|
||||||
private ILogger logger;
|
private ILogger logger;
|
||||||
private IMessageBox messageBox;
|
private IMessageBox messageBox;
|
||||||
private INativeMethods nativeMethods;
|
private INativeMethods nativeMethods;
|
||||||
|
@ -95,6 +96,7 @@ namespace SafeExamBrowser.Client
|
||||||
InitializeLogging();
|
InitializeLogging();
|
||||||
InitializeText();
|
InitializeText();
|
||||||
|
|
||||||
|
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
|
||||||
var registry = new Registry(ModuleLogger(nameof(Registry)));
|
var registry = new Registry(ModuleLogger(nameof(Registry)));
|
||||||
|
|
||||||
uiFactory = BuildUserInterfaceFactory();
|
uiFactory = BuildUserInterfaceFactory();
|
||||||
|
@ -102,6 +104,7 @@ namespace SafeExamBrowser.Client
|
||||||
context = new ClientContext();
|
context = new ClientContext();
|
||||||
messageBox = BuildMessageBox();
|
messageBox = BuildMessageBox();
|
||||||
nativeMethods = new NativeMethods();
|
nativeMethods = new NativeMethods();
|
||||||
|
applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory);
|
||||||
networkAdapter = new NetworkAdapter(ModuleLogger(nameof(NetworkAdapter)), nativeMethods);
|
networkAdapter = new NetworkAdapter(ModuleLogger(nameof(NetworkAdapter)), nativeMethods);
|
||||||
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
|
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
|
||||||
systemInfo = new SystemInfo(registry);
|
systemInfo = new SystemInfo(registry);
|
||||||
|
@ -109,8 +112,6 @@ namespace SafeExamBrowser.Client
|
||||||
taskview = uiFactory.CreateTaskview();
|
taskview = uiFactory.CreateTaskview();
|
||||||
userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
|
userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
|
||||||
|
|
||||||
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
|
|
||||||
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory);
|
|
||||||
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry))));
|
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry))));
|
||||||
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
|
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
|
||||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||||
|
@ -288,7 +289,16 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
private IOperation BuildProctoringOperation()
|
private IOperation BuildProctoringOperation()
|
||||||
{
|
{
|
||||||
var controller = new ProctoringController(context.AppConfig, new FileSystem(), ModuleLogger(nameof(ProctoringController)), nativeMethods, context.Server, text, uiFactory);
|
var controller = new ProctoringController(
|
||||||
|
context.AppConfig,
|
||||||
|
applicationMonitor,
|
||||||
|
context.Browser,
|
||||||
|
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;
|
||||||
|
|
|
@ -17,11 +17,11 @@ namespace SafeExamBrowser.Client.Operations
|
||||||
{
|
{
|
||||||
internal class BrowserOperation : ClientOperation
|
internal class BrowserOperation : ClientOperation
|
||||||
{
|
{
|
||||||
private IActionCenter actionCenter;
|
private readonly IActionCenter actionCenter;
|
||||||
private ILogger logger;
|
private readonly ILogger logger;
|
||||||
private ITaskbar taskbar;
|
private readonly ITaskbar taskbar;
|
||||||
private ITaskview taskview;
|
private readonly ITaskview taskview;
|
||||||
private IUserInterfaceFactory uiFactory;
|
private readonly IUserInterfaceFactory uiFactory;
|
||||||
|
|
||||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||||
public override event StatusChangedEventHandler StatusChanged;
|
public override event StatusChangedEventHandler StatusChanged;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides information about the currently active application.
|
||||||
|
/// </summary>
|
||||||
|
public class ActiveApplication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The process which owns the currently active window.
|
||||||
|
/// </summary>
|
||||||
|
public IProcess Process { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently active window (i.e. the window which currently has input focus).
|
||||||
|
/// </summary>
|
||||||
|
public IWindow Window { get; }
|
||||||
|
|
||||||
|
public ActiveApplication(IProcess process, IWindow window)
|
||||||
|
{
|
||||||
|
Process = process;
|
||||||
|
Window = window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,12 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the currently active application (i.e. the application which currently has input focus). Returns <c>true</c> if
|
||||||
|
/// successful, otherwise <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
bool TryGetActiveApplication(out ActiveApplication application);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to terminate all processes of the specified application. Returns <c>true</c> if successful, otherwise <c>false</c>.
|
/// Attempts to terminate all processes of the specified application. Returns <c>true</c> if successful, otherwise <c>false</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
28
SafeExamBrowser.Monitoring.Contracts/Applications/IWindow.cs
Normal file
28
SafeExamBrowser.Monitoring.Contracts/Applications/IWindow.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a native window handled by the operating system.
|
||||||
|
/// </summary>
|
||||||
|
public interface IWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The handle of the window.
|
||||||
|
/// </summary>
|
||||||
|
IntPtr Handle { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The title of the window.
|
||||||
|
/// </summary>
|
||||||
|
string Title { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,8 +55,10 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Applications\ActiveApplication.cs" />
|
||||||
<Compile Include="Applications\Events\InstanceStartedEventHandler.cs" />
|
<Compile Include="Applications\Events\InstanceStartedEventHandler.cs" />
|
||||||
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
|
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
|
||||||
|
<Compile Include="Applications\IWindow.cs" />
|
||||||
<Compile Include="Applications\RunningApplication.cs" />
|
<Compile Include="Applications\RunningApplication.cs" />
|
||||||
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
|
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
|
||||||
<Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" />
|
<Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" />
|
||||||
|
|
|
@ -92,6 +92,18 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetActiveApplication(out ActiveApplication application)
|
||||||
|
{
|
||||||
|
application = default;
|
||||||
|
|
||||||
|
if (activeWindow != default && TryGetProcessFor(activeWindow, out var process))
|
||||||
|
{
|
||||||
|
application = new ActiveApplication(process, new Window { Handle = activeWindow.Handle, Title = activeWindow.Title });
|
||||||
|
}
|
||||||
|
|
||||||
|
return application != default;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryTerminate(RunningApplication application)
|
public bool TryTerminate(RunningApplication application)
|
||||||
{
|
{
|
||||||
var success = true;
|
var success = true;
|
||||||
|
@ -355,23 +367,19 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
|
|
||||||
private bool IsAllowed(Window window)
|
private bool IsAllowed(Window window)
|
||||||
{
|
{
|
||||||
var processId = Convert.ToInt32(nativeMethods.GetProcessIdFor(window.Handle));
|
var allowed = false;
|
||||||
|
|
||||||
if (processFactory.TryGetById(processId, out var process))
|
if (TryGetProcessFor(window, out var process))
|
||||||
{
|
{
|
||||||
if (BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _))
|
allowed = BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Warn($"Window {window} belongs to not whitelisted process '{process.Name}'!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.Error($"Could not find process for window {window} and process ID = {processId}!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (!allowed)
|
||||||
|
{
|
||||||
|
logger.Warn($"Window {window} belongs to not whitelisted process '{process?.Name ?? "n/a"}'!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsWhitelisted(IProcess process, out Guid? applicationId)
|
private bool IsWhitelisted(IProcess process, out Guid? applicationId)
|
||||||
|
@ -391,6 +399,18 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryGetProcessFor(Window window, out IProcess process)
|
||||||
|
{
|
||||||
|
var processId = Convert.ToInt32(nativeMethods.GetProcessIdFor(window.Handle));
|
||||||
|
|
||||||
|
if (!processFactory.TryGetById(processId, out process))
|
||||||
|
{
|
||||||
|
logger.Error($"Could not find process for window {window} and process ID = {processId}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return process != default;
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryHide(Window window)
|
private bool TryHide(Window window)
|
||||||
{
|
{
|
||||||
var success = nativeMethods.HideWindow(window.Handle);
|
var success = nativeMethods.HideWindow(window.Handle);
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Monitoring.Applications
|
namespace SafeExamBrowser.Monitoring.Applications
|
||||||
{
|
{
|
||||||
internal class Window
|
internal class Window : IWindow
|
||||||
{
|
{
|
||||||
public IntPtr Handle { get; set; }
|
public IntPtr Handle { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
using SafeExamBrowser.Configuration.Contracts;
|
using SafeExamBrowser.Configuration.Contracts;
|
||||||
using SafeExamBrowser.Core.Contracts.Notifications;
|
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
using SafeExamBrowser.Proctoring.Contracts;
|
using SafeExamBrowser.Proctoring.Contracts;
|
||||||
using SafeExamBrowser.Proctoring.Contracts.Events;
|
using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||||
using SafeExamBrowser.Server.Contracts;
|
using SafeExamBrowser.Server.Contracts;
|
||||||
|
@ -39,6 +41,8 @@ namespace SafeExamBrowser.Proctoring
|
||||||
|
|
||||||
public ProctoringController(
|
public ProctoringController(
|
||||||
AppConfig appConfig,
|
AppConfig appConfig,
|
||||||
|
IApplicationMonitor applicationMonitor,
|
||||||
|
IBrowserApplication browser,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IModuleLogger logger,
|
IModuleLogger logger,
|
||||||
INativeMethods nativeMethods,
|
INativeMethods nativeMethods,
|
||||||
|
@ -49,7 +53,7 @@ namespace SafeExamBrowser.Proctoring
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
||||||
factory = new ProctoringFactory(appConfig, fileSystem, logger, nativeMethods, text, uiFactory);
|
factory = new ProctoringFactory(appConfig, applicationMonitor, browser, fileSystem, logger, nativeMethods, text, uiFactory);
|
||||||
implementations = new List<ProctoringImplementation>();
|
implementations = new List<ProctoringImplementation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
using SafeExamBrowser.Configuration.Contracts;
|
using SafeExamBrowser.Configuration.Contracts;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
using SafeExamBrowser.Proctoring.JitsiMeet;
|
using SafeExamBrowser.Proctoring.JitsiMeet;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring;
|
using SafeExamBrowser.Proctoring.ScreenProctoring;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
||||||
|
@ -23,6 +25,8 @@ namespace SafeExamBrowser.Proctoring
|
||||||
internal class ProctoringFactory
|
internal class ProctoringFactory
|
||||||
{
|
{
|
||||||
private readonly AppConfig appConfig;
|
private readonly AppConfig appConfig;
|
||||||
|
private readonly IApplicationMonitor applicationMonitor;
|
||||||
|
private readonly IBrowserApplication browser;
|
||||||
private readonly IFileSystem fileSystem;
|
private readonly IFileSystem fileSystem;
|
||||||
private readonly IModuleLogger logger;
|
private readonly IModuleLogger logger;
|
||||||
private readonly INativeMethods nativeMethods;
|
private readonly INativeMethods nativeMethods;
|
||||||
|
@ -31,6 +35,8 @@ namespace SafeExamBrowser.Proctoring
|
||||||
|
|
||||||
public ProctoringFactory(
|
public ProctoringFactory(
|
||||||
AppConfig appConfig,
|
AppConfig appConfig,
|
||||||
|
IApplicationMonitor applicationMonitor,
|
||||||
|
IBrowserApplication browser,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IModuleLogger logger,
|
IModuleLogger logger,
|
||||||
INativeMethods nativeMethods,
|
INativeMethods nativeMethods,
|
||||||
|
@ -38,6 +44,8 @@ namespace SafeExamBrowser.Proctoring
|
||||||
IUserInterfaceFactory uiFactory)
|
IUserInterfaceFactory uiFactory)
|
||||||
{
|
{
|
||||||
this.appConfig = appConfig;
|
this.appConfig = appConfig;
|
||||||
|
this.applicationMonitor = applicationMonitor;
|
||||||
|
this.browser = browser;
|
||||||
this.fileSystem = fileSystem;
|
this.fileSystem = fileSystem;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.nativeMethods = nativeMethods;
|
this.nativeMethods = nativeMethods;
|
||||||
|
@ -61,7 +69,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, nativeMethods, service, settings, text));
|
implementations.Add(new ScreenProctoringImplementation(applicationMonitor, browser, logger, nativeMethods, service, settings, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
return implementations;
|
return implementations;
|
||||||
|
|
|
@ -91,7 +91,11 @@
|
||||||
<Compile Include="ProctoringFactory.cs" />
|
<Compile Include="ProctoringFactory.cs" />
|
||||||
<Compile Include="ProctoringImplementation.cs" />
|
<Compile Include="ProctoringImplementation.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="ScreenProctoring\Data\IntervalTrigger.cs" />
|
||||||
|
<Compile Include="ScreenProctoring\Data\KeyboardTrigger.cs" />
|
||||||
|
<Compile Include="ScreenProctoring\Data\MouseTrigger.cs" />
|
||||||
<Compile Include="ScreenProctoring\Imaging\Extensions.cs" />
|
<Compile Include="ScreenProctoring\Imaging\Extensions.cs" />
|
||||||
|
<Compile Include="ScreenProctoring\Data\Metadata.cs" />
|
||||||
<Compile Include="ScreenProctoring\Imaging\ProcessingOrder.cs" />
|
<Compile Include="ScreenProctoring\Imaging\ProcessingOrder.cs" />
|
||||||
<Compile Include="ScreenProctoring\Imaging\ScreenShot.cs" />
|
<Compile Include="ScreenProctoring\Imaging\ScreenShot.cs" />
|
||||||
<Compile Include="ScreenProctoring\ScreenProctoringImplementation.cs" />
|
<Compile Include="ScreenProctoring\ScreenProctoringImplementation.cs" />
|
||||||
|
@ -109,6 +113,14 @@
|
||||||
<Compile Include="ScreenProctoring\Service\ServiceResponse.cs" />
|
<Compile Include="ScreenProctoring\Service\ServiceResponse.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SafeExamBrowser.Applications.Contracts\SafeExamBrowser.Applications.Contracts.csproj">
|
||||||
|
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
|
||||||
|
<Name>SafeExamBrowser.Applications.Contracts</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
|
||||||
|
<Project>{5FB5273D-277C-41DD-8593-A25CE1AFF2E9}</Project>
|
||||||
|
<Name>SafeExamBrowser.Browser.Contracts</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
|
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
|
||||||
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
|
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
|
||||||
<Name>SafeExamBrowser.Configuration.Contracts</Name>
|
<Name>SafeExamBrowser.Configuration.Contracts</Name>
|
||||||
|
@ -125,6 +137,10 @@
|
||||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
|
||||||
|
<Project>{6D563A30-366D-4C35-815B-2C9E6872278B}</Project>
|
||||||
|
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
|
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
|
||||||
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
|
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
|
||||||
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
|
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.Proctoring.ScreenProctoring.Data
|
||||||
|
{
|
||||||
|
internal class IntervalTrigger
|
||||||
|
{
|
||||||
|
public int ConfigurationValue { get; internal set; }
|
||||||
|
public int TimeElapsed { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System.Windows.Input;
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
|
||||||
|
{
|
||||||
|
internal class KeyboardTrigger
|
||||||
|
{
|
||||||
|
public Key Key { get; internal set; }
|
||||||
|
public KeyModifier Modifier { get; internal set; }
|
||||||
|
public KeyState State { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
158
SafeExamBrowser.Proctoring/ScreenProctoring/Data/Metadata.cs
Normal file
158
SafeExamBrowser.Proctoring/ScreenProctoring/Data/Metadata.cs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests;
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
|
||||||
|
{
|
||||||
|
internal class Metadata
|
||||||
|
{
|
||||||
|
private readonly IApplicationMonitor applicationMonitor;
|
||||||
|
private readonly IBrowserApplication browser;
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
internal string ApplicationInfo { get; private set; }
|
||||||
|
internal string BrowserInfo { get; private set; }
|
||||||
|
internal string TriggerInfo { get; private set; }
|
||||||
|
internal string Urls { get; private set; }
|
||||||
|
internal string WindowTitle { get; private set; }
|
||||||
|
|
||||||
|
internal Metadata(IApplicationMonitor applicationMonitor, IBrowserApplication browser, ILogger logger)
|
||||||
|
{
|
||||||
|
this.applicationMonitor = applicationMonitor;
|
||||||
|
this.browser = browser;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Capture(IntervalTrigger interval = default, KeyboardTrigger keyboard = default, MouseTrigger mouse = default)
|
||||||
|
{
|
||||||
|
CaptureApplicationData();
|
||||||
|
CaptureBrowserData();
|
||||||
|
|
||||||
|
if (interval != default)
|
||||||
|
{
|
||||||
|
CaptureIntervalTrigger(interval);
|
||||||
|
}
|
||||||
|
else if (keyboard != default)
|
||||||
|
{
|
||||||
|
CaptureKeyboardTrigger(keyboard);
|
||||||
|
}
|
||||||
|
else if (mouse != default)
|
||||||
|
{
|
||||||
|
CaptureMouseTrigger(mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug($"Captured metadata: {ApplicationInfo} / {BrowserInfo} / {TriggerInfo} / {Urls} / {WindowTitle}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string ToJson()
|
||||||
|
{
|
||||||
|
var json = new JObject
|
||||||
|
{
|
||||||
|
[Header.Metadata.ApplicationInfo] = ApplicationInfo,
|
||||||
|
[Header.Metadata.BrowserInfo] = BrowserInfo,
|
||||||
|
[Header.Metadata.BrowserUrls] = Urls,
|
||||||
|
[Header.Metadata.TriggerInfo] = TriggerInfo,
|
||||||
|
[Header.Metadata.WindowTitle] = WindowTitle
|
||||||
|
};
|
||||||
|
|
||||||
|
return json.ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureApplicationData()
|
||||||
|
{
|
||||||
|
if (applicationMonitor.TryGetActiveApplication(out var application))
|
||||||
|
{
|
||||||
|
ApplicationInfo = BuildApplicationInfo(application);
|
||||||
|
WindowTitle = BuildWindowTitle(application);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ApplicationInfo = "-";
|
||||||
|
WindowTitle = "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildApplicationInfo(ActiveApplication application)
|
||||||
|
{
|
||||||
|
var info = new StringBuilder();
|
||||||
|
|
||||||
|
info.Append(application.Process.Name);
|
||||||
|
|
||||||
|
if (application.Process.OriginalName != default)
|
||||||
|
{
|
||||||
|
info.Append($" ({application.Process.OriginalName}{(application.Process.Signature == default ? ")" : "")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (application.Process.Signature != default)
|
||||||
|
{
|
||||||
|
info.Append($"{(application.Process.OriginalName == default ? "(" : ", ")}{application.Process.Signature})");
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildWindowTitle(ActiveApplication application)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(application.Window.Title) ? "-" : application.Window.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureBrowserData()
|
||||||
|
{
|
||||||
|
var windows = browser.GetWindows();
|
||||||
|
|
||||||
|
BrowserInfo = BuildBrowserInfo(windows);
|
||||||
|
Urls = BuildUrls(windows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildUrls(IEnumerable<IBrowserWindow> windows)
|
||||||
|
{
|
||||||
|
return string.Join(", ", windows.Select(w => w.Url));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildBrowserInfo(IEnumerable<IBrowserWindow> windows)
|
||||||
|
{
|
||||||
|
return string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title} ({w.Url})"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureIntervalTrigger(IntervalTrigger interval)
|
||||||
|
{
|
||||||
|
TriggerInfo = $"Maximum interval of {interval.ConfigurationValue}ms has been reached ({interval.TimeElapsed}ms).";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureKeyboardTrigger(KeyboardTrigger keyboard)
|
||||||
|
{
|
||||||
|
var flags = Enum.GetValues(typeof(KeyModifier)).OfType<KeyModifier>().Where(m => m != KeyModifier.None && keyboard.Modifier.HasFlag(m));
|
||||||
|
var modifiers = flags.Any() ? string.Join(" + ", flags) + " + " : string.Empty;
|
||||||
|
|
||||||
|
TriggerInfo = $"'{modifiers}{keyboard.Key}' has been {keyboard.State.ToString().ToLower()}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureMouseTrigger(MouseTrigger mouse)
|
||||||
|
{
|
||||||
|
if (mouse.Info.IsTouch)
|
||||||
|
{
|
||||||
|
TriggerInfo = $"Tap as {mouse.Button} mouse button has been {mouse.State.ToString().ToLower()} at ({mouse.Info.X}/{mouse.Info.Y}).";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TriggerInfo = $"{mouse.Button} mouse button has been {mouse.State.ToString().ToLower()} at ({mouse.Info.X}/{mouse.Info.Y}).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
|
||||||
|
{
|
||||||
|
internal class MouseTrigger
|
||||||
|
{
|
||||||
|
public MouseButton Button { get; internal set; }
|
||||||
|
public MouseInformation Info { get; internal set; }
|
||||||
|
public MouseButtonState State { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,10 +11,13 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
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;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
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;
|
||||||
|
@ -31,6 +34,8 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
{
|
{
|
||||||
private readonly object @lock = new object();
|
private readonly object @lock = new object();
|
||||||
|
|
||||||
|
private readonly IApplicationMonitor applicationMonitor;
|
||||||
|
private readonly IBrowserApplication browser;
|
||||||
private readonly IModuleLogger logger;
|
private readonly IModuleLogger logger;
|
||||||
private readonly INativeMethods nativeMethods;
|
private readonly INativeMethods nativeMethods;
|
||||||
private readonly ServiceProxy service;
|
private readonly ServiceProxy service;
|
||||||
|
@ -47,12 +52,16 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
public override event NotificationChangedEventHandler NotificationChanged;
|
public override event NotificationChangedEventHandler NotificationChanged;
|
||||||
|
|
||||||
internal ScreenProctoringImplementation(
|
internal ScreenProctoringImplementation(
|
||||||
|
IApplicationMonitor applicationMonitor,
|
||||||
|
IBrowserApplication browser,
|
||||||
IModuleLogger logger,
|
IModuleLogger logger,
|
||||||
INativeMethods nativeMethods,
|
INativeMethods nativeMethods,
|
||||||
ServiceProxy service,
|
ServiceProxy service,
|
||||||
ProctoringSettings settings,
|
ProctoringSettings settings,
|
||||||
IText text)
|
IText text)
|
||||||
{
|
{
|
||||||
|
this.applicationMonitor = applicationMonitor;
|
||||||
|
this.browser = browser;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.nativeMethods = nativeMethods;
|
this.nativeMethods = nativeMethods;
|
||||||
this.service = service;
|
this.service = service;
|
||||||
|
@ -194,18 +203,28 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
|
|
||||||
private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
|
private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
|
||||||
{
|
{
|
||||||
var key = KeyInterop.KeyFromVirtualKey(keyCode);
|
var trigger = new KeyboardTrigger
|
||||||
|
{
|
||||||
|
Key = KeyInterop.KeyFromVirtualKey(keyCode),
|
||||||
|
Modifier = modifier,
|
||||||
|
State = state
|
||||||
|
};
|
||||||
|
|
||||||
TryExecute();
|
TryExecute(keyboard: trigger);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
|
private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
|
||||||
{
|
{
|
||||||
var isTouch = info.IsTouch;
|
var trigger = new MouseTrigger
|
||||||
|
{
|
||||||
|
Button = button,
|
||||||
|
Info = info,
|
||||||
|
State = state
|
||||||
|
};
|
||||||
|
|
||||||
TryExecute();
|
TryExecute(mouse: trigger);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -238,10 +257,16 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
|
|
||||||
private void Timer_Elapsed(object sender, ElapsedEventArgs args)
|
private void Timer_Elapsed(object sender, ElapsedEventArgs args)
|
||||||
{
|
{
|
||||||
TryExecute();
|
var trigger = new IntervalTrigger
|
||||||
|
{
|
||||||
|
ConfigurationValue = settings.MaxInterval,
|
||||||
|
TimeElapsed = Convert.ToInt32(DateTime.Now.Subtract(last).TotalMilliseconds)
|
||||||
|
};
|
||||||
|
|
||||||
|
TryExecute(interval: trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryExecute()
|
private void TryExecute(IntervalTrigger interval = default, KeyboardTrigger keyboard = default, MouseTrigger mouse = default)
|
||||||
{
|
{
|
||||||
if (MinimumIntervalElapsed() && Monitor.TryEnter(@lock))
|
if (MinimumIntervalElapsed() && Monitor.TryEnter(@lock))
|
||||||
{
|
{
|
||||||
|
@ -252,16 +277,27 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var metadata = new Metadata(applicationMonitor, browser, logger.CloneFor(nameof(Metadata)));
|
||||||
|
|
||||||
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
|
using (var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings))
|
||||||
{
|
{
|
||||||
|
metadata.Capture(interval, keyboard, mouse);
|
||||||
screenShot.Take();
|
screenShot.Take();
|
||||||
screenShot.Compress();
|
screenShot.Compress();
|
||||||
service.Send(screenShot);
|
|
||||||
|
if (service.IsConnected)
|
||||||
|
{
|
||||||
|
service.Send(metadata, screenShot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Warn("Cannot send screen shot as service is disconnected!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.Error("Failed to process screen shot!", e);
|
logger.Error("Failed to execute capturing and/or transmission!", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,18 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
|
||||||
internal const string ACCEPT = "Accept";
|
internal const string ACCEPT = "Accept";
|
||||||
internal const string AUTHORIZATION = "Authorization";
|
internal const string AUTHORIZATION = "Authorization";
|
||||||
internal const string GROUP_ID = "SEB_GROUP_UUID";
|
internal const string GROUP_ID = "SEB_GROUP_UUID";
|
||||||
|
internal const string IMAGE_FORMAT = "imageFormat";
|
||||||
|
internal const string METADATA = "metaData";
|
||||||
internal const string SESSION_ID = "SEB_SESSION_UUID";
|
internal const string SESSION_ID = "SEB_SESSION_UUID";
|
||||||
|
internal const string TIMESTAMP = "timestamp";
|
||||||
|
|
||||||
|
internal static class Metadata
|
||||||
|
{
|
||||||
|
internal const string ApplicationInfo = "screenProctoringMetadataApplication";
|
||||||
|
internal const string BrowserInfo = "screenProctoringMetadataBrowser";
|
||||||
|
internal const string BrowserUrls = "screenProctoringMetadataURL";
|
||||||
|
internal const string TriggerInfo = "screenProctoringMetadataUserAction";
|
||||||
|
internal const string WindowTitle = "screenProctoringMetadataWindowTitle";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
||||||
using SafeExamBrowser.Settings.Proctoring;
|
using SafeExamBrowser.Settings.Proctoring;
|
||||||
|
|
||||||
|
@ -20,12 +21,13 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryExecute(ScreenShot screenShot, string sessionId, out string message)
|
internal bool TryExecute(Metadata metadata, ScreenShot screenShot, string sessionId, out string message)
|
||||||
{
|
{
|
||||||
var imageFormat = ("imageFormat", ToString(screenShot.Format));
|
var data = (Header.METADATA, metadata.ToJson());
|
||||||
var timestamp = ("timestamp", DateTime.Now.ToUnixTimestamp().ToString());
|
var imageFormat = (Header.IMAGE_FORMAT, ToString(screenShot.Format));
|
||||||
|
var timestamp = (Header.TIMESTAMP, DateTime.Now.ToUnixTimestamp().ToString());
|
||||||
var url = api.ScreenShotEndpoint.Replace(Api.SESSION_ID, sessionId);
|
var url = api.ScreenShotEndpoint.Replace(Api.SESSION_ID, sessionId);
|
||||||
var success = TryExecute(HttpMethod.Post, url, out var response, screenShot.Data, ContentType.OCTET_STREAM, Authorization, imageFormat, timestamp);
|
var success = TryExecute(HttpMethod.Post, url, out var response, screenShot.Data, ContentType.OCTET_STREAM, Authorization, data, imageFormat, timestamp);
|
||||||
|
|
||||||
message = response.ToLogString();
|
message = response.ToLogString();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
||||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests;
|
using SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests;
|
||||||
|
|
||||||
|
@ -69,10 +70,10 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
|
||||||
return new ServiceResponse(success, message);
|
return new ServiceResponse(success, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServiceResponse Send(ScreenShot screenShot)
|
internal ServiceResponse Send(Metadata metadata, 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(metadata, screenShot, SessionId, out var message);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace SafeExamBrowser.UserInterface.Contracts
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an application control for the specified application and location.
|
/// Creates an application control for the specified application and location.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IApplicationControl CreateApplicationControl(IApplication application, Location location);
|
IApplicationControl CreateApplicationControl(IApplication<IApplicationWindow> application, Location location);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a system control which allows to change the audio settings of the computer.
|
/// Creates a system control which allows to change the audio settings of the computer.
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the given application to the task view.
|
/// Adds the given application to the task view.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Add(IApplication application);
|
void Add(IApplication<IApplicationWindow> application);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers the specified activator for the task view.
|
/// Registers the specified activator for the task view.
|
||||||
|
|
|
@ -17,12 +17,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||||
{
|
{
|
||||||
internal partial class ApplicationButton : UserControl
|
internal partial class ApplicationButton : UserControl
|
||||||
{
|
{
|
||||||
private readonly IApplication application;
|
private readonly IApplication<IApplicationWindow> application;
|
||||||
private readonly IApplicationWindow window;
|
private readonly IApplicationWindow window;
|
||||||
|
|
||||||
internal event EventHandler Clicked;
|
internal event EventHandler Clicked;
|
||||||
|
|
||||||
internal ApplicationButton(IApplication application, IApplicationWindow window = null)
|
internal ApplicationButton(IApplication<IApplicationWindow> application, IApplicationWindow window = null)
|
||||||
{
|
{
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
|
|
@ -15,9 +15,9 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||||
{
|
{
|
||||||
internal partial class ApplicationControl : UserControl, IApplicationControl
|
internal partial class ApplicationControl : UserControl, IApplicationControl
|
||||||
{
|
{
|
||||||
private IApplication application;
|
private readonly IApplication<IApplicationWindow> application;
|
||||||
|
|
||||||
internal ApplicationControl(IApplication application)
|
internal ApplicationControl(IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
this.application = application;
|
this.application = application;
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||||
{
|
{
|
||||||
internal partial class ApplicationControl : UserControl, IApplicationControl
|
internal partial class ApplicationControl : UserControl, IApplicationControl
|
||||||
{
|
{
|
||||||
private readonly IApplication application;
|
private readonly IApplication<IApplicationWindow> application;
|
||||||
private IApplicationWindow single;
|
private IApplicationWindow single;
|
||||||
|
|
||||||
internal ApplicationControl(IApplication application)
|
internal ApplicationControl(IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
this.application = application;
|
this.application = application;
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
||||||
return new ActionCenter();
|
return new ActionCenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IApplicationControl CreateApplicationControl(IApplication application, Location location)
|
public IApplicationControl CreateApplicationControl(IApplication<IApplicationWindow> application, Location location)
|
||||||
{
|
{
|
||||||
if (location == Location.ActionCenter)
|
if (location == Location.ActionCenter)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,22 +20,22 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
|
||||||
{
|
{
|
||||||
internal partial class Taskview : Window, ITaskview
|
internal partial class Taskview : Window, ITaskview
|
||||||
{
|
{
|
||||||
private IList<IApplication> applications;
|
private readonly IList<IApplication<IApplicationWindow>> applications;
|
||||||
private LinkedListNode<WindowControl> current;
|
private readonly LinkedList<WindowControl> controls;
|
||||||
private LinkedList<WindowControl> controls;
|
|
||||||
|
|
||||||
|
private LinkedListNode<WindowControl> current;
|
||||||
internal IntPtr Handle { get; private set; }
|
internal IntPtr Handle { get; private set; }
|
||||||
|
|
||||||
internal Taskview()
|
internal Taskview()
|
||||||
{
|
{
|
||||||
applications = new List<IApplication>();
|
applications = new List<IApplication<IApplicationWindow>>();
|
||||||
controls = new LinkedList<WindowControl>();
|
controls = new LinkedList<WindowControl>();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
InitializeTaskview();
|
InitializeTaskview();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(IApplication application)
|
public void Add(IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
application.WindowsChanged += Application_WindowsChanged;
|
application.WindowsChanged += Application_WindowsChanged;
|
||||||
applications.Add(application);
|
applications.Add(application);
|
||||||
|
|
|
@ -17,12 +17,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
|
||||||
{
|
{
|
||||||
internal partial class ApplicationButton : UserControl
|
internal partial class ApplicationButton : UserControl
|
||||||
{
|
{
|
||||||
private readonly IApplication application;
|
private readonly IApplication<IApplicationWindow> application;
|
||||||
private readonly IApplicationWindow window;
|
private readonly IApplicationWindow window;
|
||||||
|
|
||||||
internal event EventHandler Clicked;
|
internal event EventHandler Clicked;
|
||||||
|
|
||||||
internal ApplicationButton(IApplication application, IApplicationWindow window = null)
|
internal ApplicationButton(IApplication<IApplicationWindow> application, IApplicationWindow window = null)
|
||||||
{
|
{
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
|
|
@ -15,9 +15,9 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
|
||||||
{
|
{
|
||||||
internal partial class ApplicationControl : UserControl, IApplicationControl
|
internal partial class ApplicationControl : UserControl, IApplicationControl
|
||||||
{
|
{
|
||||||
private IApplication application;
|
private readonly IApplication<IApplicationWindow> application;
|
||||||
|
|
||||||
internal ApplicationControl(IApplication application)
|
internal ApplicationControl(IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
this.application = application;
|
this.application = application;
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
|
||||||
{
|
{
|
||||||
internal partial class ApplicationControl : UserControl, IApplicationControl
|
internal partial class ApplicationControl : UserControl, IApplicationControl
|
||||||
{
|
{
|
||||||
private readonly IApplication application;
|
private readonly IApplication<IApplicationWindow> application;
|
||||||
private IApplicationWindow single;
|
private IApplicationWindow single;
|
||||||
|
|
||||||
internal ApplicationControl(IApplication application)
|
internal ApplicationControl(IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
this.application = application;
|
this.application = application;
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
||||||
return new ActionCenter();
|
return new ActionCenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IApplicationControl CreateApplicationControl(IApplication application, Location location)
|
public IApplicationControl CreateApplicationControl(IApplication<IApplicationWindow> application, Location location)
|
||||||
{
|
{
|
||||||
if (location == Location.ActionCenter)
|
if (location == Location.ActionCenter)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,22 +20,23 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||||
{
|
{
|
||||||
internal partial class Taskview : Window, ITaskview
|
internal partial class Taskview : Window, ITaskview
|
||||||
{
|
{
|
||||||
private IList<IApplication> applications;
|
private readonly IList<IApplication<IApplicationWindow>> applications;
|
||||||
|
private readonly LinkedList<WindowControl> controls;
|
||||||
|
|
||||||
private LinkedListNode<WindowControl> current;
|
private LinkedListNode<WindowControl> current;
|
||||||
private LinkedList<WindowControl> controls;
|
|
||||||
|
|
||||||
internal IntPtr Handle { get; private set; }
|
internal IntPtr Handle { get; private set; }
|
||||||
|
|
||||||
internal Taskview()
|
internal Taskview()
|
||||||
{
|
{
|
||||||
applications = new List<IApplication>();
|
applications = new List<IApplication<IApplicationWindow>>();
|
||||||
controls = new LinkedList<WindowControl>();
|
controls = new LinkedList<WindowControl>();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
InitializeTaskview();
|
InitializeTaskview();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(IApplication application)
|
public void Add(IApplication<IApplicationWindow> application)
|
||||||
{
|
{
|
||||||
application.WindowsChanged += Application_WindowsChanged;
|
application.WindowsChanged += Application_WindowsChanged;
|
||||||
applications.Add(application);
|
applications.Add(application);
|
||||||
|
|
Loading…
Reference in a new issue