diff --git a/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs b/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs
deleted file mode 100644
index aa4a32b0..00000000
--- a/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2019 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.Core.Contracts;
-
-namespace SafeExamBrowser.Applications.Contracts
-{
- ///
- /// The information about an application which can be accessed via the shell.
- ///
- public class ApplicationInfo
- {
- ///
- /// Indicates whether the application should be automatically started.
- ///
- public bool AutoStart { get; set; }
-
- ///
- /// The name of the application.
- ///
- public string Name { get; set; }
-
- ///
- /// The tooltip for the application.
- ///
- public string Tooltip { get; set; }
-
- ///
- /// The resource providing the application icon.
- ///
- public IconResource Icon { get; set; }
- }
-}
diff --git a/SafeExamBrowser.Applications.Contracts/IApplication.cs b/SafeExamBrowser.Applications.Contracts/IApplication.cs
index 28f8def2..a37e9c88 100644
--- a/SafeExamBrowser.Applications.Contracts/IApplication.cs
+++ b/SafeExamBrowser.Applications.Contracts/IApplication.cs
@@ -6,8 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+using System;
using System.Collections.Generic;
using SafeExamBrowser.Applications.Contracts.Events;
+using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Applications.Contracts
{
@@ -17,9 +19,29 @@ namespace SafeExamBrowser.Applications.Contracts
public interface IApplication
{
///
- /// Provides information about the application.
+ /// Indicates whether the application should be automatically started.
///
- ApplicationInfo Info { get; }
+ bool AutoStart { get; }
+
+ ///
+ /// The resource providing the application icon.
+ ///
+ IconResource Icon { get; }
+
+ ///
+ /// The unique identifier of the application.
+ ///
+ Guid Id { get; }
+
+ ///
+ /// The name of the application.
+ ///
+ string Name { get; }
+
+ ///
+ /// The tooltip for the application.
+ ///
+ string Tooltip { get; }
///
/// Event fired when the windows of the application have changed.
diff --git a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj
index f2e84263..09a77d78 100644
--- a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj
+++ b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj
@@ -60,7 +60,6 @@
-
diff --git a/SafeExamBrowser.Applications/ApplicationFactory.cs b/SafeExamBrowser.Applications/ApplicationFactory.cs
index 9e5549b8..492eebb4 100644
--- a/SafeExamBrowser.Applications/ApplicationFactory.cs
+++ b/SafeExamBrowser.Applications/ApplicationFactory.cs
@@ -11,8 +11,8 @@ using System.Collections.Generic;
using System.IO;
using Microsoft.Win32;
using SafeExamBrowser.Applications.Contracts;
-using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.Logging.Contracts;
+using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.WindowsApi.Contracts;
@@ -20,12 +20,18 @@ namespace SafeExamBrowser.Applications
{
public class ApplicationFactory : IApplicationFactory
{
+ private IApplicationMonitor applicationMonitor;
private IModuleLogger logger;
private INativeMethods nativeMethods;
private IProcessFactory processFactory;
- public ApplicationFactory(IModuleLogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
+ public ApplicationFactory(
+ IApplicationMonitor applicationMonitor,
+ IModuleLogger logger,
+ INativeMethods nativeMethods,
+ IProcessFactory processFactory)
{
+ this.applicationMonitor = applicationMonitor;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.processFactory = processFactory;
@@ -65,9 +71,8 @@ namespace SafeExamBrowser.Applications
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
{
- var icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) };
- var info = new ApplicationInfo { AutoStart = settings.AutoStart, Icon = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName };
- var application = new ExternalApplication(executablePath, info, logger.CloneFor(settings.DisplayName), nativeMethods, processFactory);
+ var applicationLogger = logger.CloneFor(settings.DisplayName);
+ var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings);
return application;
}
diff --git a/SafeExamBrowser.Applications/ExternalApplication.cs b/SafeExamBrowser.Applications/ExternalApplication.cs
index 0e3e3e4c..708ef0cb 100644
--- a/SafeExamBrowser.Applications/ExternalApplication.cs
+++ b/SafeExamBrowser.Applications/ExternalApplication.cs
@@ -11,48 +11,68 @@ using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
+using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.Logging.Contracts;
+using SafeExamBrowser.Monitoring.Contracts.Applications;
+using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplication : IApplication
{
- private int instanceIdCounter = default(int);
+ private readonly object @lock = new object();
+ private IApplicationMonitor applicationMonitor;
private string executablePath;
private IModuleLogger logger;
private INativeMethods nativeMethods;
private IList instances;
private IProcessFactory processFactory;
+ private WhitelistApplication settings;
+
+ public bool AutoStart { get; private set; }
+ public IconResource Icon { get; private set; }
+ public Guid Id { get; private set; }
+ public string Name { get; private set; }
+ public string Tooltip { get; private set; }
public event WindowsChangedEventHandler WindowsChanged;
- public ApplicationInfo Info { get; }
-
internal ExternalApplication(
+ IApplicationMonitor applicationMonitor,
string executablePath,
- ApplicationInfo info,
IModuleLogger logger,
INativeMethods nativeMethods,
- IProcessFactory processFactory)
+ IProcessFactory processFactory,
+ WhitelistApplication settings)
{
+ this.applicationMonitor = applicationMonitor;
this.executablePath = executablePath;
- this.Info = info;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.instances = new List();
this.processFactory = processFactory;
+ this.settings = settings;
}
public IEnumerable GetWindows()
{
- return instances.SelectMany(i => i.GetWindows());
+ lock (@lock)
+ {
+ return instances.SelectMany(i => i.GetWindows());
+ }
}
public void Initialize()
{
- // Nothing to do here for now.
+ AutoStart = settings.AutoStart;
+ Icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) };
+ Id = settings.Id;
+ Name = settings.DisplayName;
+ Tooltip = settings.Description ?? settings.DisplayName;
+
+ applicationMonitor.InstanceStarted += ApplicationMonitor_InstanceStarted;
}
public void Start()
@@ -60,16 +80,7 @@ namespace SafeExamBrowser.Applications
try
{
logger.Info("Starting application...");
-
- var process = processFactory.StartNew(executablePath);
- var id = ++instanceIdCounter;
- var instanceLogger = logger.CloneFor($"{Info.Name} Instance #{id}");
- var instance = new ExternalApplicationInstance(Info.Icon, id, instanceLogger, nativeMethods, process);
-
- instance.Initialize();
- instance.Terminated += Instance_Terminated;
- instance.WindowsChanged += () => WindowsChanged?.Invoke();
- instances.Add(instance);
+ InitializeInstance(processFactory.StartNew(executablePath));
}
catch (Exception e)
{
@@ -77,22 +88,55 @@ namespace SafeExamBrowser.Applications
}
}
+ public void Terminate()
+ {
+ applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
+
+ lock (@lock)
+ {
+ if (instances.Any())
+ {
+ logger.Info("Terminating application...");
+
+ foreach (var instance in instances)
+ {
+ instance.Terminate();
+ }
+ }
+ }
+ }
+
+ private void ApplicationMonitor_InstanceStarted(Guid applicationId, IProcess process)
+ {
+ if (applicationId == Id)
+ {
+ logger.Info("New application instance was started.");
+ InitializeInstance(process);
+ }
+ }
+
private void Instance_Terminated(int id)
{
- instances.Remove(instances.First(i => i.Id == id));
+ lock (@lock)
+ {
+ instances.Remove(instances.First(i => i.Id == id));
+ }
+
WindowsChanged?.Invoke();
}
- public void Terminate()
+ private void InitializeInstance(IProcess process)
{
- if (instances.Any())
- {
- logger.Info("Terminating application...");
+ var instanceLogger = logger.CloneFor($"{Name} ({process.Id})");
+ var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process);
- foreach (var instance in instances)
- {
- instance.Terminate();
- }
+ instance.Terminated += Instance_Terminated;
+ instance.WindowsChanged += () => WindowsChanged?.Invoke();
+ instance.Initialize();
+
+ lock (@lock)
+ {
+ instances.Add(instance);
}
}
}
diff --git a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs
index 786c9b3d..2bd3ed2c 100644
--- a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs
+++ b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs
@@ -30,15 +30,14 @@ namespace SafeExamBrowser.Applications
private Timer timer;
private IList windows;
- internal int Id { get; }
+ internal int Id { get; private set; }
internal event InstanceTerminatedEventHandler Terminated;
internal event WindowsChangedEventHandler WindowsChanged;
- internal ExternalApplicationInstance(IconResource icon, int id, ILogger logger, INativeMethods nativeMethods, IProcess process)
+ internal ExternalApplicationInstance(IconResource icon, ILogger logger, INativeMethods nativeMethods, IProcess process)
{
this.icon = icon;
- this.Id = id;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.process = process;
@@ -55,6 +54,7 @@ namespace SafeExamBrowser.Applications
internal void Initialize()
{
+ Id = process.Id;
InitializeEvents();
logger.Info("Initialized application instance.");
}
@@ -106,7 +106,7 @@ namespace SafeExamBrowser.Applications
lock (@lock)
{
var closedWindows = windows.Where(w => openWindows.All(ow => ow != w.Handle)).ToList();
- var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow));
+ var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow)).ToList();
foreach (var window in closedWindows)
{
@@ -128,7 +128,6 @@ namespace SafeExamBrowser.Applications
if (changed)
{
- logger.Error("WINDOWS CHANGED!");
WindowsChanged?.Invoke();
}
diff --git a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj
index 546a58e2..1c748536 100644
--- a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj
+++ b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj
@@ -74,6 +74,10 @@
{64ea30fb-11d4-436a-9c2b-88566285363e}
SafeExamBrowser.Logging.Contracts
+
+ {6d563a30-366d-4c35-815b-2c9e6872278b}
+ SafeExamBrowser.Monitoring.Contracts
+
{30b2d907-5861-4f39-abad-c4abf1b3470e}
SafeExamBrowser.Settings
diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs
index 1b6578be..cd48c69e 100644
--- a/SafeExamBrowser.Browser/BrowserApplication.cs
+++ b/SafeExamBrowser.Browser/BrowserApplication.cs
@@ -17,6 +17,7 @@ using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
+using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Logging;
@@ -38,7 +39,11 @@ namespace SafeExamBrowser.Browser
private IText text;
private IUserInterfaceFactory uiFactory;
- public ApplicationInfo Info { get; private set; }
+ public bool AutoStart { get; private set; }
+ public IconResource Icon { get; private set; }
+ public Guid Id { get; private set; }
+ public string Name { get; private set; }
+ public string Tooltip { get; private set; }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event WindowsChangedEventHandler WindowsChanged;
@@ -102,12 +107,11 @@ namespace SafeExamBrowser.Browser
private void InitializeApplicationInfo()
{
- Info = new ApplicationInfo
- {
- Icon = new BrowserIconResource(),
- Name = "Safe Exam Browser",
- Tooltip = text.Get(TextKey.Browser_Tooltip)
- };
+ AutoStart = true;
+ Icon = new BrowserIconResource();
+ Id = Guid.NewGuid();
+ Name = text.Get(TextKey.Browser_Name);
+ Tooltip = text.Get(TextKey.Browser_Tooltip);
}
private void CreateNewInstance(string url = null)
diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs
index bdb48d94..c3212ec2 100644
--- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs
+++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs
@@ -40,7 +40,7 @@ namespace SafeExamBrowser.Client.UnitTests
private AppConfig appConfig;
private Mock actionCenter;
private Mock applicationMonitor;
- private Mock browserController;
+ private Mock browser;
private Mock clientHost;
private ClientContext context;
private Mock displayMonitor;
@@ -66,7 +66,7 @@ namespace SafeExamBrowser.Client.UnitTests
appConfig = new AppConfig();
actionCenter = new Mock();
applicationMonitor = new Mock();
- browserController = new Mock();
+ browser = new Mock();
clientHost = new Mock();
context = new ClientContext();
displayMonitor = new Mock();
@@ -106,7 +106,7 @@ namespace SafeExamBrowser.Client.UnitTests
context.AppConfig = appConfig;
context.Activators.Add(terminationActivator.Object);
- context.Browser = browserController.Object;
+ context.Browser = browser.Object;
context.ClientHost = clientHost.Object;
context.SessionId = sessionId;
context.Settings = settings;
@@ -300,7 +300,7 @@ namespace SafeExamBrowser.Client.UnitTests
It.IsAny())).Returns(MessageBoxResult.Ok);
sut.TryStart();
- browserController.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", new DownloadEventArgs());
+ browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", new DownloadEventArgs());
}
[TestMethod]
@@ -321,7 +321,7 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is(p => p == downloadPath))).Returns(new CommunicationResult(true));
sut.TryStart();
- browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
+ browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
args.Callback(true, downloadPath);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is(p => p == downloadPath)), Times.Once);
@@ -348,7 +348,7 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is(p => p == downloadPath))).Returns(new CommunicationResult(true));
sut.TryStart();
- browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
+ browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
args.Callback(false, downloadPath);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny()), Times.Never);
@@ -372,7 +372,7 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is(p => p == downloadPath))).Returns(new CommunicationResult(false));
sut.TryStart();
- browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
+ browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
args.Callback(true, downloadPath);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny()), Times.Once);
@@ -632,9 +632,9 @@ namespace SafeExamBrowser.Client.UnitTests
var application2 = new Mock();
var application3 = new Mock();
- application1.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = true });
- application2.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = false });
- application3.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = true });
+ application1.SetupGet(a => a.AutoStart).Returns(true);
+ application2.SetupGet(a => a.AutoStart).Returns(false);
+ application3.SetupGet(a => a.AutoStart).Returns(true);
context.Applications.Add(application1.Object);
context.Applications.Add(application2.Object);
context.Applications.Add(application3.Object);
@@ -647,6 +647,23 @@ namespace SafeExamBrowser.Client.UnitTests
application3.Verify(a => a.Start(), Times.Once);
}
+ [TestMethod]
+ public void Startup_MustAutoStartBrowser()
+ {
+ browser.SetupGet(b => b.AutoStart).Returns(true);
+ operationSequence.Setup(o => o.TryPerform()).Returns(OperationResult.Success);
+
+ sut.TryStart();
+
+ browser.Verify(b => b.Start(), Times.Once);
+ browser.Reset();
+ browser.SetupGet(b => b.AutoStart).Returns(false);
+
+ sut.TryStart();
+
+ browser.Verify(b => b.Start(), Times.Never);
+ }
+
[TestMethod]
public void TerminationActivator_MustCorrectlyInitiateShutdown()
{
diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs
index f26ecd93..8fe48d67 100644
--- a/SafeExamBrowser.Client/ClientController.cs
+++ b/SafeExamBrowser.Client/ClientController.cs
@@ -241,14 +241,17 @@ namespace SafeExamBrowser.Client
private void AutoStartApplications()
{
- logger.Info("Starting browser application...");
- Browser.Start();
+ if (Browser.AutoStart)
+ {
+ logger.Info("Auto-starting browser...");
+ Browser.Start();
+ }
foreach (var application in context.Applications)
{
- if (application.Info.AutoStart)
+ if (application.AutoStart)
{
- logger.Info($"Auto-starting '{application.Info.Name}'...");
+ logger.Info($"Auto-starting '{application.Name}'...");
application.Start();
}
}
diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs
index 386d4957..52bc5453 100644
--- a/SafeExamBrowser.Client/CompositionRoot.cs
+++ b/SafeExamBrowser.Client/CompositionRoot.cs
@@ -98,8 +98,8 @@ namespace SafeExamBrowser.Client
taskView = BuildTaskView();
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
- var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory);
- var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, 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);
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var hashAlgorithm = new HashAlgorithm();
diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs
index fbf939e5..1e26f2b0 100644
--- a/SafeExamBrowser.I18n.Contracts/TextKey.cs
+++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs
@@ -18,6 +18,7 @@ namespace SafeExamBrowser.I18n.Contracts
Browser_BlockedPageButton,
Browser_BlockedPageMessage,
Browser_BlockedPageTitle,
+ Browser_Name,
Browser_Tooltip,
BrowserWindow_DeveloperConsoleMenuItem,
BrowserWindow_ZoomMenuItem,
diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml
index 592feac9..228f06cb 100644
--- a/SafeExamBrowser.I18n/Text.xml
+++ b/SafeExamBrowser.I18n/Text.xml
@@ -12,6 +12,9 @@
Page Blocked
+
+ Browser
+
Browser Application
diff --git a/SafeExamBrowser.Monitoring.Contracts/Applications/Events/InstanceStartedEventHandler.cs b/SafeExamBrowser.Monitoring.Contracts/Applications/Events/InstanceStartedEventHandler.cs
new file mode 100644
index 00000000..d37e6917
--- /dev/null
+++ b/SafeExamBrowser.Monitoring.Contracts/Applications/Events/InstanceStartedEventHandler.cs
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2019 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 SafeExamBrowser.WindowsApi.Contracts;
+
+namespace SafeExamBrowser.Monitoring.Contracts.Applications.Events
+{
+ ///
+ /// Indicates that a new instance of a whitelisted application has been started.
+ ///
+ public delegate void InstanceStartedEventHandler(Guid applicationId, IProcess process);
+}
diff --git a/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs b/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs
index bd0062dd..fd3064e9 100644
--- a/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs
+++ b/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs
@@ -21,6 +21,11 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
///
event ExplorerStartedEventHandler ExplorerStarted;
+ ///
+ /// Event fired when a new instance of a whitelisted application has been started.
+ ///
+ event InstanceStartedEventHandler InstanceStarted;
+
///
/// Event fired when the automatic termination of a blacklisted application failed.
///
diff --git a/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj b/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj
index 86a1e456..b2ad98d9 100644
--- a/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj
+++ b/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj
@@ -54,6 +54,7 @@
+
diff --git a/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs b/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs
index f18f7009..5b67d180 100644
--- a/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs
+++ b/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs
@@ -33,6 +33,7 @@ namespace SafeExamBrowser.Monitoring.Applications
private Window activeWindow;
public event ExplorerStartedEventHandler ExplorerStarted;
+ public event InstanceStartedEventHandler InstanceStarted;
public event TerminationFailedEventHandler TerminationFailed;
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
@@ -134,7 +135,7 @@ namespace SafeExamBrowser.Monitoring.Applications
logger.Debug($"Process {process} has been started.");
processes.Add(process);
- if (process.Name == "explorer")
+ if (process.Name == "explorer.exe")
{
HandleExplorerStart(process);
}
@@ -142,6 +143,10 @@ namespace SafeExamBrowser.Monitoring.Applications
{
AddFailed(process, failed);
}
+ else if (IsWhitelisted(process, out var applicationId))
+ {
+ HandleInstanceStart(applicationId.Value, process);
+ }
}
foreach (var process in terminated)
@@ -238,6 +243,12 @@ namespace SafeExamBrowser.Monitoring.Applications
Task.Run(() => ExplorerStarted?.Invoke());
}
+ private void HandleInstanceStart(Guid applicationId, IProcess process)
+ {
+ logger.Debug($"Detected start of whitelisted application instance {process}.");
+ Task.Run(() => InstanceStarted?.Invoke(applicationId, process));
+ }
+
private void InitializeProcesses()
{
processes = processFactory.GetAllRunning();
@@ -329,24 +340,33 @@ namespace SafeExamBrowser.Monitoring.Applications
if (processFactory.TryGetById(processId, out var process))
{
- if (BelongsToSafeExamBrowser(process))
+ if (BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _))
{
return true;
}
- foreach (var application in whitelist)
- {
- if (BelongsToApplication(process, application))
- {
- return true;
- }
- }
-
- logger.Warn($"Window {window} belongs to not allowed process '{process.Name}'!");
+ logger.Warn($"Window {window} belongs to not whitelisted process '{process.Name}'!");
}
else
{
- logger.Error($"Could not find process for window {window} and process with ID = {processId}!");
+ logger.Error($"Could not find process for window {window} and process ID = {processId}!");
+ }
+
+ return false;
+ }
+
+ private bool IsWhitelisted(IProcess process, out Guid? applicationId)
+ {
+ applicationId = default(Guid?);
+
+ foreach (var application in whitelist)
+ {
+ if (BelongsToApplication(process, application))
+ {
+ applicationId = application.Id;
+
+ return true;
+ }
}
return false;
diff --git a/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs b/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs
index 85b49d85..7fd0a960 100644
--- a/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs
+++ b/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs
@@ -62,6 +62,11 @@ namespace SafeExamBrowser.Settings.Applications
///
public string ExecutablePath { get; set; }
+ ///
+ /// Unique identifier to be used to identify the application during runtime.
+ ///
+ public Guid Id { get; }
+
///
/// The original file name of the main executable of the application, if available.
///
@@ -80,6 +85,7 @@ namespace SafeExamBrowser.Settings.Applications
public WhitelistApplication()
{
Arguments = new List();
+ Id = Guid.NewGuid();
}
}
}
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs
index 04a03b32..92226fff 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs
@@ -16,14 +16,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class ActionCenterApplicationButton : UserControl
{
- private ApplicationInfo info;
+ private IApplication application;
private IApplicationWindow window;
internal event EventHandler Clicked;
- public ActionCenterApplicationButton(ApplicationInfo info, IApplicationWindow window = null)
+ public ActionCenterApplicationButton(IApplication application, IApplicationWindow window = null)
{
- this.info = info;
+ this.application = application;
this.window = window;
InitializeComponent();
@@ -32,10 +32,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void InitializeApplicationInstanceButton()
{
- Icon.Content = IconResourceLoader.Load(info.Icon);
- Text.Text = window?.Title ?? info.Name;
+ Icon.Content = IconResourceLoader.Load(application.Icon);
+ Text.Text = window?.Title ?? application.Name;
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
- Button.ToolTip = window?.Title ?? info.Tooltip;
+ Button.ToolTip = window?.Title ?? application.Tooltip;
if (window != null)
{
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs
index d289ef42..7f8a4d23 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs
@@ -27,11 +27,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void InitializeApplicationControl()
{
- var button = new ActionCenterApplicationButton(application.Info);
+ var button = new ActionCenterApplicationButton(application);
application.WindowsChanged += Application_WindowsChanged;
button.Clicked += (o, args) => application.Start();
- ApplicationName.Text = application.Info.Name;
+ ApplicationName.Text = application.Name;
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Content = button;
}
@@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
foreach (var window in windows)
{
- var button = new ActionCenterApplicationButton(application.Info, window);
+ var button = new ActionCenterApplicationButton(application, window);
button.Clicked += (o, args) => window.Activate();
WindowPanel.Children.Add(button);
diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs
index 18434b5c..9cac7212 100644
--- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs
@@ -38,10 +38,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
application.WindowsChanged += Application_WindowsChanged;
Button.Click += Button_Click;
- Button.Content = IconResourceLoader.Load(application.Info.Icon);
+ Button.Content = IconResourceLoader.Load(application.Icon);
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
- Button.ToolTip = application.Info.Tooltip;
+ Button.ToolTip = application.Tooltip;
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
WindowPopup.Opened += (o, args) =>
diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs
index b60c51f8..b2c581f7 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs
@@ -16,14 +16,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{
public partial class ActionCenterApplicationButton : UserControl
{
- private ApplicationInfo info;
+ private IApplication application;
private IApplicationWindow window;
internal event EventHandler Clicked;
- public ActionCenterApplicationButton(ApplicationInfo info, IApplicationWindow window = null)
+ public ActionCenterApplicationButton(IApplication application, IApplicationWindow window = null)
{
- this.info = info;
+ this.application = application;
this.window = window;
InitializeComponent();
@@ -32,10 +32,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void InitializeApplicationInstanceButton()
{
- Icon.Content = IconResourceLoader.Load(info.Icon);
- Text.Text = window?.Title ?? info.Name;
+ Icon.Content = IconResourceLoader.Load(application.Icon);
+ Text.Text = window?.Title ?? application.Name;
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
- Button.ToolTip = window?.Title ?? info.Tooltip;
+ Button.ToolTip = window?.Title ?? application.Tooltip;
if (window != null)
{
diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs
index 3f8ad7aa..21f0d78a 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs
@@ -27,11 +27,11 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void InitializeApplicationControl()
{
- var button = new ActionCenterApplicationButton(application.Info);
+ var button = new ActionCenterApplicationButton(application);
application.WindowsChanged += Application_WindowsChanged;
button.Clicked += (o, args) => application.Start();
- ApplicationName.Text = application.Info.Name;
+ ApplicationName.Text = application.Name;
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Content = button;
}
@@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
foreach (var window in windows)
{
- var button = new ActionCenterApplicationButton(application.Info, window);
+ var button = new ActionCenterApplicationButton(application, window);
button.Clicked += (o, args) => window.Activate();
WindowPanel.Children.Add(button);
diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs
index fc98dcf0..df16c255 100644
--- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs
@@ -38,10 +38,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
application.WindowsChanged += Application_WindowsChanged;
Button.Click += Button_Click;
- Button.Content = IconResourceLoader.Load(application.Info.Icon);
+ Button.Content = IconResourceLoader.Load(application.Icon);
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
- Button.ToolTip = application.Info.Tooltip;
+ Button.ToolTip = application.Tooltip;
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
WindowPopup.Opened += (o, args) =>