SEBWIN-312: Implemented mechanism to detect start of whitelisted application instances.

This commit is contained in:
dbuechel 2019-12-02 15:48:06 +01:00
parent f19f284d95
commit df13e96dcd
24 changed files with 248 additions and 135 deletions

View file

@ -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; }
}
}

View file

@ -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
{
/// <summary>
/// Provides information about the application.
/// Indicates whether the application should be automatically started.
/// </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>
/// Event fired when the windows of the application have changed.

View file

@ -60,7 +60,6 @@
<Compile Include="FactoryResult.cs" />
<Compile Include="IApplication.cs" />
<Compile Include="IApplicationFactory.cs" />
<Compile Include="ApplicationInfo.cs" />
<Compile Include="IApplicationWindow.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View file

@ -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;
}

View file

@ -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<ExternalApplicationInstance> 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<ExternalApplicationInstance>();
this.processFactory = processFactory;
this.settings = settings;
}
public IEnumerable<IApplicationWindow> 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);
}
}
}

View file

@ -30,15 +30,14 @@ namespace SafeExamBrowser.Applications
private Timer timer;
private IList<ExternalApplicationWindow> 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();
}

View file

@ -74,6 +74,10 @@
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</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">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>

View file

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

View file

@ -40,7 +40,7 @@ namespace SafeExamBrowser.Client.UnitTests
private AppConfig appConfig;
private Mock<IActionCenter> actionCenter;
private Mock<IApplicationMonitor> applicationMonitor;
private Mock<IBrowserApplication> browserController;
private Mock<IBrowserApplication> browser;
private Mock<IClientHost> clientHost;
private ClientContext context;
private Mock<IDisplayMonitor> displayMonitor;
@ -66,7 +66,7 @@ namespace SafeExamBrowser.Client.UnitTests
appConfig = new AppConfig();
actionCenter = new Mock<IActionCenter>();
applicationMonitor = new Mock<IApplicationMonitor>();
browserController = new Mock<IBrowserApplication>();
browser = new Mock<IBrowserApplication>();
clientHost = new Mock<IClientHost>();
context = new ClientContext();
displayMonitor = new Mock<IDisplayMonitor>();
@ -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<IWindow>())).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<string>(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<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));
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<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));
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<string>()), Times.Once);
@ -632,9 +632,9 @@ namespace SafeExamBrowser.Client.UnitTests
var application2 = new Mock<IApplication>();
var application3 = new Mock<IApplication>();
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()
{

View file

@ -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();
}
}

View file

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

View file

@ -18,6 +18,7 @@ namespace SafeExamBrowser.I18n.Contracts
Browser_BlockedPageButton,
Browser_BlockedPageMessage,
Browser_BlockedPageTitle,
Browser_Name,
Browser_Tooltip,
BrowserWindow_DeveloperConsoleMenuItem,
BrowserWindow_ZoomMenuItem,

View file

@ -12,6 +12,9 @@
<Entry key="Browser_BlockedPageTitle">
Page Blocked
</Entry>
<Entry key="Browser_Name">
Browser
</Entry>
<Entry key="Browser_Tooltip">
Browser Application
</Entry>

View file

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

View file

@ -21,6 +21,11 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
/// </summary>
event ExplorerStartedEventHandler ExplorerStarted;
/// <summary>
/// Event fired when a new instance of a whitelisted application has been started.
/// </summary>
event InstanceStartedEventHandler InstanceStarted;
/// <summary>
/// Event fired when the automatic termination of a blacklisted application failed.
/// </summary>

View file

@ -54,6 +54,7 @@
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Applications\Events\InstanceStartedEventHandler.cs" />
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
<Compile Include="Applications\RunningApplication.cs" />
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />

View file

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

View file

@ -62,6 +62,11 @@ namespace SafeExamBrowser.Settings.Applications
/// </summary>
public string ExecutablePath { get; set; }
/// <summary>
/// Unique identifier to be used to identify the application during runtime.
/// </summary>
public Guid Id { get; }
/// <summary>
/// The original file name of the main executable of the application, if available.
/// </summary>
@ -80,6 +85,7 @@ namespace SafeExamBrowser.Settings.Applications
public WhitelistApplication()
{
Arguments = new List<string>();
Id = Guid.NewGuid();
}
}
}

View file

@ -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)
{

View file

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

View file

@ -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) =>

View file

@ -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)
{

View file

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

View file

@ -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) =>