SEBWIN-312: Implemented loading of whitelisted applications into shell.

This commit is contained in:
dbuechel 2019-11-06 08:45:37 +01:00
parent 7e76b029a6
commit c21005b934
58 changed files with 300 additions and 211 deletions

View file

@ -13,21 +13,21 @@ namespace SafeExamBrowser.Applications.Contracts
/// <summary> /// <summary>
/// The information about an application which can be accessed via the shell. /// The information about an application which can be accessed via the shell.
/// </summary> /// </summary>
public interface IApplicationInfo public class ApplicationInfo
{ {
/// <summary> /// <summary>
/// The name of the application. /// The name of the application.
/// </summary> /// </summary>
string Name { get; } public string Name { get; set; }
/// <summary> /// <summary>
/// The tooltip for the application. /// The tooltip for the application.
/// </summary> /// </summary>
string Tooltip { get; } public string Tooltip { get; set; }
/// <summary> /// <summary>
/// The resource providing the application icon. /// The resource providing the application icon.
/// </summary> /// </summary>
IIconResource IconResource { get; } public IconResource IconResource { get; set; }
} }
} }

View file

@ -13,5 +13,5 @@ namespace SafeExamBrowser.Applications.Contracts.Events
/// <summary> /// <summary>
/// Event handler used to indicate that the icon of an <see cref="IApplicationInstance"/> has changed. /// Event handler used to indicate that the icon of an <see cref="IApplicationInstance"/> has changed.
/// </summary> /// </summary>
public delegate void IconChangedEventHandler(IIconResource icon); public delegate void IconChangedEventHandler(IconResource icon);
} }

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Applications.Contracts
/// <summary> /// <summary>
/// Provides information about the application. /// Provides information about the application.
/// </summary> /// </summary>
IApplicationInfo Info { get; } ApplicationInfo Info { get; }
/// <summary> /// <summary>
/// Fired when a new <see cref="IApplicationInstance"/> has started. /// Fired when a new <see cref="IApplicationInstance"/> has started.

View file

@ -60,7 +60,7 @@
<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="IApplicationInfo.cs" /> <Compile Include="ApplicationInfo.cs" />
<Compile Include="IApplicationInstance.cs" /> <Compile Include="IApplicationInstance.cs" />
<Compile Include="InstanceIdentifier.cs" /> <Compile Include="InstanceIdentifier.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -11,6 +11,7 @@ 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.Settings.Applications; using SafeExamBrowser.Settings.Applications;
@ -27,33 +28,46 @@ namespace SafeExamBrowser.Applications
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication application) public FactoryResult TryCreate(WhitelistApplication settings, out IApplication application)
{ {
var name = $"'{settings.DisplayName}' ({ settings.ExecutableName})";
application = default(IApplication); application = default(IApplication);
try try
{ {
var success = TryFindMainExecutable(settings, out var mainExecutable); var success = TryFindApplication(settings, out var executablePath);
if (success) if (success)
{ {
application = new ExternalApplication(); application = BuildApplication(executablePath, settings);
logger.Debug($"Successfully initialized application '{settings.DisplayName}' ({settings.ExecutableName})."); application.Initialize();
logger.Debug($"Successfully initialized application {name}.");
return FactoryResult.Success; return FactoryResult.Success;
} }
logger.Error($"Could not find application '{settings.DisplayName}' ({settings.ExecutableName})!"); logger.Error($"Could not find application {name}!");
return FactoryResult.NotFound; return FactoryResult.NotFound;
} }
catch (Exception e) catch (Exception e)
{ {
logger.Error($"Unexpected error while trying to create application '{settings.DisplayName}' ({settings.ExecutableName})!", e); logger.Error($"Unexpected error while trying to initialize application {name}!", e);
} }
return FactoryResult.Error; return FactoryResult.Error;
} }
private bool TryFindMainExecutable(WhitelistApplication settings, out string mainExecutable) private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
{
var icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) };
var info = new ApplicationInfo { IconResource = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName };
var application = new ExternalApplication(executablePath, info);
return application;
}
private bool TryFindApplication(WhitelistApplication settings, out string mainExecutable)
{ {
var paths = new List<string[]>(); var paths = new List<string[]>();
var registryPath = QueryPathFromRegistry(settings); var registryPath = QueryPathFromRegistry(settings);

View file

@ -6,7 +6,6 @@
* 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 SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events; using SafeExamBrowser.Applications.Contracts.Events;
@ -14,10 +13,18 @@ namespace SafeExamBrowser.Applications
{ {
internal class ExternalApplication : IApplication internal class ExternalApplication : IApplication
{ {
public IApplicationInfo Info => throw new NotImplementedException(); private string executablePath;
public ApplicationInfo Info { get; }
public event InstanceStartedEventHandler InstanceStarted; public event InstanceStartedEventHandler InstanceStarted;
internal ExternalApplication(string executablePath, ApplicationInfo info)
{
this.executablePath = executablePath;
this.Info = info;
}
public void Initialize() public void Initialize()
{ {

View file

@ -62,6 +62,10 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project> <Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name> <Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
<Name>SafeExamBrowser.Core.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project> <Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name> <Name>SafeExamBrowser.Logging.Contracts</Name>

View file

@ -38,7 +38,7 @@ namespace SafeExamBrowser.Browser
private IText text; private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
public IApplicationInfo Info { get; private set; } public ApplicationInfo Info { get; private set; }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested; public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event InstanceStartedEventHandler InstanceStarted; public event InstanceStartedEventHandler InstanceStarted;
@ -65,7 +65,7 @@ namespace SafeExamBrowser.Browser
var cefSettings = InitializeCefSettings(); var cefSettings = InitializeCefSettings();
var success = Cef.Initialize(cefSettings, true, default(IApp)); var success = Cef.Initialize(cefSettings, true, default(IApp));
Info = new BrowserApplicationInfo(); Info = BuildApplicationInfo();
if (success) if (success)
{ {
@ -95,6 +95,16 @@ namespace SafeExamBrowser.Browser
logger.Info("Terminated browser."); logger.Info("Terminated browser.");
} }
private ApplicationInfo BuildApplicationInfo()
{
return new ApplicationInfo
{
IconResource = new BrowserIconResource(),
Name = "Safe Exam Browser",
Tooltip = text.Get(TextKey.Browser_Tooltip)
};
}
private void CreateNewInstance(string url = null) private void CreateNewInstance(string url = null)
{ {
var id = new BrowserInstanceIdentifier(++instanceIdCounter); var id = new BrowserInstanceIdentifier(++instanceIdCounter);

View file

@ -1,20 +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.Applications.Contracts;
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Browser
{
public class BrowserApplicationInfo : IApplicationInfo
{
public string Name => "Safe Exam Browser";
public string Tooltip => Name;
public IIconResource IconResource { get; } = new BrowserIconResource();
}
}

View file

@ -11,14 +11,11 @@ using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Browser namespace SafeExamBrowser.Browser
{ {
public class BrowserIconResource : IIconResource public class BrowserIconResource : IconResource
{ {
public Uri Uri { get; private set; }
public bool IsBitmapResource => true;
public bool IsXamlResource => false;
public BrowserIconResource(string uri = null) public BrowserIconResource(string uri = null)
{ {
Type = IconResourceType.Bitmap;
Uri = new Uri(uri ?? "pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/SafeExamBrowser.ico"); Uri = new Uri(uri ?? "pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/SafeExamBrowser.ico");
} }
} }

View file

@ -66,7 +66,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="BrowserApplication.cs" /> <Compile Include="BrowserApplication.cs" />
<Compile Include="BrowserApplicationInfo.cs" />
<Compile Include="BrowserApplicationInstance.cs" /> <Compile Include="BrowserApplicationInstance.cs" />
<Compile Include="BrowserInstanceIdentifier.cs" /> <Compile Include="BrowserInstanceIdentifier.cs" />
<Compile Include="Events\FaviconChangedEventHandler.cs" /> <Compile Include="Events\FaviconChangedEventHandler.cs" />

View file

@ -23,6 +23,6 @@ namespace SafeExamBrowser.Client.Contracts
/// <summary> /// <summary>
/// The resource providing the notification icon. /// The resource providing the notification icon.
/// </summary> /// </summary>
IIconResource IconResource { get; } IconResource IconResource { get; }
} }
} }

View file

@ -49,8 +49,8 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
sut.Perform(); sut.Perform();
browser.Verify(c => c.Initialize(), Times.Once); browser.Verify(c => c.Initialize(), Times.Once);
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>()), Times.Once); actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Once);
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>()), Times.Once); taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Once);
} }
[TestMethod] [TestMethod]

View file

@ -198,7 +198,6 @@ namespace SafeExamBrowser.Client
{ {
var moduleLogger = ModuleLogger(nameof(BrowserApplication)); var moduleLogger = ModuleLogger(nameof(BrowserApplication));
var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory); var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory);
var browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, uiFactory); var operation = new BrowserOperation(actionCenter, context, logger, taskbar, uiFactory);
context.Browser = browser; context.Browser = browser;

View file

@ -1,20 +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 System;
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Client.Notifications
{
internal class AboutNotificationIconResource : IIconResource
{
public Uri Uri => new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/AboutNotification.xaml");
public bool IsBitmapResource => false;
public bool IsXamlResource => true;
}
}

View file

@ -6,6 +6,7 @@
* 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 SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
@ -14,14 +15,17 @@ namespace SafeExamBrowser.Client.Notifications
{ {
internal class AboutNotificationInfo : INotificationInfo internal class AboutNotificationInfo : INotificationInfo
{ {
private IText text; public string Tooltip { get; }
public IconResource IconResource { get; }
public string Tooltip => text.Get(TextKey.Notification_AboutTooltip);
public IIconResource IconResource { get; } = new AboutNotificationIconResource();
public AboutNotificationInfo(IText text) public AboutNotificationInfo(IText text)
{ {
this.text = text; IconResource = new IconResource
{
Type = IconResourceType.Xaml,
Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/AboutNotification.xaml")
};
Tooltip = text.Get(TextKey.Notification_AboutTooltip);
} }
} }
} }

View file

@ -1,20 +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 System;
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Client.Notifications
{
internal class LogNotificationIconResource : IIconResource
{
public Uri Uri => new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/LogNotification.ico");
public bool IsBitmapResource => true;
public bool IsXamlResource => false;
}
}

View file

@ -6,6 +6,7 @@
* 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 SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
@ -14,13 +15,17 @@ namespace SafeExamBrowser.Client.Notifications
{ {
internal class LogNotificationInfo : INotificationInfo internal class LogNotificationInfo : INotificationInfo
{ {
public string Tooltip { get; private set; } public string Tooltip { get; }
public IIconResource IconResource { get; private set; } public IconResource IconResource { get; }
public LogNotificationInfo(IText text) public LogNotificationInfo(IText text)
{ {
IconResource = new IconResource
{
Type = IconResourceType.Bitmap,
Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/LogNotification.ico")
};
Tooltip = text.Get(TextKey.Notification_LogTooltip); Tooltip = text.Get(TextKey.Notification_LogTooltip);
IconResource = new LogNotificationIconResource();
} }
} }
} }

View file

@ -117,7 +117,6 @@ namespace SafeExamBrowser.Client.Operations
if (result == FactoryResult.Success) if (result == FactoryResult.Success)
{ {
application.Initialize();
Context.Applications.Add(application); Context.Applications.Add(application);
} }
else else
@ -129,7 +128,10 @@ namespace SafeExamBrowser.Client.Operations
private void FinalizeApplications() private void FinalizeApplications()
{ {
// TODO: Terminate all running applications! foreach (var application in Context.Applications)
{
application.Terminate();
}
} }
private OperationResult HandleAutoTerminationFailure(IList<RunningApplication> applications) private OperationResult HandleAutoTerminationFailure(IList<RunningApplication> applications)

View file

@ -45,8 +45,15 @@ namespace SafeExamBrowser.Client.Operations
Context.Browser.Initialize(); Context.Browser.Initialize();
actionCenter.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.ActionCenter)); if (Context.Settings.ActionCenter.EnableActionCenter)
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar)); {
actionCenter.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.ActionCenter), true);
}
if (Context.Settings.Taskbar.EnableTaskbar)
{
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true);
}
return OperationResult.Success; return OperationResult.Success;
} }

View file

@ -124,6 +124,7 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing action center..."); logger.Info("Initializing action center...");
actionCenter.InitializeText(text); actionCenter.InitializeText(text);
InitializeApplicationsFor(Location.ActionCenter);
InitializeAboutNotificationForActionCenter(); InitializeAboutNotificationForActionCenter();
InitializeAudioForActionCenter(); InitializeAudioForActionCenter();
InitializeClockForActionCenter(); InitializeClockForActionCenter();
@ -145,6 +146,7 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing taskbar..."); logger.Info("Initializing taskbar...");
taskbar.InitializeText(text); taskbar.InitializeText(text);
InitializeApplicationsFor(Location.Taskbar);
InitializeAboutNotificationForTaskbar(); InitializeAboutNotificationForTaskbar();
InitializeLogNotificationForTaskbar(); InitializeLogNotificationForTaskbar();
InitializePowerSupplyForTaskbar(); InitializePowerSupplyForTaskbar();
@ -159,6 +161,24 @@ namespace SafeExamBrowser.Client.Operations
} }
} }
private void InitializeApplicationsFor(Location location)
{
foreach (var application in Context.Applications)
{
var control = uiFactory.CreateApplicationControl(application, location);
switch (location)
{
case Location.ActionCenter:
actionCenter.AddApplicationControl(control);
break;
case Location.Taskbar:
taskbar.AddApplicationControl(control);
break;
}
}
}
private void InitializeSystemComponents() private void InitializeSystemComponents()
{ {
audio.Initialize(); audio.Initialize();

View file

@ -84,10 +84,8 @@
<Compile Include="Communication\ClientHost.cs" /> <Compile Include="Communication\ClientHost.cs" />
<Compile Include="CompositionRoot.cs" /> <Compile Include="CompositionRoot.cs" />
<Compile Include="Notifications\AboutNotificationController.cs" /> <Compile Include="Notifications\AboutNotificationController.cs" />
<Compile Include="Notifications\AboutNotificationIconResource.cs" />
<Compile Include="Notifications\AboutNotificationInfo.cs" /> <Compile Include="Notifications\AboutNotificationInfo.cs" />
<Compile Include="Notifications\LogNotificationController.cs" /> <Compile Include="Notifications\LogNotificationController.cs" />
<Compile Include="Notifications\LogNotificationIconResource.cs" />
<Compile Include="Notifications\LogNotificationInfo.cs" /> <Compile Include="Notifications\LogNotificationInfo.cs" />
<Compile Include="Operations\BrowserOperation.cs" /> <Compile Include="Operations\BrowserOperation.cs" />
<Compile Include="Operations\ClipboardOperation.cs" /> <Compile Include="Operations\ClipboardOperation.cs" />

View file

@ -102,6 +102,11 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
application.AutoTerminate = autoTerminate; application.AutoTerminate = autoTerminate;
} }
if (applicationData.TryGetValue(Keys.Applications.Description, out v) && v is string description)
{
application.Description = description;
}
if (applicationData.TryGetValue(Keys.Applications.DisplayName, out v) && v is string displayName) if (applicationData.TryGetValue(Keys.Applications.DisplayName, out v) && v is string displayName)
{ {
application.DisplayName = displayName; application.DisplayName = displayName;

View file

@ -26,6 +26,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string AutoStart = "autostart"; internal const string AutoStart = "autostart";
internal const string AutoTerminate = "strongKill"; internal const string AutoTerminate = "strongKill";
internal const string Blacklist = "prohibitedProcesses"; internal const string Blacklist = "prohibitedProcesses";
internal const string Description = "description";
internal const string DisplayName = "title"; internal const string DisplayName = "title";
internal const string ExecutableName = "executable"; internal const string ExecutableName = "executable";
internal const string ExecutablePath = "path"; internal const string ExecutablePath = "path";

View file

@ -13,21 +13,16 @@ namespace SafeExamBrowser.Core.Contracts
/// <summary> /// <summary>
/// Defines an icon resource, i.e. the path to and type of an icon. /// Defines an icon resource, i.e. the path to and type of an icon.
/// </summary> /// </summary>
public interface IIconResource public class IconResource
{ {
/// <summary> /// <summary>
/// The <see cref="System.Uri"/> pointing to the icon. /// Defines the data type of the resource.
/// </summary> /// </summary>
Uri Uri { get; } public IconResourceType Type { get; set; }
/// <summary> /// <summary>
/// Indicates whether the icon resource consists of a bitmap image (i.e. raster graphics). /// The <see cref="System.Uri"/> pointing to the icon data.
/// </summary> /// </summary>
bool IsBitmapResource { get; } public Uri Uri { get; set; }
/// <summary>
/// Indicates whether the icon resource consists of XAML markup (i.e. vector graphics).
/// </summary>
bool IsXamlResource { get; }
} }
} }

View file

@ -0,0 +1,31 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Core.Contracts
{
/// <summary>
/// Defines the data format of an icon resource.
/// </summary>
public enum IconResourceType
{
/// <summary>
/// The icon resource is a bitmap image (i.e. raster graphics).
/// </summary>
Bitmap,
/// <summary>
/// The icon resource is a file with embedded icon data (e.g. an executable).
/// </summary>
Embedded,
/// <summary>
/// The icon resource consists of XAML markup (i.e. vector graphics).
/// </summary>
Xaml
}
}

View file

@ -53,7 +53,8 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="IIconResource.cs" /> <Compile Include="IconResource.cs" />
<Compile Include="IconResourceType.cs" />
<Compile Include="OperationModel\Events\ActionRequiredEventArgs.cs" /> <Compile Include="OperationModel\Events\ActionRequiredEventArgs.cs" />
<Compile Include="OperationModel\Events\ActionRequiredEventHandler.cs" /> <Compile Include="OperationModel\Events\ActionRequiredEventHandler.cs" />
<Compile Include="OperationModel\Events\ProgressChangedEventArgs.cs" /> <Compile Include="OperationModel\Events\ProgressChangedEventArgs.cs" />

View file

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

View file

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

View file

@ -42,6 +42,11 @@ namespace SafeExamBrowser.Settings.Applications
/// </summary> /// </summary>
public bool AutoTerminate { get; set; } public bool AutoTerminate { get; set; }
/// <summary>
/// Provides further information about the application.
/// </summary>
public string Description { get; set; }
/// <summary> /// <summary>
/// The display name to be used for the application (e.g. in the shell). /// The display name to be used for the application (e.g. in the shell).
/// </summary> /// </summary>

View file

@ -75,7 +75,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// <summary> /// <summary>
/// Updates the icon of the browser window. /// Updates the icon of the browser window.
/// </summary> /// </summary>
void UpdateIcon(IIconResource icon); void UpdateIcon(IconResource icon);
/// <summary> /// <summary>
/// Updates the loading state according to the given value. /// Updates the loading state according to the given value.

View file

@ -29,7 +29,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// <summary> /// <summary>
/// Adds the given application control to the action center. /// Adds the given application control to the action center.
/// </summary> /// </summary>
void AddApplicationControl(IApplicationControl control); void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false);
/// <summary> /// <summary>
/// Adds the given notification control to the action center. /// Adds the given notification control to the action center.

View file

@ -29,7 +29,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// <summary> /// <summary>
/// Adds the given application control to the taskbar. /// Adds the given application control to the taskbar.
/// </summary> /// </summary>
void AddApplicationControl(IApplicationControl control); void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false);
/// <summary> /// <summary>
/// Adds the given notification control to the taskbar. /// Adds the given notification control to the taskbar.

View file

@ -30,11 +30,18 @@ namespace SafeExamBrowser.UserInterface.Desktop
InitializeActionCenter(); InitializeActionCenter();
} }
public void AddApplicationControl(IApplicationControl control) public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false)
{ {
if (control is UIElement uiElement) if (control is UIElement uiElement)
{ {
ApplicationPanel.Children.Add(uiElement); if (atFirstPosition)
{
ApplicationPanel.Children.Insert(0, uiElement);
}
else
{
ApplicationPanel.Children.Add(uiElement);
}
} }
} }

View file

@ -104,7 +104,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
Dispatcher.Invoke(() => UrlTextBox.Text = url); Dispatcher.Invoke(() => UrlTextBox.Text = url);
} }
public void UpdateIcon(IIconResource icon) public void UpdateIcon(IconResource icon)
{ {
Dispatcher.InvokeAsync(() => Icon = new BitmapImage(icon.Uri)); Dispatcher.InvokeAsync(() => Icon = new BitmapImage(icon.Uri));
} }
@ -282,10 +282,10 @@ namespace SafeExamBrowser.UserInterface.Desktop
var forwardUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/NavigateForward.xaml"); var forwardUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/NavigateForward.xaml");
var menuUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Menu.xaml"); var menuUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Menu.xaml");
var reloadUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Reload.xaml"); var reloadUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Reload.xaml");
var backward = new XamlIconResource(backUri); var backward = new IconResource { Type = IconResourceType.Xaml, Uri = backUri };
var forward = new XamlIconResource(forwardUri); var forward = new IconResource { Type = IconResourceType.Xaml, Uri = forwardUri };
var menu = new XamlIconResource(menuUri); var menu = new IconResource { Type = IconResourceType.Xaml, Uri = menuUri };
var reload = new XamlIconResource(reloadUri); var reload = new IconResource { Type = IconResourceType.Xaml, Uri = reloadUri };
BackwardButton.Content = IconResourceLoader.Load(backward); BackwardButton.Content = IconResourceLoader.Load(backward);
ForwardButton.Content = IconResourceLoader.Load(forward); ForwardButton.Content = IconResourceLoader.Load(forward);

View file

@ -16,12 +16,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
public partial class ActionCenterApplicationButton : UserControl public partial class ActionCenterApplicationButton : UserControl
{ {
private IApplicationInfo info; private ApplicationInfo info;
private IApplicationInstance instance; private IApplicationInstance instance;
internal event EventHandler Clicked; internal event EventHandler Clicked;
public ActionCenterApplicationButton(IApplicationInfo info, IApplicationInstance instance = null) public ActionCenterApplicationButton(ApplicationInfo info, IApplicationInstance instance = null)
{ {
this.info = info; this.info = info;
this.instance = instance; this.instance = instance;
@ -44,7 +44,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
} }
} }
private void Instance_IconChanged(IIconResource icon) private void Instance_IconChanged(IconResource icon)
{ {
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
} }

View file

@ -13,6 +13,7 @@ using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -25,8 +26,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private readonly IAudio audio; private readonly IAudio audio;
private readonly IText text; private readonly IText text;
private bool muted; private bool muted;
private XamlIconResource MutedIcon; private IconResource MutedIcon;
private XamlIconResource NoDeviceIcon; private IconResource NoDeviceIcon;
public ActionCenterAudioControl(IAudio audio, IText text) public ActionCenterAudioControl(IAudio audio, IText text)
{ {
@ -50,8 +51,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
MuteButton.Click += MuteButton_Click; MuteButton.Click += MuteButton_Click;
MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml")); MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml") };
NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_NoDevice.xaml")); NoDeviceIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_NoDevice.xaml") };
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray; Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
Popup.Closed += (o, args) => Grid.Background = originalBrush; Popup.Closed += (o, args) => Grid.Background = originalBrush;
@ -146,7 +147,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33"); var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri};
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -9,6 +9,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Controls; using System.Windows.Controls;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -27,7 +28,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void InitializeControl() private void InitializeControl()
{ {
var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml"); var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
Icon.Content = IconResourceLoader.Load(resource); Icon.Content = IconResourceLoader.Load(resource);
Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs()); Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs());

View file

@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using FontAwesome.WPF; using FontAwesome.WPF;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -133,7 +134,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0")); var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0"));
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_Light_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_Light_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -16,10 +16,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
public partial class TaskbarApplicationInstanceButton : UserControl public partial class TaskbarApplicationInstanceButton : UserControl
{ {
private IApplicationInfo info; private ApplicationInfo info;
private IApplicationInstance instance; private IApplicationInstance instance;
public TaskbarApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info) public TaskbarApplicationInstanceButton(IApplicationInstance instance, ApplicationInfo info)
{ {
this.info = info; this.info = info;
this.instance = instance; this.instance = instance;
@ -43,7 +43,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
instance.Activate(); instance.Activate();
} }
private void Instance_IconChanged(IIconResource icon) private void Instance_IconChanged(IconResource icon)
{ {
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
} }

View file

@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Media; using System.Windows.Media;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -24,8 +25,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private readonly IAudio audio; private readonly IAudio audio;
private readonly IText text; private readonly IText text;
private bool muted; private bool muted;
private XamlIconResource MutedIcon; private IconResource MutedIcon;
private XamlIconResource NoDeviceIcon; private IconResource NoDeviceIcon;
public TaskbarAudioControl(IAudio audio, IText text) public TaskbarAudioControl(IAudio audio, IText text)
{ {
@ -49,8 +50,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
MuteButton.Click += MuteButton_Click; MuteButton.Click += MuteButton_Click;
MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml")); MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml") };
NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_NoDevice.xaml")); NoDeviceIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_NoDevice.xaml") };
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Volume.ValueChanged += Volume_ValueChanged; Volume.ValueChanged += Volume_ValueChanged;
@ -153,7 +154,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33"); var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -10,6 +10,7 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -33,7 +34,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void LoadIcon() private void LoadIcon()
{ {
var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml"); var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
Button.Content = IconResourceLoader.Load(resource); Button.Content = IconResourceLoader.Load(resource);
} }

View file

@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using FontAwesome.WPF; using FontAwesome.WPF;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -142,7 +143,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{ {
var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0")); var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0"));
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -36,11 +36,18 @@ namespace SafeExamBrowser.UserInterface.Desktop
InitializeTaskbar(); InitializeTaskbar();
} }
public void AddApplicationControl(IApplicationControl control) public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false)
{ {
if (control is UIElement uiElement) if (control is UIElement uiElement)
{ {
ApplicationStackPanel.Children.Add(uiElement); if (atFirstPosition)
{
ApplicationStackPanel.Children.Insert(0, uiElement);
}
else
{
ApplicationStackPanel.Children.Add(uiElement);
}
} }
} }

View file

@ -30,11 +30,18 @@ namespace SafeExamBrowser.UserInterface.Mobile
InitializeActionCenter(); InitializeActionCenter();
} }
public void AddApplicationControl(IApplicationControl control) public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false)
{ {
if (control is UIElement uiElement) if (control is UIElement uiElement)
{ {
ApplicationPanel.Children.Add(uiElement); if (atFirstPosition)
{
ApplicationPanel.Children.Insert(0, uiElement);
}
else
{
ApplicationPanel.Children.Add(uiElement);
}
} }
} }

View file

@ -104,7 +104,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
Dispatcher.Invoke(() => UrlTextBox.Text = url); Dispatcher.Invoke(() => UrlTextBox.Text = url);
} }
public void UpdateIcon(IIconResource icon) public void UpdateIcon(IconResource icon)
{ {
Dispatcher.InvokeAsync(() => Icon = new BitmapImage(icon.Uri)); Dispatcher.InvokeAsync(() => Icon = new BitmapImage(icon.Uri));
} }
@ -291,10 +291,10 @@ namespace SafeExamBrowser.UserInterface.Mobile
var forwardUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/NavigateForward.xaml"); var forwardUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/NavigateForward.xaml");
var menuUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Menu.xaml"); var menuUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Menu.xaml");
var reloadUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Reload.xaml"); var reloadUri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Reload.xaml");
var backward = new XamlIconResource(backUri); var backward = new IconResource { Type = IconResourceType.Xaml, Uri = backUri };
var forward = new XamlIconResource(forwardUri); var forward = new IconResource { Type = IconResourceType.Xaml, Uri = forwardUri };
var menu = new XamlIconResource(menuUri); var menu = new IconResource { Type = IconResourceType.Xaml, Uri = menuUri };
var reload = new XamlIconResource(reloadUri); var reload = new IconResource { Type = IconResourceType.Xaml, Uri = reloadUri };
BackwardButton.Content = IconResourceLoader.Load(backward); BackwardButton.Content = IconResourceLoader.Load(backward);
ForwardButton.Content = IconResourceLoader.Load(forward); ForwardButton.Content = IconResourceLoader.Load(forward);

View file

@ -16,12 +16,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{ {
public partial class ActionCenterApplicationButton : UserControl public partial class ActionCenterApplicationButton : UserControl
{ {
private IApplicationInfo info; private ApplicationInfo info;
private IApplicationInstance instance; private IApplicationInstance instance;
internal event EventHandler Clicked; internal event EventHandler Clicked;
public ActionCenterApplicationButton(IApplicationInfo info, IApplicationInstance instance = null) public ActionCenterApplicationButton(ApplicationInfo info, IApplicationInstance instance = null)
{ {
this.info = info; this.info = info;
this.instance = instance; this.instance = instance;
@ -44,7 +44,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
} }
} }
private void Instance_IconChanged(IIconResource icon) private void Instance_IconChanged(IconResource icon)
{ {
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
} }

View file

@ -13,6 +13,7 @@ using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -25,8 +26,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private readonly IAudio audio; private readonly IAudio audio;
private readonly IText text; private readonly IText text;
private bool muted; private bool muted;
private XamlIconResource MutedIcon; private IconResource MutedIcon;
private XamlIconResource NoDeviceIcon; private IconResource NoDeviceIcon;
public ActionCenterAudioControl(IAudio audio, IText text) public ActionCenterAudioControl(IAudio audio, IText text)
{ {
@ -50,8 +51,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
MuteButton.Click += MuteButton_Click; MuteButton.Click += MuteButton_Click;
MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.xaml")); MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.xaml") };
NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Light_NoDevice.xaml")); NoDeviceIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Light_NoDevice.xaml") };
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray; Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
Popup.Closed += (o, args) => Grid.Background = originalBrush; Popup.Closed += (o, args) => Grid.Background = originalBrush;
@ -145,7 +146,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{ {
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33"); var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Light_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Light_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -9,6 +9,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Controls; using System.Windows.Controls;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -27,7 +28,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void InitializeControl() private void InitializeControl()
{ {
var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml"); var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
Icon.Content = IconResourceLoader.Load(resource); Icon.Content = IconResourceLoader.Load(resource);
Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs()); Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs());

View file

@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using FontAwesome.WPF; using FontAwesome.WPF;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -133,7 +134,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{ {
var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0")); var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0"));
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/WiFi_Light_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/WiFi_Light_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -16,10 +16,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{ {
public partial class TaskbarApplicationInstanceButton : UserControl public partial class TaskbarApplicationInstanceButton : UserControl
{ {
private IApplicationInfo info; private ApplicationInfo info;
private IApplicationInstance instance; private IApplicationInstance instance;
public TaskbarApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info) public TaskbarApplicationInstanceButton(IApplicationInstance instance, ApplicationInfo info)
{ {
this.info = info; this.info = info;
this.instance = instance; this.instance = instance;
@ -43,7 +43,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
instance.Activate(); instance.Activate();
} }
private void Instance_IconChanged(IIconResource icon) private void Instance_IconChanged(IconResource icon)
{ {
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
} }

View file

@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Media; using System.Windows.Media;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -24,8 +25,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private readonly IAudio audio; private readonly IAudio audio;
private readonly IText text; private readonly IText text;
private bool muted; private bool muted;
private XamlIconResource MutedIcon; private IconResource MutedIcon;
private XamlIconResource NoDeviceIcon; private IconResource NoDeviceIcon;
public TaskbarAudioControl(IAudio audio, IText text) public TaskbarAudioControl(IAudio audio, IText text)
{ {
@ -49,8 +50,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
MuteButton.Click += MuteButton_Click; MuteButton.Click += MuteButton_Click;
MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.xaml")); MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.xaml") };
NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_NoDevice.xaml")); NoDeviceIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_NoDevice.xaml") };
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Volume.ValueChanged += Volume_ValueChanged; Volume.ValueChanged += Volume_ValueChanged;
@ -153,7 +154,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{ {
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33"); var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -10,6 +10,7 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -33,7 +34,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void LoadIcon() private void LoadIcon()
{ {
var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml"); var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
Button.Content = IconResourceLoader.Load(resource); Button.Content = IconResourceLoader.Load(resource);
} }

View file

@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using FontAwesome.WPF; using FontAwesome.WPF;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -142,7 +143,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{ {
var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0")); var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0"));
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/WiFi_{icon}.xaml"); var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/WiFi_{icon}.xaml");
var resource = new XamlIconResource(uri); var resource = new IconResource { Type = IconResourceType.Xaml, Uri = uri };
return IconResourceLoader.Load(resource); return IconResourceLoader.Load(resource);
} }

View file

@ -36,11 +36,18 @@ namespace SafeExamBrowser.UserInterface.Mobile
InitializeTaskbar(); InitializeTaskbar();
} }
public void AddApplicationControl(IApplicationControl control) public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false)
{ {
if (control is UIElement uiElement) if (control is UIElement uiElement)
{ {
ApplicationStackPanel.Children.Add(uiElement); if (atFirstPosition)
{
ApplicationStackPanel.Children.Insert(0, uiElement);
}
else
{
ApplicationStackPanel.Children.Add(uiElement);
}
} }
} }

View file

@ -51,6 +51,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Xaml"> <Reference Include="System.Xaml">
@ -67,7 +68,6 @@
<Compile Include="Utilities\IconResourceLoader.cs" /> <Compile Include="Utilities\IconResourceLoader.cs" />
<Compile Include="Utilities\VisualExtensions.cs" /> <Compile Include="Utilities\VisualExtensions.cs" />
<Compile Include="Utilities\WindowUtility.cs" /> <Compile Include="Utilities\WindowUtility.cs" />
<Compile Include="Utilities\XamlIconResource.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">

View file

@ -7,40 +7,45 @@
*/ */
using System; using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Markup; using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.Core.Contracts;
using Brushes = System.Windows.Media.Brushes;
using Image = System.Windows.Controls.Image;
namespace SafeExamBrowser.UserInterface.Shared.Utilities namespace SafeExamBrowser.UserInterface.Shared.Utilities
{ {
public static class IconResourceLoader public static class IconResourceLoader
{ {
public static UIElement Load(IIconResource resource) public static UIElement Load(IconResource resource)
{ {
try try
{ {
if (resource.IsBitmapResource) switch (resource.Type)
{ {
return LoadBitmapResource(resource); case IconResourceType.Bitmap:
} return LoadBitmapResource(resource);
else if (resource.IsXamlResource) case IconResourceType.Embedded:
{ return LoadEmbeddedResource(resource);
return LoadXamlResource(resource); case IconResourceType.Xaml:
return LoadXamlResource(resource);
default:
throw new NotSupportedException($"Application icon resource of type '{resource.Type}' is not supported!");
} }
} }
catch (Exception) catch (Exception)
{ {
return NotFoundSymbol(); return NotFoundSymbol();
} }
throw new NotSupportedException($"Application icon resource of type '{resource.GetType()}' is not supported!");
} }
private static UIElement LoadBitmapResource(IIconResource resource) private static UIElement LoadBitmapResource(IconResource resource)
{ {
return new Image return new Image
{ {
@ -48,7 +53,28 @@ namespace SafeExamBrowser.UserInterface.Shared.Utilities
}; };
} }
private static UIElement LoadXamlResource(IIconResource resource) private static UIElement LoadEmbeddedResource(IconResource resource)
{
using (var stream = new MemoryStream())
{
var bitmap = new BitmapImage();
Icon.ExtractAssociatedIcon(resource.Uri.LocalPath).ToBitmap().Save(stream, ImageFormat.Png);
bitmap.BeginInit();
bitmap.StreamSource = stream;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
return new Image
{
Source = bitmap
};
}
}
private static UIElement LoadXamlResource(IconResource resource)
{ {
using (var stream = Application.GetResourceStream(resource.Uri)?.Stream) using (var stream = Application.GetResourceStream(resource.Uri)?.Stream)
{ {

View file

@ -1,25 +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 System;
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.UserInterface.Shared.Utilities
{
public class XamlIconResource : IIconResource
{
public Uri Uri { get; private set; }
public bool IsBitmapResource => false;
public bool IsXamlResource => true;
public XamlIconResource(Uri uri)
{
Uri = uri;
}
}
}