diff --git a/SafeExamBrowser.Applications.Contracts/IApplicationInfo.cs b/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs similarity index 82% rename from SafeExamBrowser.Applications.Contracts/IApplicationInfo.cs rename to SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs index cf147822..b47e66f1 100644 --- a/SafeExamBrowser.Applications.Contracts/IApplicationInfo.cs +++ b/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs @@ -13,21 +13,21 @@ namespace SafeExamBrowser.Applications.Contracts /// /// The information about an application which can be accessed via the shell. /// - public interface IApplicationInfo + public class ApplicationInfo { /// /// The name of the application. /// - string Name { get; } + public string Name { get; set; } /// /// The tooltip for the application. /// - string Tooltip { get; } + public string Tooltip { get; set; } /// /// The resource providing the application icon. /// - IIconResource IconResource { get; } + public IconResource IconResource { get; set; } } } diff --git a/SafeExamBrowser.Applications.Contracts/Events/IconChangedEventHandler.cs b/SafeExamBrowser.Applications.Contracts/Events/IconChangedEventHandler.cs index 1376c40a..93c764bc 100644 --- a/SafeExamBrowser.Applications.Contracts/Events/IconChangedEventHandler.cs +++ b/SafeExamBrowser.Applications.Contracts/Events/IconChangedEventHandler.cs @@ -13,5 +13,5 @@ namespace SafeExamBrowser.Applications.Contracts.Events /// /// Event handler used to indicate that the icon of an has changed. /// - public delegate void IconChangedEventHandler(IIconResource icon); + public delegate void IconChangedEventHandler(IconResource icon); } diff --git a/SafeExamBrowser.Applications.Contracts/IApplication.cs b/SafeExamBrowser.Applications.Contracts/IApplication.cs index 7b471329..c75b4908 100644 --- a/SafeExamBrowser.Applications.Contracts/IApplication.cs +++ b/SafeExamBrowser.Applications.Contracts/IApplication.cs @@ -18,7 +18,7 @@ namespace SafeExamBrowser.Applications.Contracts /// /// Provides information about the application. /// - IApplicationInfo Info { get; } + ApplicationInfo Info { get; } /// /// Fired when a new has started. diff --git a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj index 88aa494b..d37ed27a 100644 --- a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj +++ b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj @@ -60,7 +60,7 @@ - + diff --git a/SafeExamBrowser.Applications/ApplicationFactory.cs b/SafeExamBrowser.Applications/ApplicationFactory.cs index 92da83c6..2d77946c 100644 --- a/SafeExamBrowser.Applications/ApplicationFactory.cs +++ b/SafeExamBrowser.Applications/ApplicationFactory.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.IO; using Microsoft.Win32; using SafeExamBrowser.Applications.Contracts; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Settings.Applications; @@ -27,33 +28,46 @@ namespace SafeExamBrowser.Applications public FactoryResult TryCreate(WhitelistApplication settings, out IApplication application) { + var name = $"'{settings.DisplayName}' ({ settings.ExecutableName})"; + application = default(IApplication); try { - var success = TryFindMainExecutable(settings, out var mainExecutable); + var success = TryFindApplication(settings, out var executablePath); if (success) { - application = new ExternalApplication(); - logger.Debug($"Successfully initialized application '{settings.DisplayName}' ({settings.ExecutableName})."); + application = BuildApplication(executablePath, settings); + application.Initialize(); + + logger.Debug($"Successfully initialized application {name}."); return FactoryResult.Success; } - logger.Error($"Could not find application '{settings.DisplayName}' ({settings.ExecutableName})!"); + logger.Error($"Could not find application {name}!"); return FactoryResult.NotFound; } 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; } - 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(); var registryPath = QueryPathFromRegistry(settings); diff --git a/SafeExamBrowser.Applications/ExternalApplication.cs b/SafeExamBrowser.Applications/ExternalApplication.cs index 8136d4a5..08392f3b 100644 --- a/SafeExamBrowser.Applications/ExternalApplication.cs +++ b/SafeExamBrowser.Applications/ExternalApplication.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts.Events; @@ -14,10 +13,18 @@ namespace SafeExamBrowser.Applications { internal class ExternalApplication : IApplication { - public IApplicationInfo Info => throw new NotImplementedException(); + private string executablePath; + + public ApplicationInfo Info { get; } public event InstanceStartedEventHandler InstanceStarted; + internal ExternalApplication(string executablePath, ApplicationInfo info) + { + this.executablePath = executablePath; + this.Info = info; + } + public void Initialize() { diff --git a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj index 3f81c3cf..0ec08b64 100644 --- a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj +++ b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj @@ -62,6 +62,10 @@ {ac77745d-3b41-43e2-8e84-d40e5a4ee77f} SafeExamBrowser.Applications.Contracts + + {fe0e1224-b447-4b14-81e7-ed7d84822aa0} + SafeExamBrowser.Core.Contracts + {64ea30fb-11d4-436a-9c2b-88566285363e} SafeExamBrowser.Logging.Contracts diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs index 8b1d8dd9..45af2b62 100644 --- a/SafeExamBrowser.Browser/BrowserApplication.cs +++ b/SafeExamBrowser.Browser/BrowserApplication.cs @@ -38,7 +38,7 @@ namespace SafeExamBrowser.Browser private IText text; private IUserInterfaceFactory uiFactory; - public IApplicationInfo Info { get; private set; } + public ApplicationInfo Info { get; private set; } public event DownloadRequestedEventHandler ConfigurationDownloadRequested; public event InstanceStartedEventHandler InstanceStarted; @@ -65,7 +65,7 @@ namespace SafeExamBrowser.Browser var cefSettings = InitializeCefSettings(); var success = Cef.Initialize(cefSettings, true, default(IApp)); - Info = new BrowserApplicationInfo(); + Info = BuildApplicationInfo(); if (success) { @@ -95,6 +95,16 @@ namespace SafeExamBrowser.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) { var id = new BrowserInstanceIdentifier(++instanceIdCounter); diff --git a/SafeExamBrowser.Browser/BrowserApplicationInfo.cs b/SafeExamBrowser.Browser/BrowserApplicationInfo.cs deleted file mode 100644 index 5639cfb6..00000000 --- a/SafeExamBrowser.Browser/BrowserApplicationInfo.cs +++ /dev/null @@ -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(); - } -} diff --git a/SafeExamBrowser.Browser/BrowserIconResource.cs b/SafeExamBrowser.Browser/BrowserIconResource.cs index 9e07b06d..61f16d88 100644 --- a/SafeExamBrowser.Browser/BrowserIconResource.cs +++ b/SafeExamBrowser.Browser/BrowserIconResource.cs @@ -11,14 +11,11 @@ using SafeExamBrowser.Core.Contracts; 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) { + Type = IconResourceType.Bitmap; Uri = new Uri(uri ?? "pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/SafeExamBrowser.ico"); } } diff --git a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj index d9159f47..41ddff18 100644 --- a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj +++ b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj @@ -66,7 +66,6 @@ - diff --git a/SafeExamBrowser.Client.Contracts/INotificationInfo.cs b/SafeExamBrowser.Client.Contracts/INotificationInfo.cs index fe103dc7..c6e1de79 100644 --- a/SafeExamBrowser.Client.Contracts/INotificationInfo.cs +++ b/SafeExamBrowser.Client.Contracts/INotificationInfo.cs @@ -23,6 +23,6 @@ namespace SafeExamBrowser.Client.Contracts /// /// The resource providing the notification icon. /// - IIconResource IconResource { get; } + IconResource IconResource { get; } } } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs index acb181d8..b7b91f58 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/BrowserOperationTests.cs @@ -49,8 +49,8 @@ namespace SafeExamBrowser.Client.UnitTests.Operations sut.Perform(); browser.Verify(c => c.Initialize(), Times.Once); - actionCenter.Verify(a => a.AddApplicationControl(It.IsAny()), Times.Once); - taskbar.Verify(t => t.AddApplicationControl(It.IsAny()), Times.Once); + actionCenter.Verify(a => a.AddApplicationControl(It.IsAny(), true), Times.Once); + taskbar.Verify(t => t.AddApplicationControl(It.IsAny(), true), Times.Once); } [TestMethod] diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index ce209094..553b7940 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -198,7 +198,6 @@ namespace SafeExamBrowser.Client { var moduleLogger = ModuleLogger(nameof(BrowserApplication)); 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); context.Browser = browser; diff --git a/SafeExamBrowser.Client/Notifications/AboutNotificationIconResource.cs b/SafeExamBrowser.Client/Notifications/AboutNotificationIconResource.cs deleted file mode 100644 index 57ecab7c..00000000 --- a/SafeExamBrowser.Client/Notifications/AboutNotificationIconResource.cs +++ /dev/null @@ -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; - } -} diff --git a/SafeExamBrowser.Client/Notifications/AboutNotificationInfo.cs b/SafeExamBrowser.Client/Notifications/AboutNotificationInfo.cs index 8b13e3d6..5f50c675 100644 --- a/SafeExamBrowser.Client/Notifications/AboutNotificationInfo.cs +++ b/SafeExamBrowser.Client/Notifications/AboutNotificationInfo.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; @@ -14,14 +15,17 @@ namespace SafeExamBrowser.Client.Notifications { internal class AboutNotificationInfo : INotificationInfo { - private IText text; - - public string Tooltip => text.Get(TextKey.Notification_AboutTooltip); - public IIconResource IconResource { get; } = new AboutNotificationIconResource(); + public string Tooltip { get; } + public IconResource IconResource { get; } 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); } } } diff --git a/SafeExamBrowser.Client/Notifications/LogNotificationIconResource.cs b/SafeExamBrowser.Client/Notifications/LogNotificationIconResource.cs deleted file mode 100644 index 6078f8b1..00000000 --- a/SafeExamBrowser.Client/Notifications/LogNotificationIconResource.cs +++ /dev/null @@ -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; - } -} diff --git a/SafeExamBrowser.Client/Notifications/LogNotificationInfo.cs b/SafeExamBrowser.Client/Notifications/LogNotificationInfo.cs index 8f809860..dd7a9cd7 100644 --- a/SafeExamBrowser.Client/Notifications/LogNotificationInfo.cs +++ b/SafeExamBrowser.Client/Notifications/LogNotificationInfo.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; @@ -14,13 +15,17 @@ namespace SafeExamBrowser.Client.Notifications { internal class LogNotificationInfo : INotificationInfo { - public string Tooltip { get; private set; } - public IIconResource IconResource { get; private set; } + public string Tooltip { get; } + public IconResource IconResource { get; } 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); - IconResource = new LogNotificationIconResource(); } } } diff --git a/SafeExamBrowser.Client/Operations/ApplicationOperation.cs b/SafeExamBrowser.Client/Operations/ApplicationOperation.cs index 7087c04a..fccae8b3 100644 --- a/SafeExamBrowser.Client/Operations/ApplicationOperation.cs +++ b/SafeExamBrowser.Client/Operations/ApplicationOperation.cs @@ -117,7 +117,6 @@ namespace SafeExamBrowser.Client.Operations if (result == FactoryResult.Success) { - application.Initialize(); Context.Applications.Add(application); } else @@ -129,7 +128,10 @@ namespace SafeExamBrowser.Client.Operations private void FinalizeApplications() { - // TODO: Terminate all running applications! + foreach (var application in Context.Applications) + { + application.Terminate(); + } } private OperationResult HandleAutoTerminationFailure(IList applications) diff --git a/SafeExamBrowser.Client/Operations/BrowserOperation.cs b/SafeExamBrowser.Client/Operations/BrowserOperation.cs index 3a813074..e44cd11c 100644 --- a/SafeExamBrowser.Client/Operations/BrowserOperation.cs +++ b/SafeExamBrowser.Client/Operations/BrowserOperation.cs @@ -45,8 +45,15 @@ namespace SafeExamBrowser.Client.Operations Context.Browser.Initialize(); - actionCenter.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.ActionCenter)); - taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar)); + if (Context.Settings.ActionCenter.EnableActionCenter) + { + 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; } diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs index f71f7ccf..31d0fdae 100644 --- a/SafeExamBrowser.Client/Operations/ShellOperation.cs +++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs @@ -124,6 +124,7 @@ namespace SafeExamBrowser.Client.Operations logger.Info("Initializing action center..."); actionCenter.InitializeText(text); + InitializeApplicationsFor(Location.ActionCenter); InitializeAboutNotificationForActionCenter(); InitializeAudioForActionCenter(); InitializeClockForActionCenter(); @@ -145,6 +146,7 @@ namespace SafeExamBrowser.Client.Operations logger.Info("Initializing taskbar..."); taskbar.InitializeText(text); + InitializeApplicationsFor(Location.Taskbar); InitializeAboutNotificationForTaskbar(); InitializeLogNotificationForTaskbar(); 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() { audio.Initialize(); diff --git a/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj b/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj index cd77d41e..5472797a 100644 --- a/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj +++ b/SafeExamBrowser.Client/SafeExamBrowser.Client.csproj @@ -84,10 +84,8 @@ - - diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Applications.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Applications.cs index 25ea5fc3..4d241670 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Applications.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Applications.cs @@ -102,6 +102,11 @@ namespace SafeExamBrowser.Configuration.ConfigurationData 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) { application.DisplayName = displayName; diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs index 8a4643cb..f9d8634f 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs @@ -26,6 +26,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData internal const string AutoStart = "autostart"; internal const string AutoTerminate = "strongKill"; internal const string Blacklist = "prohibitedProcesses"; + internal const string Description = "description"; internal const string DisplayName = "title"; internal const string ExecutableName = "executable"; internal const string ExecutablePath = "path"; diff --git a/SafeExamBrowser.Core.Contracts/IIconResource.cs b/SafeExamBrowser.Core.Contracts/IconResource.cs similarity index 57% rename from SafeExamBrowser.Core.Contracts/IIconResource.cs rename to SafeExamBrowser.Core.Contracts/IconResource.cs index fb192573..beff06d3 100644 --- a/SafeExamBrowser.Core.Contracts/IIconResource.cs +++ b/SafeExamBrowser.Core.Contracts/IconResource.cs @@ -13,21 +13,16 @@ namespace SafeExamBrowser.Core.Contracts /// /// Defines an icon resource, i.e. the path to and type of an icon. /// - public interface IIconResource + public class IconResource { /// - /// The pointing to the icon. + /// Defines the data type of the resource. /// - Uri Uri { get; } + public IconResourceType Type { get; set; } /// - /// Indicates whether the icon resource consists of a bitmap image (i.e. raster graphics). + /// The pointing to the icon data. /// - bool IsBitmapResource { get; } - - /// - /// Indicates whether the icon resource consists of XAML markup (i.e. vector graphics). - /// - bool IsXamlResource { get; } + public Uri Uri { get; set; } } } diff --git a/SafeExamBrowser.Core.Contracts/IconResourceType.cs b/SafeExamBrowser.Core.Contracts/IconResourceType.cs new file mode 100644 index 00000000..11a004aa --- /dev/null +++ b/SafeExamBrowser.Core.Contracts/IconResourceType.cs @@ -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 +{ + /// + /// Defines the data format of an icon resource. + /// + public enum IconResourceType + { + /// + /// The icon resource is a bitmap image (i.e. raster graphics). + /// + Bitmap, + + /// + /// The icon resource is a file with embedded icon data (e.g. an executable). + /// + Embedded, + + /// + /// The icon resource consists of XAML markup (i.e. vector graphics). + /// + Xaml + } +} diff --git a/SafeExamBrowser.Core.Contracts/SafeExamBrowser.Core.Contracts.csproj b/SafeExamBrowser.Core.Contracts/SafeExamBrowser.Core.Contracts.csproj index ff9f5b20..2dad9aba 100644 --- a/SafeExamBrowser.Core.Contracts/SafeExamBrowser.Core.Contracts.csproj +++ b/SafeExamBrowser.Core.Contracts/SafeExamBrowser.Core.Contracts.csproj @@ -53,7 +53,8 @@ - + + diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index 9188234c..3872fd9a 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -18,6 +18,7 @@ namespace SafeExamBrowser.I18n.Contracts Browser_BlockedPageButton, Browser_BlockedPageMessage, Browser_BlockedPageTitle, + Browser_Tooltip, BrowserWindow_DeveloperConsoleMenuItem, BrowserWindow_ZoomMenuItem, Build, diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 3fa7dc06..56ee7796 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -12,6 +12,9 @@ Page Blocked + + Browser Application + Developer Console diff --git a/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs b/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs index 4d162450..e7515be5 100644 --- a/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs +++ b/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs @@ -42,6 +42,11 @@ namespace SafeExamBrowser.Settings.Applications /// public bool AutoTerminate { get; set; } + /// + /// Provides further information about the application. + /// + public string Description { get; set; } + /// /// The display name to be used for the application (e.g. in the shell). /// diff --git a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs index c27faab5..dda2c424 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Browser/IBrowserWindow.cs @@ -75,7 +75,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser /// /// Updates the icon of the browser window. /// - void UpdateIcon(IIconResource icon); + void UpdateIcon(IconResource icon); /// /// Updates the loading state according to the given value. diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs index 588036c8..7a74b4a5 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenter.cs @@ -29,7 +29,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell /// /// Adds the given application control to the action center. /// - void AddApplicationControl(IApplicationControl control); + void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false); /// /// Adds the given notification control to the action center. diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs index b5f9885d..e1989121 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskbar.cs @@ -29,7 +29,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell /// /// Adds the given application control to the taskbar. /// - void AddApplicationControl(IApplicationControl control); + void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false); /// /// Adds the given notification control to the taskbar. diff --git a/SafeExamBrowser.UserInterface.Desktop/ActionCenter.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/ActionCenter.xaml.cs index eaf0b0a4..2b81aa90 100644 --- a/SafeExamBrowser.UserInterface.Desktop/ActionCenter.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/ActionCenter.xaml.cs @@ -30,11 +30,18 @@ namespace SafeExamBrowser.UserInterface.Desktop InitializeActionCenter(); } - public void AddApplicationControl(IApplicationControl control) + public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false) { if (control is UIElement uiElement) { - ApplicationPanel.Children.Add(uiElement); + if (atFirstPosition) + { + ApplicationPanel.Children.Insert(0, uiElement); + } + else + { + ApplicationPanel.Children.Add(uiElement); + } } } diff --git a/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs index b1fd3b1a..59f83a39 100644 --- a/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/BrowserWindow.xaml.cs @@ -104,7 +104,7 @@ namespace SafeExamBrowser.UserInterface.Desktop Dispatcher.Invoke(() => UrlTextBox.Text = url); } - public void UpdateIcon(IIconResource icon) + public void UpdateIcon(IconResource icon) { 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 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 backward = new XamlIconResource(backUri); - var forward = new XamlIconResource(forwardUri); - var menu = new XamlIconResource(menuUri); - var reload = new XamlIconResource(reloadUri); + var backward = new IconResource { Type = IconResourceType.Xaml, Uri = backUri }; + var forward = new IconResource { Type = IconResourceType.Xaml, Uri = forwardUri }; + var menu = new IconResource { Type = IconResourceType.Xaml, Uri = menuUri }; + var reload = new IconResource { Type = IconResourceType.Xaml, Uri = reloadUri }; BackwardButton.Content = IconResourceLoader.Load(backward); ForwardButton.Content = IconResourceLoader.Load(forward); diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs index 8fa47cec..0e58921f 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs @@ -16,12 +16,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls { public partial class ActionCenterApplicationButton : UserControl { - private IApplicationInfo info; + private ApplicationInfo info; private IApplicationInstance instance; internal event EventHandler Clicked; - public ActionCenterApplicationButton(IApplicationInfo info, IApplicationInstance instance = null) + public ActionCenterApplicationButton(ApplicationInfo info, IApplicationInstance instance = null) { this.info = info; 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)); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterAudioControl.xaml.cs index 0f9b688d..d7ee7830 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterAudioControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterAudioControl.xaml.cs @@ -13,6 +13,7 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Threading; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.UserInterface.Contracts.Shell; @@ -25,8 +26,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls private readonly IAudio audio; private readonly IText text; private bool muted; - private XamlIconResource MutedIcon; - private XamlIconResource NoDeviceIcon; + private IconResource MutedIcon; + private IconResource NoDeviceIcon; public ActionCenterAudioControl(IAudio audio, IText text) { @@ -50,8 +51,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); MuteButton.Click += MuteButton_Click; - MutedIcon = new XamlIconResource(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")); + MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.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.Opened += (o, args) => Grid.Background = Brushes.Gray; 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 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); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterQuitButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterQuitButton.xaml.cs index 1f7cd5bf..3c43d031 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterQuitButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterQuitButton.xaml.cs @@ -9,6 +9,7 @@ using System; using System.ComponentModel; using System.Windows.Controls; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Shared.Utilities; @@ -27,7 +28,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls private void InitializeControl() { 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); Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs()); diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterWirelessNetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterWirelessNetworkControl.xaml.cs index 6ec45f5d..60251f58 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterWirelessNetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterWirelessNetworkControl.xaml.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using FontAwesome.WPF; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; 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 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); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs index b8d59716..a848b3c2 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs @@ -16,10 +16,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls { public partial class TaskbarApplicationInstanceButton : UserControl { - private IApplicationInfo info; + private ApplicationInfo info; private IApplicationInstance instance; - public TaskbarApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info) + public TaskbarApplicationInstanceButton(IApplicationInstance instance, ApplicationInfo info) { this.info = info; this.instance = instance; @@ -43,7 +43,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls instance.Activate(); } - private void Instance_IconChanged(IIconResource icon) + private void Instance_IconChanged(IconResource icon) { Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs index 321d6e7d..3435f8d5 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.UserInterface.Contracts.Shell; @@ -24,8 +25,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls private readonly IAudio audio; private readonly IText text; private bool muted; - private XamlIconResource MutedIcon; - private XamlIconResource NoDeviceIcon; + private IconResource MutedIcon; + private IconResource NoDeviceIcon; public TaskbarAudioControl(IAudio audio, IText text) { @@ -49,8 +50,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); MuteButton.Click += MuteButton_Click; - MutedIcon = new XamlIconResource(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")); + MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.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)); 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 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); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarQuitButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarQuitButton.xaml.cs index 4de0e8be..1067e0c1 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarQuitButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarQuitButton.xaml.cs @@ -10,6 +10,7 @@ using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Shared.Utilities; @@ -33,7 +34,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls private void LoadIcon() { 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); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarWirelessNetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarWirelessNetworkControl.xaml.cs index bec84b6d..b32549af 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarWirelessNetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarWirelessNetworkControl.xaml.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using FontAwesome.WPF; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; 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 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); } diff --git a/SafeExamBrowser.UserInterface.Desktop/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Taskbar.xaml.cs index eb69aacd..d9937372 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Taskbar.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Taskbar.xaml.cs @@ -36,11 +36,18 @@ namespace SafeExamBrowser.UserInterface.Desktop InitializeTaskbar(); } - public void AddApplicationControl(IApplicationControl control) + public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false) { if (control is UIElement uiElement) { - ApplicationStackPanel.Children.Add(uiElement); + if (atFirstPosition) + { + ApplicationStackPanel.Children.Insert(0, uiElement); + } + else + { + ApplicationStackPanel.Children.Add(uiElement); + } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs index a5d87517..8b423f06 100644 --- a/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/ActionCenter.xaml.cs @@ -30,11 +30,18 @@ namespace SafeExamBrowser.UserInterface.Mobile InitializeActionCenter(); } - public void AddApplicationControl(IApplicationControl control) + public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false) { if (control is UIElement uiElement) { - ApplicationPanel.Children.Add(uiElement); + if (atFirstPosition) + { + ApplicationPanel.Children.Insert(0, uiElement); + } + else + { + ApplicationPanel.Children.Add(uiElement); + } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs index c61a6852..3283080c 100644 --- a/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/BrowserWindow.xaml.cs @@ -104,7 +104,7 @@ namespace SafeExamBrowser.UserInterface.Mobile Dispatcher.Invoke(() => UrlTextBox.Text = url); } - public void UpdateIcon(IIconResource icon) + public void UpdateIcon(IconResource icon) { 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 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 backward = new XamlIconResource(backUri); - var forward = new XamlIconResource(forwardUri); - var menu = new XamlIconResource(menuUri); - var reload = new XamlIconResource(reloadUri); + var backward = new IconResource { Type = IconResourceType.Xaml, Uri = backUri }; + var forward = new IconResource { Type = IconResourceType.Xaml, Uri = forwardUri }; + var menu = new IconResource { Type = IconResourceType.Xaml, Uri = menuUri }; + var reload = new IconResource { Type = IconResourceType.Xaml, Uri = reloadUri }; BackwardButton.Content = IconResourceLoader.Load(backward); ForwardButton.Content = IconResourceLoader.Load(forward); diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs index 2bb70ffd..eb31f74a 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs @@ -16,12 +16,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls { public partial class ActionCenterApplicationButton : UserControl { - private IApplicationInfo info; + private ApplicationInfo info; private IApplicationInstance instance; internal event EventHandler Clicked; - public ActionCenterApplicationButton(IApplicationInfo info, IApplicationInstance instance = null) + public ActionCenterApplicationButton(ApplicationInfo info, IApplicationInstance instance = null) { this.info = info; 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)); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs index 992128fb..385babb1 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterAudioControl.xaml.cs @@ -13,6 +13,7 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Threading; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.UserInterface.Contracts.Shell; @@ -25,8 +26,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls private readonly IAudio audio; private readonly IText text; private bool muted; - private XamlIconResource MutedIcon; - private XamlIconResource NoDeviceIcon; + private IconResource MutedIcon; + private IconResource NoDeviceIcon; public ActionCenterAudioControl(IAudio audio, IText text) { @@ -50,8 +51,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); MuteButton.Click += MuteButton_Click; - MutedIcon = new XamlIconResource(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")); + MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.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.Opened += (o, args) => Grid.Background = Brushes.Gray; 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 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); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs index 18ab9a51..faa45d05 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterQuitButton.xaml.cs @@ -9,6 +9,7 @@ using System; using System.ComponentModel; using System.Windows.Controls; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Shared.Utilities; @@ -27,7 +28,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls private void InitializeControl() { 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); Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs()); diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs index 3248fef0..99f2b65c 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterWirelessNetworkControl.xaml.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using FontAwesome.WPF; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; 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 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); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs index c93cf56d..e901f343 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs @@ -16,10 +16,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls { public partial class TaskbarApplicationInstanceButton : UserControl { - private IApplicationInfo info; + private ApplicationInfo info; private IApplicationInstance instance; - public TaskbarApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info) + public TaskbarApplicationInstanceButton(IApplicationInstance instance, ApplicationInfo info) { this.info = info; this.instance = instance; @@ -43,7 +43,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls instance.Activate(); } - private void Instance_IconChanged(IIconResource icon) + private void Instance_IconChanged(IconResource icon) { Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon)); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs index d6458f55..6907412c 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarAudioControl.xaml.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.UserInterface.Contracts.Shell; @@ -24,8 +25,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls private readonly IAudio audio; private readonly IText text; private bool muted; - private XamlIconResource MutedIcon; - private XamlIconResource NoDeviceIcon; + private IconResource MutedIcon; + private IconResource NoDeviceIcon; public TaskbarAudioControl(IAudio audio, IText text) { @@ -49,8 +50,8 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); MuteButton.Click += MuteButton_Click; - MutedIcon = new XamlIconResource(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")); + MutedIcon = new IconResource { Type = IconResourceType.Xaml, Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.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)); 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 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); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml.cs index f351cbd4..ddf99ff0 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarQuitButton.xaml.cs @@ -10,6 +10,7 @@ using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Shared.Utilities; @@ -33,7 +34,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls private void LoadIcon() { 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); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs index 80d05004..8d6267e9 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarWirelessNetworkControl.xaml.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using FontAwesome.WPF; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; 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 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); } diff --git a/SafeExamBrowser.UserInterface.Mobile/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Taskbar.xaml.cs index cbe19867..23f61077 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Taskbar.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Taskbar.xaml.cs @@ -36,11 +36,18 @@ namespace SafeExamBrowser.UserInterface.Mobile InitializeTaskbar(); } - public void AddApplicationControl(IApplicationControl control) + public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false) { if (control is UIElement uiElement) { - ApplicationStackPanel.Children.Add(uiElement); + if (atFirstPosition) + { + ApplicationStackPanel.Children.Insert(0, uiElement); + } + else + { + ApplicationStackPanel.Children.Add(uiElement); + } } } diff --git a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj index 9793bd4e..5f2c7568 100644 --- a/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj +++ b/SafeExamBrowser.UserInterface.Shared/SafeExamBrowser.UserInterface.Shared.csproj @@ -51,6 +51,7 @@ + @@ -67,7 +68,6 @@ - diff --git a/SafeExamBrowser.UserInterface.Shared/Utilities/IconResourceLoader.cs b/SafeExamBrowser.UserInterface.Shared/Utilities/IconResourceLoader.cs index 7b841dc6..475dff51 100644 --- a/SafeExamBrowser.UserInterface.Shared/Utilities/IconResourceLoader.cs +++ b/SafeExamBrowser.UserInterface.Shared/Utilities/IconResourceLoader.cs @@ -7,40 +7,45 @@ */ using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Markup; -using System.Windows.Media; using System.Windows.Media.Imaging; using SafeExamBrowser.Core.Contracts; +using Brushes = System.Windows.Media.Brushes; +using Image = System.Windows.Controls.Image; namespace SafeExamBrowser.UserInterface.Shared.Utilities { public static class IconResourceLoader { - public static UIElement Load(IIconResource resource) + public static UIElement Load(IconResource resource) { try { - if (resource.IsBitmapResource) + switch (resource.Type) { - return LoadBitmapResource(resource); - } - else if (resource.IsXamlResource) - { - return LoadXamlResource(resource); + case IconResourceType.Bitmap: + return LoadBitmapResource(resource); + case IconResourceType.Embedded: + return LoadEmbeddedResource(resource); + case IconResourceType.Xaml: + return LoadXamlResource(resource); + default: + throw new NotSupportedException($"Application icon resource of type '{resource.Type}' is not supported!"); } } catch (Exception) { 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 { @@ -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) { diff --git a/SafeExamBrowser.UserInterface.Shared/Utilities/XamlIconResource.cs b/SafeExamBrowser.UserInterface.Shared/Utilities/XamlIconResource.cs deleted file mode 100644 index 97b33a15..00000000 --- a/SafeExamBrowser.UserInterface.Shared/Utilities/XamlIconResource.cs +++ /dev/null @@ -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; - } - } -}