SEBWIN-312: Implemented mechanism to detect start of whitelisted application instances.
This commit is contained in:
parent
f19f284d95
commit
df13e96dcd
24 changed files with 248 additions and 135 deletions
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The information about an application which can be accessed via the shell.
|
|
||||||
/// </summary>
|
|
||||||
public class ApplicationInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the application should be automatically started.
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoStart { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the application.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The tooltip for the application.
|
|
||||||
/// </summary>
|
|
||||||
public string Tooltip { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The resource providing the application icon.
|
|
||||||
/// </summary>
|
|
||||||
public IconResource Icon { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,10 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using SafeExamBrowser.Applications.Contracts.Events;
|
using SafeExamBrowser.Applications.Contracts.Events;
|
||||||
|
using SafeExamBrowser.Core.Contracts;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Applications.Contracts
|
namespace SafeExamBrowser.Applications.Contracts
|
||||||
{
|
{
|
||||||
|
@ -17,9 +19,29 @@ namespace SafeExamBrowser.Applications.Contracts
|
||||||
public interface IApplication
|
public interface IApplication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides information about the application.
|
/// Indicates whether the application should be automatically started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ApplicationInfo Info { get; }
|
bool AutoStart { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resource providing the application icon.
|
||||||
|
/// </summary>
|
||||||
|
IconResource Icon { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unique identifier of the application.
|
||||||
|
/// </summary>
|
||||||
|
Guid Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the application.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tooltip for the application.
|
||||||
|
/// </summary>
|
||||||
|
string Tooltip { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the windows of the application have changed.
|
/// Event fired when the windows of the application have changed.
|
||||||
|
|
|
@ -60,7 +60,6 @@
|
||||||
<Compile Include="FactoryResult.cs" />
|
<Compile Include="FactoryResult.cs" />
|
||||||
<Compile Include="IApplication.cs" />
|
<Compile Include="IApplication.cs" />
|
||||||
<Compile Include="IApplicationFactory.cs" />
|
<Compile Include="IApplicationFactory.cs" />
|
||||||
<Compile Include="ApplicationInfo.cs" />
|
|
||||||
<Compile Include="IApplicationWindow.cs" />
|
<Compile Include="IApplicationWindow.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -11,8 +11,8 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using SafeExamBrowser.Applications.Contracts;
|
using SafeExamBrowser.Applications.Contracts;
|
||||||
using SafeExamBrowser.Core.Contracts;
|
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
using SafeExamBrowser.Settings.Applications;
|
using SafeExamBrowser.Settings.Applications;
|
||||||
using SafeExamBrowser.WindowsApi.Contracts;
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
|
||||||
|
@ -20,12 +20,18 @@ namespace SafeExamBrowser.Applications
|
||||||
{
|
{
|
||||||
public class ApplicationFactory : IApplicationFactory
|
public class ApplicationFactory : IApplicationFactory
|
||||||
{
|
{
|
||||||
|
private IApplicationMonitor applicationMonitor;
|
||||||
private IModuleLogger logger;
|
private IModuleLogger logger;
|
||||||
private INativeMethods nativeMethods;
|
private INativeMethods nativeMethods;
|
||||||
private IProcessFactory processFactory;
|
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.logger = logger;
|
||||||
this.nativeMethods = nativeMethods;
|
this.nativeMethods = nativeMethods;
|
||||||
this.processFactory = processFactory;
|
this.processFactory = processFactory;
|
||||||
|
@ -65,9 +71,8 @@ namespace SafeExamBrowser.Applications
|
||||||
|
|
||||||
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
|
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
|
||||||
{
|
{
|
||||||
var icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) };
|
var applicationLogger = logger.CloneFor(settings.DisplayName);
|
||||||
var info = new ApplicationInfo { AutoStart = settings.AutoStart, Icon = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName };
|
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings);
|
||||||
var application = new ExternalApplication(executablePath, info, logger.CloneFor(settings.DisplayName), nativeMethods, processFactory);
|
|
||||||
|
|
||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,48 +11,68 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SafeExamBrowser.Applications.Contracts;
|
using SafeExamBrowser.Applications.Contracts;
|
||||||
using SafeExamBrowser.Applications.Contracts.Events;
|
using SafeExamBrowser.Applications.Contracts.Events;
|
||||||
|
using SafeExamBrowser.Core.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
|
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||||
|
using SafeExamBrowser.Settings.Applications;
|
||||||
using SafeExamBrowser.WindowsApi.Contracts;
|
using SafeExamBrowser.WindowsApi.Contracts;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Applications
|
namespace SafeExamBrowser.Applications
|
||||||
{
|
{
|
||||||
internal class ExternalApplication : IApplication
|
internal class ExternalApplication : IApplication
|
||||||
{
|
{
|
||||||
private int instanceIdCounter = default(int);
|
private readonly object @lock = new object();
|
||||||
|
|
||||||
|
private IApplicationMonitor applicationMonitor;
|
||||||
private string executablePath;
|
private string executablePath;
|
||||||
private IModuleLogger logger;
|
private IModuleLogger logger;
|
||||||
private INativeMethods nativeMethods;
|
private INativeMethods nativeMethods;
|
||||||
private IList<ExternalApplicationInstance> instances;
|
private IList<ExternalApplicationInstance> instances;
|
||||||
private IProcessFactory processFactory;
|
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 event WindowsChangedEventHandler WindowsChanged;
|
||||||
|
|
||||||
public ApplicationInfo Info { get; }
|
|
||||||
|
|
||||||
internal ExternalApplication(
|
internal ExternalApplication(
|
||||||
|
IApplicationMonitor applicationMonitor,
|
||||||
string executablePath,
|
string executablePath,
|
||||||
ApplicationInfo info,
|
|
||||||
IModuleLogger logger,
|
IModuleLogger logger,
|
||||||
INativeMethods nativeMethods,
|
INativeMethods nativeMethods,
|
||||||
IProcessFactory processFactory)
|
IProcessFactory processFactory,
|
||||||
|
WhitelistApplication settings)
|
||||||
{
|
{
|
||||||
|
this.applicationMonitor = applicationMonitor;
|
||||||
this.executablePath = executablePath;
|
this.executablePath = executablePath;
|
||||||
this.Info = info;
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.nativeMethods = nativeMethods;
|
this.nativeMethods = nativeMethods;
|
||||||
this.instances = new List<ExternalApplicationInstance>();
|
this.instances = new List<ExternalApplicationInstance>();
|
||||||
this.processFactory = processFactory;
|
this.processFactory = processFactory;
|
||||||
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IApplicationWindow> GetWindows()
|
public IEnumerable<IApplicationWindow> GetWindows()
|
||||||
{
|
{
|
||||||
return instances.SelectMany(i => i.GetWindows());
|
lock (@lock)
|
||||||
|
{
|
||||||
|
return instances.SelectMany(i => i.GetWindows());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
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()
|
public void Start()
|
||||||
|
@ -60,16 +80,7 @@ namespace SafeExamBrowser.Applications
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.Info("Starting application...");
|
logger.Info("Starting application...");
|
||||||
|
InitializeInstance(processFactory.StartNew(executablePath));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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)
|
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();
|
WindowsChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Terminate()
|
private void InitializeInstance(IProcess process)
|
||||||
{
|
{
|
||||||
if (instances.Any())
|
var instanceLogger = logger.CloneFor($"{Name} ({process.Id})");
|
||||||
{
|
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process);
|
||||||
logger.Info("Terminating application...");
|
|
||||||
|
|
||||||
foreach (var instance in instances)
|
instance.Terminated += Instance_Terminated;
|
||||||
{
|
instance.WindowsChanged += () => WindowsChanged?.Invoke();
|
||||||
instance.Terminate();
|
instance.Initialize();
|
||||||
}
|
|
||||||
|
lock (@lock)
|
||||||
|
{
|
||||||
|
instances.Add(instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,15 +30,14 @@ namespace SafeExamBrowser.Applications
|
||||||
private Timer timer;
|
private Timer timer;
|
||||||
private IList<ExternalApplicationWindow> windows;
|
private IList<ExternalApplicationWindow> windows;
|
||||||
|
|
||||||
internal int Id { get; }
|
internal int Id { get; private set; }
|
||||||
|
|
||||||
internal event InstanceTerminatedEventHandler Terminated;
|
internal event InstanceTerminatedEventHandler Terminated;
|
||||||
internal event WindowsChangedEventHandler WindowsChanged;
|
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.icon = icon;
|
||||||
this.Id = id;
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.nativeMethods = nativeMethods;
|
this.nativeMethods = nativeMethods;
|
||||||
this.process = process;
|
this.process = process;
|
||||||
|
@ -55,6 +54,7 @@ namespace SafeExamBrowser.Applications
|
||||||
|
|
||||||
internal void Initialize()
|
internal void Initialize()
|
||||||
{
|
{
|
||||||
|
Id = process.Id;
|
||||||
InitializeEvents();
|
InitializeEvents();
|
||||||
logger.Info("Initialized application instance.");
|
logger.Info("Initialized application instance.");
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ namespace SafeExamBrowser.Applications
|
||||||
lock (@lock)
|
lock (@lock)
|
||||||
{
|
{
|
||||||
var closedWindows = windows.Where(w => openWindows.All(ow => ow != w.Handle)).ToList();
|
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)
|
foreach (var window in closedWindows)
|
||||||
{
|
{
|
||||||
|
@ -128,7 +128,6 @@ namespace SafeExamBrowser.Applications
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
{
|
{
|
||||||
logger.Error("WINDOWS CHANGED!");
|
|
||||||
WindowsChanged?.Invoke();
|
WindowsChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,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.Settings\SafeExamBrowser.Settings.csproj">
|
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||||
<Name>SafeExamBrowser.Settings</Name>
|
<Name>SafeExamBrowser.Settings</Name>
|
||||||
|
|
|
@ -17,6 +17,7 @@ using SafeExamBrowser.Browser.Contracts;
|
||||||
using SafeExamBrowser.Browser.Contracts.Events;
|
using SafeExamBrowser.Browser.Contracts.Events;
|
||||||
using SafeExamBrowser.Browser.Events;
|
using SafeExamBrowser.Browser.Events;
|
||||||
using SafeExamBrowser.Configuration.Contracts;
|
using SafeExamBrowser.Configuration.Contracts;
|
||||||
|
using SafeExamBrowser.Core.Contracts;
|
||||||
using SafeExamBrowser.I18n.Contracts;
|
using SafeExamBrowser.I18n.Contracts;
|
||||||
using SafeExamBrowser.Logging.Contracts;
|
using SafeExamBrowser.Logging.Contracts;
|
||||||
using SafeExamBrowser.Settings.Logging;
|
using SafeExamBrowser.Settings.Logging;
|
||||||
|
@ -38,7 +39,11 @@ namespace SafeExamBrowser.Browser
|
||||||
private IText text;
|
private IText text;
|
||||||
private IUserInterfaceFactory uiFactory;
|
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 DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||||
public event WindowsChangedEventHandler WindowsChanged;
|
public event WindowsChangedEventHandler WindowsChanged;
|
||||||
|
@ -102,12 +107,11 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
private void InitializeApplicationInfo()
|
private void InitializeApplicationInfo()
|
||||||
{
|
{
|
||||||
Info = new ApplicationInfo
|
AutoStart = true;
|
||||||
{
|
Icon = new BrowserIconResource();
|
||||||
Icon = new BrowserIconResource(),
|
Id = Guid.NewGuid();
|
||||||
Name = "Safe Exam Browser",
|
Name = text.Get(TextKey.Browser_Name);
|
||||||
Tooltip = text.Get(TextKey.Browser_Tooltip)
|
Tooltip = text.Get(TextKey.Browser_Tooltip);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateNewInstance(string url = null)
|
private void CreateNewInstance(string url = null)
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
private AppConfig appConfig;
|
private AppConfig appConfig;
|
||||||
private Mock<IActionCenter> actionCenter;
|
private Mock<IActionCenter> actionCenter;
|
||||||
private Mock<IApplicationMonitor> applicationMonitor;
|
private Mock<IApplicationMonitor> applicationMonitor;
|
||||||
private Mock<IBrowserApplication> browserController;
|
private Mock<IBrowserApplication> browser;
|
||||||
private Mock<IClientHost> clientHost;
|
private Mock<IClientHost> clientHost;
|
||||||
private ClientContext context;
|
private ClientContext context;
|
||||||
private Mock<IDisplayMonitor> displayMonitor;
|
private Mock<IDisplayMonitor> displayMonitor;
|
||||||
|
@ -66,7 +66,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
appConfig = new AppConfig();
|
appConfig = new AppConfig();
|
||||||
actionCenter = new Mock<IActionCenter>();
|
actionCenter = new Mock<IActionCenter>();
|
||||||
applicationMonitor = new Mock<IApplicationMonitor>();
|
applicationMonitor = new Mock<IApplicationMonitor>();
|
||||||
browserController = new Mock<IBrowserApplication>();
|
browser = new Mock<IBrowserApplication>();
|
||||||
clientHost = new Mock<IClientHost>();
|
clientHost = new Mock<IClientHost>();
|
||||||
context = new ClientContext();
|
context = new ClientContext();
|
||||||
displayMonitor = new Mock<IDisplayMonitor>();
|
displayMonitor = new Mock<IDisplayMonitor>();
|
||||||
|
@ -106,7 +106,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
|
|
||||||
context.AppConfig = appConfig;
|
context.AppConfig = appConfig;
|
||||||
context.Activators.Add(terminationActivator.Object);
|
context.Activators.Add(terminationActivator.Object);
|
||||||
context.Browser = browserController.Object;
|
context.Browser = browser.Object;
|
||||||
context.ClientHost = clientHost.Object;
|
context.ClientHost = clientHost.Object;
|
||||||
context.SessionId = sessionId;
|
context.SessionId = sessionId;
|
||||||
context.Settings = settings;
|
context.Settings = settings;
|
||||||
|
@ -300,7 +300,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
It.IsAny<IWindow>())).Returns(MessageBoxResult.Ok);
|
It.IsAny<IWindow>())).Returns(MessageBoxResult.Ok);
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", new DownloadEventArgs());
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", new DownloadEventArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -321,7 +321,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(true));
|
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(true));
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||||
args.Callback(true, downloadPath);
|
args.Callback(true, downloadPath);
|
||||||
|
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath)), Times.Once);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath)), Times.Once);
|
||||||
|
@ -348,7 +348,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(true));
|
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(true));
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||||
args.Callback(false, downloadPath);
|
args.Callback(false, downloadPath);
|
||||||
|
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>()), Times.Never);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>()), Times.Never);
|
||||||
|
@ -372,7 +372,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(false));
|
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(false));
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||||
args.Callback(true, downloadPath);
|
args.Callback(true, downloadPath);
|
||||||
|
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>()), Times.Once);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>()), Times.Once);
|
||||||
|
@ -632,9 +632,9 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var application2 = new Mock<IApplication>();
|
var application2 = new Mock<IApplication>();
|
||||||
var application3 = new Mock<IApplication>();
|
var application3 = new Mock<IApplication>();
|
||||||
|
|
||||||
application1.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = true });
|
application1.SetupGet(a => a.AutoStart).Returns(true);
|
||||||
application2.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = false });
|
application2.SetupGet(a => a.AutoStart).Returns(false);
|
||||||
application3.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = true });
|
application3.SetupGet(a => a.AutoStart).Returns(true);
|
||||||
context.Applications.Add(application1.Object);
|
context.Applications.Add(application1.Object);
|
||||||
context.Applications.Add(application2.Object);
|
context.Applications.Add(application2.Object);
|
||||||
context.Applications.Add(application3.Object);
|
context.Applications.Add(application3.Object);
|
||||||
|
@ -647,6 +647,23 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
application3.Verify(a => a.Start(), Times.Once);
|
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]
|
[TestMethod]
|
||||||
public void TerminationActivator_MustCorrectlyInitiateShutdown()
|
public void TerminationActivator_MustCorrectlyInitiateShutdown()
|
||||||
{
|
{
|
||||||
|
|
|
@ -241,14 +241,17 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
private void AutoStartApplications()
|
private void AutoStartApplications()
|
||||||
{
|
{
|
||||||
logger.Info("Starting browser application...");
|
if (Browser.AutoStart)
|
||||||
Browser.Start();
|
{
|
||||||
|
logger.Info("Auto-starting browser...");
|
||||||
|
Browser.Start();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var application in context.Applications)
|
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();
|
application.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,8 +98,8 @@ namespace SafeExamBrowser.Client
|
||||||
taskView = BuildTaskView();
|
taskView = BuildTaskView();
|
||||||
|
|
||||||
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
|
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, processFactory);
|
||||||
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
|
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory);
|
||||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||||
var hashAlgorithm = new HashAlgorithm();
|
var hashAlgorithm = new HashAlgorithm();
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace SafeExamBrowser.I18n.Contracts
|
||||||
Browser_BlockedPageButton,
|
Browser_BlockedPageButton,
|
||||||
Browser_BlockedPageMessage,
|
Browser_BlockedPageMessage,
|
||||||
Browser_BlockedPageTitle,
|
Browser_BlockedPageTitle,
|
||||||
|
Browser_Name,
|
||||||
Browser_Tooltip,
|
Browser_Tooltip,
|
||||||
BrowserWindow_DeveloperConsoleMenuItem,
|
BrowserWindow_DeveloperConsoleMenuItem,
|
||||||
BrowserWindow_ZoomMenuItem,
|
BrowserWindow_ZoomMenuItem,
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<Entry key="Browser_BlockedPageTitle">
|
<Entry key="Browser_BlockedPageTitle">
|
||||||
Page Blocked
|
Page Blocked
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="Browser_Name">
|
||||||
|
Browser
|
||||||
|
</Entry>
|
||||||
<Entry key="Browser_Tooltip">
|
<Entry key="Browser_Tooltip">
|
||||||
Browser Application
|
Browser Application
|
||||||
</Entry>
|
</Entry>
|
||||||
|
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a new instance of a whitelisted application has been started.
|
||||||
|
/// </summary>
|
||||||
|
public delegate void InstanceStartedEventHandler(Guid applicationId, IProcess process);
|
||||||
|
}
|
|
@ -21,6 +21,11 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ExplorerStartedEventHandler ExplorerStarted;
|
event ExplorerStartedEventHandler ExplorerStarted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when a new instance of a whitelisted application has been started.
|
||||||
|
/// </summary>
|
||||||
|
event InstanceStartedEventHandler InstanceStarted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the automatic termination of a blacklisted application failed.
|
/// Event fired when the automatic termination of a blacklisted application failed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Applications\Events\InstanceStartedEventHandler.cs" />
|
||||||
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
|
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
|
||||||
<Compile Include="Applications\RunningApplication.cs" />
|
<Compile Include="Applications\RunningApplication.cs" />
|
||||||
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
|
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
private Window activeWindow;
|
private Window activeWindow;
|
||||||
|
|
||||||
public event ExplorerStartedEventHandler ExplorerStarted;
|
public event ExplorerStartedEventHandler ExplorerStarted;
|
||||||
|
public event InstanceStartedEventHandler InstanceStarted;
|
||||||
public event TerminationFailedEventHandler TerminationFailed;
|
public event TerminationFailedEventHandler TerminationFailed;
|
||||||
|
|
||||||
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
|
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.");
|
logger.Debug($"Process {process} has been started.");
|
||||||
processes.Add(process);
|
processes.Add(process);
|
||||||
|
|
||||||
if (process.Name == "explorer")
|
if (process.Name == "explorer.exe")
|
||||||
{
|
{
|
||||||
HandleExplorerStart(process);
|
HandleExplorerStart(process);
|
||||||
}
|
}
|
||||||
|
@ -142,6 +143,10 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
{
|
{
|
||||||
AddFailed(process, failed);
|
AddFailed(process, failed);
|
||||||
}
|
}
|
||||||
|
else if (IsWhitelisted(process, out var applicationId))
|
||||||
|
{
|
||||||
|
HandleInstanceStart(applicationId.Value, process);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var process in terminated)
|
foreach (var process in terminated)
|
||||||
|
@ -238,6 +243,12 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
Task.Run(() => ExplorerStarted?.Invoke());
|
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()
|
private void InitializeProcesses()
|
||||||
{
|
{
|
||||||
processes = processFactory.GetAllRunning();
|
processes = processFactory.GetAllRunning();
|
||||||
|
@ -329,24 +340,33 @@ namespace SafeExamBrowser.Monitoring.Applications
|
||||||
|
|
||||||
if (processFactory.TryGetById(processId, out var process))
|
if (processFactory.TryGetById(processId, out var process))
|
||||||
{
|
{
|
||||||
if (BelongsToSafeExamBrowser(process))
|
if (BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var application in whitelist)
|
logger.Warn($"Window {window} belongs to not whitelisted process '{process.Name}'!");
|
||||||
{
|
|
||||||
if (BelongsToApplication(process, application))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Warn($"Window {window} belongs to not allowed process '{process.Name}'!");
|
|
||||||
}
|
}
|
||||||
else
|
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;
|
return false;
|
||||||
|
|
|
@ -62,6 +62,11 @@ namespace SafeExamBrowser.Settings.Applications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ExecutablePath { get; set; }
|
public string ExecutablePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unique identifier to be used to identify the application during runtime.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The original file name of the main executable of the application, if available.
|
/// The original file name of the main executable of the application, if available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -80,6 +85,7 @@ namespace SafeExamBrowser.Settings.Applications
|
||||||
public WhitelistApplication()
|
public WhitelistApplication()
|
||||||
{
|
{
|
||||||
Arguments = new List<string>();
|
Arguments = new List<string>();
|
||||||
|
Id = Guid.NewGuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
||||||
{
|
{
|
||||||
public partial class ActionCenterApplicationButton : UserControl
|
public partial class ActionCenterApplicationButton : UserControl
|
||||||
{
|
{
|
||||||
private ApplicationInfo info;
|
private IApplication application;
|
||||||
private IApplicationWindow window;
|
private IApplicationWindow window;
|
||||||
|
|
||||||
internal event EventHandler Clicked;
|
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;
|
this.window = window;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -32,10 +32,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
||||||
|
|
||||||
private void InitializeApplicationInstanceButton()
|
private void InitializeApplicationInstanceButton()
|
||||||
{
|
{
|
||||||
Icon.Content = IconResourceLoader.Load(info.Icon);
|
Icon.Content = IconResourceLoader.Load(application.Icon);
|
||||||
Text.Text = window?.Title ?? info.Name;
|
Text.Text = window?.Title ?? application.Name;
|
||||||
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
||||||
Button.ToolTip = window?.Title ?? info.Tooltip;
|
Button.ToolTip = window?.Title ?? application.Tooltip;
|
||||||
|
|
||||||
if (window != null)
|
if (window != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,11 +27,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
||||||
|
|
||||||
private void InitializeApplicationControl()
|
private void InitializeApplicationControl()
|
||||||
{
|
{
|
||||||
var button = new ActionCenterApplicationButton(application.Info);
|
var button = new ActionCenterApplicationButton(application);
|
||||||
|
|
||||||
application.WindowsChanged += Application_WindowsChanged;
|
application.WindowsChanged += Application_WindowsChanged;
|
||||||
button.Clicked += (o, args) => application.Start();
|
button.Clicked += (o, args) => application.Start();
|
||||||
ApplicationName.Text = application.Info.Name;
|
ApplicationName.Text = application.Name;
|
||||||
ApplicationName.Visibility = Visibility.Collapsed;
|
ApplicationName.Visibility = Visibility.Collapsed;
|
||||||
ApplicationButton.Content = button;
|
ApplicationButton.Content = button;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
||||||
|
|
||||||
foreach (var window in windows)
|
foreach (var window in windows)
|
||||||
{
|
{
|
||||||
var button = new ActionCenterApplicationButton(application.Info, window);
|
var button = new ActionCenterApplicationButton(application, window);
|
||||||
|
|
||||||
button.Clicked += (o, args) => window.Activate();
|
button.Clicked += (o, args) => window.Activate();
|
||||||
WindowPanel.Children.Add(button);
|
WindowPanel.Children.Add(button);
|
||||||
|
|
|
@ -38,10 +38,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
||||||
application.WindowsChanged += Application_WindowsChanged;
|
application.WindowsChanged += Application_WindowsChanged;
|
||||||
|
|
||||||
Button.Click += Button_Click;
|
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.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
|
||||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
|
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.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
|
||||||
|
|
||||||
WindowPopup.Opened += (o, args) =>
|
WindowPopup.Opened += (o, args) =>
|
||||||
|
|
|
@ -16,14 +16,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
||||||
{
|
{
|
||||||
public partial class ActionCenterApplicationButton : UserControl
|
public partial class ActionCenterApplicationButton : UserControl
|
||||||
{
|
{
|
||||||
private ApplicationInfo info;
|
private IApplication application;
|
||||||
private IApplicationWindow window;
|
private IApplicationWindow window;
|
||||||
|
|
||||||
internal event EventHandler Clicked;
|
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;
|
this.window = window;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -32,10 +32,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
||||||
|
|
||||||
private void InitializeApplicationInstanceButton()
|
private void InitializeApplicationInstanceButton()
|
||||||
{
|
{
|
||||||
Icon.Content = IconResourceLoader.Load(info.Icon);
|
Icon.Content = IconResourceLoader.Load(application.Icon);
|
||||||
Text.Text = window?.Title ?? info.Name;
|
Text.Text = window?.Title ?? application.Name;
|
||||||
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
||||||
Button.ToolTip = window?.Title ?? info.Tooltip;
|
Button.ToolTip = window?.Title ?? application.Tooltip;
|
||||||
|
|
||||||
if (window != null)
|
if (window != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,11 +27,11 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
||||||
|
|
||||||
private void InitializeApplicationControl()
|
private void InitializeApplicationControl()
|
||||||
{
|
{
|
||||||
var button = new ActionCenterApplicationButton(application.Info);
|
var button = new ActionCenterApplicationButton(application);
|
||||||
|
|
||||||
application.WindowsChanged += Application_WindowsChanged;
|
application.WindowsChanged += Application_WindowsChanged;
|
||||||
button.Clicked += (o, args) => application.Start();
|
button.Clicked += (o, args) => application.Start();
|
||||||
ApplicationName.Text = application.Info.Name;
|
ApplicationName.Text = application.Name;
|
||||||
ApplicationName.Visibility = Visibility.Collapsed;
|
ApplicationName.Visibility = Visibility.Collapsed;
|
||||||
ApplicationButton.Content = button;
|
ApplicationButton.Content = button;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
||||||
|
|
||||||
foreach (var window in windows)
|
foreach (var window in windows)
|
||||||
{
|
{
|
||||||
var button = new ActionCenterApplicationButton(application.Info, window);
|
var button = new ActionCenterApplicationButton(application, window);
|
||||||
|
|
||||||
button.Clicked += (o, args) => window.Activate();
|
button.Clicked += (o, args) => window.Activate();
|
||||||
WindowPanel.Children.Add(button);
|
WindowPanel.Children.Add(button);
|
||||||
|
|
|
@ -38,10 +38,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
||||||
application.WindowsChanged += Application_WindowsChanged;
|
application.WindowsChanged += Application_WindowsChanged;
|
||||||
|
|
||||||
Button.Click += Button_Click;
|
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.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
|
||||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
|
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.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
|
||||||
|
|
||||||
WindowPopup.Opened += (o, args) =>
|
WindowPopup.Opened += (o, args) =>
|
||||||
|
|
Loading…
Reference in a new issue