diff --git a/SafeExamBrowser.Core/Configuration/AboutNotificationIconResource.cs b/SafeExamBrowser.Configuration/AboutNotificationIconResource.cs similarity index 93% rename from SafeExamBrowser.Core/Configuration/AboutNotificationIconResource.cs rename to SafeExamBrowser.Configuration/AboutNotificationIconResource.cs index c0eefc69..43a52f3d 100644 --- a/SafeExamBrowser.Core/Configuration/AboutNotificationIconResource.cs +++ b/SafeExamBrowser.Configuration/AboutNotificationIconResource.cs @@ -9,7 +9,7 @@ using System; using SafeExamBrowser.Contracts.Configuration; -namespace SafeExamBrowser.Core.Configuration +namespace SafeExamBrowser.Configuration { public class AboutNotificationIconResource : IIconResource { diff --git a/SafeExamBrowser.Core/Configuration/AboutNotificationInfo.cs b/SafeExamBrowser.Configuration/AboutNotificationInfo.cs similarity index 93% rename from SafeExamBrowser.Core/Configuration/AboutNotificationInfo.cs rename to SafeExamBrowser.Configuration/AboutNotificationInfo.cs index 2594fa57..e1f7dc11 100644 --- a/SafeExamBrowser.Core/Configuration/AboutNotificationInfo.cs +++ b/SafeExamBrowser.Configuration/AboutNotificationInfo.cs @@ -9,7 +9,7 @@ using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.I18n; -namespace SafeExamBrowser.Core.Configuration +namespace SafeExamBrowser.Configuration { public class AboutNotificationInfo : INotificationInfo { diff --git a/SafeExamBrowser.Configuration/Properties/AssemblyInfo.cs b/SafeExamBrowser.Configuration/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..7e30e8c0 --- /dev/null +++ b/SafeExamBrowser.Configuration/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SafeExamBrowser.Configuration")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SafeExamBrowser.Configuration")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c388c4dd-a159-457d-af92-89f7ad185109")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj new file mode 100644 index 00000000..4f3ea868 --- /dev/null +++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj @@ -0,0 +1,62 @@ + + + + + Debug + AnyCPU + {C388C4DD-A159-457D-AF92-89F7AD185109} + Library + Properties + SafeExamBrowser.Configuration + SafeExamBrowser.Configuration + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + {47DA5933-BEF8-4729-94E6-ABDE2DB12262} + SafeExamBrowser.Contracts + + + {73724659-4150-4792-a94e-42f5f3c1b696} + SafeExamBrowser.WindowsApi + + + + \ No newline at end of file diff --git a/SafeExamBrowser.Core/Configuration/Settings.cs b/SafeExamBrowser.Configuration/Settings.cs similarity index 96% rename from SafeExamBrowser.Core/Configuration/Settings.cs rename to SafeExamBrowser.Configuration/Settings.cs index 56b5ae25..63370932 100644 --- a/SafeExamBrowser.Core/Configuration/Settings.cs +++ b/SafeExamBrowser.Configuration/Settings.cs @@ -11,7 +11,7 @@ using System.IO; using System.Reflection; using SafeExamBrowser.Contracts.Configuration; -namespace SafeExamBrowser.Core.Configuration +namespace SafeExamBrowser.Configuration { public class Settings : ISettings { diff --git a/SafeExamBrowser.Configuration/WorkingArea.cs b/SafeExamBrowser.Configuration/WorkingArea.cs new file mode 100644 index 00000000..5c2ba20f --- /dev/null +++ b/SafeExamBrowser.Configuration/WorkingArea.cs @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 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.Windows.Forms; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.WindowsApi; +using SafeExamBrowser.WindowsApi.Types; + +namespace SafeExamBrowser.Configuration +{ + public class WorkingArea : IWorkingArea + { + private ILogger logger; + private RECT? initial; + + public WorkingArea(ILogger logger) + { + this.logger = logger; + } + + public void InitializeFor(ITaskbar taskbar) + { + initial = User32.GetWorkingArea(); + + LogWorkingArea("Saved initial working area", initial.Value); + + var area = new RECT + { + Left = 0, + Top = 0, + Right = Screen.PrimaryScreen.Bounds.Width, + Bottom = Screen.PrimaryScreen.Bounds.Height - taskbar.GetAbsoluteHeight() + }; + + LogWorkingArea("Setting new working area", area); + User32.SetWorkingArea(area); + LogWorkingArea("Working area is now set to", User32.GetWorkingArea()); + } + + public void Reset() + { + if (initial.HasValue) + { + User32.SetWorkingArea(initial.Value); + LogWorkingArea("Restored initial working area", initial.Value); + } + } + + private void LogWorkingArea(string message, RECT area) + { + logger.Info($"{message}: Left = {area.Left}, Top = {area.Top}, Right = {area.Right}, Bottom = {area.Bottom}."); + } + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/IWorkingArea.cs b/SafeExamBrowser.Contracts/Configuration/IWorkingArea.cs new file mode 100644 index 00000000..2b61ce86 --- /dev/null +++ b/SafeExamBrowser.Contracts/Configuration/IWorkingArea.cs @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2017 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.Contracts.UserInterface; + +namespace SafeExamBrowser.Contracts.Configuration +{ + public interface IWorkingArea + { + /// + /// Sets the Windows working area to accommodate to the taskbar's dimensions. + /// + void InitializeFor(ITaskbar taskbar); + + /// + /// Resets the Windows working area to its previous (initial) state. + /// + void Reset(); + } +} diff --git a/SafeExamBrowser.Contracts/I18n/Key.cs b/SafeExamBrowser.Contracts/I18n/Key.cs index 16c021fc..e8060f11 100644 --- a/SafeExamBrowser.Contracts/I18n/Key.cs +++ b/SafeExamBrowser.Contracts/I18n/Key.cs @@ -23,8 +23,8 @@ namespace SafeExamBrowser.Contracts.I18n SplashScreen_InitializeBrowser, SplashScreen_InitializeProcessMonitoring, SplashScreen_InitializeTaskbar, - SplashScreen_InitializeWorkArea, - SplashScreen_RestoreWorkArea, + SplashScreen_InitializeWorkingArea, + SplashScreen_RestoreWorkingArea, SplashScreen_ShutdownProcedure, SplashScreen_StartupProcedure, SplashScreen_StopProcessMonitoring, diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index 66ee8d3a..c28dffe8 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -49,6 +49,7 @@ + diff --git a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs index 5bcad222..88eff1ab 100644 --- a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs +++ b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs @@ -27,24 +27,15 @@ namespace SafeExamBrowser.Contracts.UserInterface /// void SetMaxProgress(int max); - /// - /// Starts an animation indicating the user that something is going on. - /// - void StartBusyIndication(); - - /// - /// Stops the busy animation, if it was running. - /// - void StopBusyIndication(); - /// /// Updates the progress bar of the splash screen according to the specified amount. /// void UpdateProgress(int amount = 1); /// - /// Updates the status text of the splash screen. + /// Updates the status text of the splash screen. If the busy flag is set, + /// the splash screen will show an animation to indicate a long-running operation. /// - void UpdateText(Key key); + void UpdateText(Key key, bool showBusyIndication = false); } } diff --git a/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs b/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs index 973fb184..0a91efaa 100644 --- a/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs +++ b/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs @@ -22,13 +22,8 @@ namespace SafeExamBrowser.Contracts.UserInterface void AddNotification(ITaskbarNotification button); /// - /// Moves the taskbar to the given location on the screen. + /// Returns the absolute height of the taskbar (i.e. in physical pixels). /// - void SetPosition(int x, int y); - - /// - /// Sets the size of the taskbar. - /// - void SetSize(int width, int height); + int GetAbsoluteHeight(); } } diff --git a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs index fa2c3934..4870beec 100644 --- a/SafeExamBrowser.Core/Behaviour/ShutdownController.cs +++ b/SafeExamBrowser.Core/Behaviour/ShutdownController.cs @@ -28,13 +28,14 @@ namespace SafeExamBrowser.Core.Behaviour private ISplashScreen splashScreen; private IText text; private IUiElementFactory uiFactory; + private IWorkingArea workingArea; private IEnumerable ShutdownOperations { get { yield return StopProcessMonitoring; - yield return RestoreWorkArea; + yield return RestoreWorkingArea; yield return FinalizeApplicationLog; } } @@ -45,7 +46,8 @@ namespace SafeExamBrowser.Core.Behaviour IProcessMonitor processMonitor, ISettings settings, IText text, - IUiElementFactory uiFactory) + IUiElementFactory uiFactory, + IWorkingArea workingArea) { this.logger = logger; this.messageBox = messageBox; @@ -53,6 +55,7 @@ namespace SafeExamBrowser.Core.Behaviour this.settings = settings; this.text = text; this.uiFactory = uiFactory; + this.workingArea = workingArea; } public void FinalizeApplication() @@ -83,11 +86,12 @@ namespace SafeExamBrowser.Core.Behaviour splashScreen.SetMaxProgress(ShutdownOperations.Count()); splashScreen.UpdateText(Key.SplashScreen_ShutdownProcedure); splashScreen.InvokeShow(); + logger.Info("--- Initiating shutdown procedure ---"); } private void StopProcessMonitoring() { - logger.Info("Stopping process monitoring."); + logger.Info("--- Stopping process monitoring ---"); splashScreen.UpdateText(Key.SplashScreen_StopProcessMonitoring); // TODO @@ -95,21 +99,22 @@ namespace SafeExamBrowser.Core.Behaviour processMonitor.StopMonitoringExplorer(); } - private void RestoreWorkArea() + private void RestoreWorkingArea() { - logger.Info("Restoring work area."); - splashScreen.UpdateText(Key.SplashScreen_RestoreWorkArea); + logger.Info("--- Restoring working area ---"); + splashScreen.UpdateText(Key.SplashScreen_RestoreWorkingArea); // TODO - splashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup); - splashScreen.StartBusyIndication(); + workingArea.Reset(); + + splashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup, true); processMonitor.StartExplorerShell(); - splashScreen.StopBusyIndication(); } private void FinalizeApplicationLog() { + logger.Info("--- Application successfully finalized! ---"); logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } } diff --git a/SafeExamBrowser.Core/Behaviour/StartupController.cs b/SafeExamBrowser.Core/Behaviour/StartupController.cs index 5d698945..a842ce3f 100644 --- a/SafeExamBrowser.Core/Behaviour/StartupController.cs +++ b/SafeExamBrowser.Core/Behaviour/StartupController.cs @@ -32,6 +32,7 @@ namespace SafeExamBrowser.Core.Behaviour private ITaskbar taskbar; private IText text; private IUiElementFactory uiFactory; + private IWorkingArea workingArea; private IEnumerable StartupOperations { @@ -42,7 +43,7 @@ namespace SafeExamBrowser.Core.Behaviour yield return EstablishWcfServiceConnection; yield return DeactivateWindowsFeatures; yield return InitializeProcessMonitoring; - yield return InitializeWorkArea; + yield return InitializeWorkingArea; yield return InitializeTaskbar; yield return InitializeBrowser; yield return FinishInitialization; @@ -59,7 +60,8 @@ namespace SafeExamBrowser.Core.Behaviour ISettings settings, ITaskbar taskbar, IText text, - IUiElementFactory uiFactory) + IUiElementFactory uiFactory, + IWorkingArea workingArea) { this.browserController = browserController; this.browserInfo = browserInfo; @@ -71,6 +73,7 @@ namespace SafeExamBrowser.Core.Behaviour this.taskbar = taskbar; this.text = text; this.uiFactory = uiFactory; + this.workingArea = workingArea; } public bool TryInitializeApplication() @@ -109,7 +112,7 @@ namespace SafeExamBrowser.Core.Behaviour logger.Log($"{titleLine}{copyrightLine}{emptyLine}{githubLine}"); logger.Log($"{Environment.NewLine}# Application started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}{Environment.NewLine}"); - logger.Info("Initiating startup procedure."); + logger.Info("--- Initiating startup procedure ---"); } private void InitializeSplashScreen() @@ -142,7 +145,7 @@ namespace SafeExamBrowser.Core.Behaviour private void InitializeProcessMonitoring() { - logger.Info("Initializing process monitoring."); + logger.Info("--- Initializing process monitoring ---"); splashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring); // TODO @@ -150,24 +153,23 @@ namespace SafeExamBrowser.Core.Behaviour processMonitor.StartMonitoringExplorer(); } - private void InitializeWorkArea() + private void InitializeWorkingArea() { - logger.Info("Initializing work area."); - splashScreen.UpdateText(Key.SplashScreen_InitializeWorkArea); + logger.Info("--- Initializing working area ---"); + splashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination, true); + processMonitor.CloseExplorerShell(); // TODO // - Minimizing all open windows // - Emptying clipboard - splashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination); - splashScreen.StartBusyIndication(); - processMonitor.CloseExplorerShell(); - splashScreen.StopBusyIndication(); + splashScreen.UpdateText(Key.SplashScreen_InitializeWorkingArea); + workingArea.InitializeFor(taskbar); } private void InitializeTaskbar() { - logger.Info("Initializing taskbar."); + logger.Info("--- Initializing taskbar ---"); splashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar); // TODO @@ -179,7 +181,7 @@ namespace SafeExamBrowser.Core.Behaviour private void InitializeBrowser() { - logger.Info("Initializing browser."); + logger.Info("--- Initializing browser ---"); splashScreen.UpdateText(Key.SplashScreen_InitializeBrowser); // TODO @@ -192,7 +194,7 @@ namespace SafeExamBrowser.Core.Behaviour private void FinishInitialization() { - logger.Info("Application successfully initialized!"); + logger.Info("--- Application successfully initialized! ---"); splashScreen.InvokeClose(); } } diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml index 6629783b..4046706f 100644 --- a/SafeExamBrowser.Core/I18n/Text.xml +++ b/SafeExamBrowser.Core/I18n/Text.xml @@ -8,8 +8,8 @@ Initializing browser Initializing process monitoring Initializing taskbar - Initializing work area - Restoring work area + Initializing working area + Restoring working area Initiating shutdown procedure Initiating startup procedure Stopping process monitoring diff --git a/SafeExamBrowser.Core/Logging/ModuleLogger.cs b/SafeExamBrowser.Core/Logging/ModuleLogger.cs new file mode 100644 index 00000000..558267d9 --- /dev/null +++ b/SafeExamBrowser.Core/Logging/ModuleLogger.cs @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 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 System.Collections.Generic; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Core.Logging +{ + public class ModuleLogger : ILogger + { + private ILogger logger; + private Type module; + + /// + /// Creates a wrapper around an ILogger that includes information + /// about the specified module when logging messages with a severity. + /// + public ModuleLogger(ILogger logger, Type module) + { + this.logger = logger; + this.module = module; + } + + public void Error(string message) + { + logger.Error(AppendModuleInfo(message)); + } + + public void Error(string message, Exception exception) + { + logger.Error(AppendModuleInfo(message), exception); + } + + public IList GetLog() + { + return logger.GetLog(); + } + + public void Info(string message) + { + logger.Info(AppendModuleInfo(message)); + } + + public void Log(string message) + { + logger.Log(message); + } + + public void Log(ILogContent content) + { + logger.Log(content); + } + + public void Subscribe(ILogObserver observer) + { + logger.Subscribe(observer); + } + + public void Unsubscribe(ILogObserver observer) + { + logger.Unsubscribe(observer); + } + + public void Warn(string message) + { + logger.Warn(AppendModuleInfo(message)); + } + + private string AppendModuleInfo(string message) + { + return $"[{module.Name}] {message}"; + } + } +} diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index a33eced1..db22d8c5 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -40,9 +40,6 @@ - - - @@ -51,6 +48,7 @@ + diff --git a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs index 2f50e75c..dc7cde93 100644 --- a/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs +++ b/SafeExamBrowser.Monitoring/Processes/ProcessMonitor.cs @@ -13,6 +13,7 @@ using System.Linq; using System.Threading; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; +using SafeExamBrowser.WindowsApi; namespace SafeExamBrowser.Monitoring.Processes { @@ -28,9 +29,9 @@ namespace SafeExamBrowser.Monitoring.Processes public void StartExplorerShell() { var process = new Process(); - var explorerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "explorer.exe"); + var explorerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"); - Log("Restarting explorer shell..."); + logger.Info("Restarting explorer shell..."); process.StartInfo.CreateNoWindow = true; process.StartInfo.FileName = explorerPath; @@ -42,7 +43,7 @@ namespace SafeExamBrowser.Monitoring.Processes } process.Refresh(); - Log($"Explorer shell successfully started with PID = {process.Id}."); + logger.Info($"Explorer shell successfully started with PID = {process.Id}."); process.Close(); } @@ -64,7 +65,7 @@ namespace SafeExamBrowser.Monitoring.Processes if (shellProcess != null) { - Log($"Found explorer shell processes with PID = {processId}. Sending close message..."); + logger.Info($"Found explorer shell processes with PID = {processId}. Sending close message..."); User32.PostCloseMessageToShell(); while (!shellProcess.HasExited) @@ -73,17 +74,12 @@ namespace SafeExamBrowser.Monitoring.Processes Thread.Sleep(20); } - Log($"Successfully terminated explorer shell process with PID = {processId}."); + logger.Info($"Successfully terminated explorer shell process with PID = {processId}."); } else { - Log("The explorer shell seems to already be terminated. Skipping this step..."); + logger.Info("The explorer shell seems to already be terminated. Skipping this step..."); } } - - private void Log(string message) - { - logger.Info($"[{nameof(ProcessMonitor)}] {message}"); - } } } diff --git a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj index d710e122..52b83b7b 100644 --- a/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj +++ b/SafeExamBrowser.Monitoring/SafeExamBrowser.Monitoring.csproj @@ -42,13 +42,16 @@ - {47da5933-bef8-4729-94e6-abde2db12262} SafeExamBrowser.Contracts + + {73724659-4150-4792-a94e-42f5f3c1b696} + SafeExamBrowser.WindowsApi + diff --git a/SafeExamBrowser.Monitoring/User32.cs b/SafeExamBrowser.Monitoring/User32.cs deleted file mode 100644 index eca68cd4..00000000 --- a/SafeExamBrowser.Monitoring/User32.cs +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2017 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 System.ComponentModel; -using System.Runtime.InteropServices; - -namespace SafeExamBrowser.Monitoring -{ - /// - /// Provides access to the native Windows API exposed by user32.dll. - /// - internal static class User32 - { - internal static IntPtr GetShellWindowHandle() - { - return FindWindow("Shell_TrayWnd", null); - } - - internal static uint GetShellProcessId() - { - var handle = FindWindow("Shell_TrayWnd", null); - var threadId = GetWindowThreadProcessId(handle, out uint processId); - - return processId; - } - - /// - /// The close message 0x5B4 posted to the shell is undocumented and not officially supported: - /// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965 - /// - internal static void PostCloseMessageToShell() - { - var taskbarHandle = FindWindow("Shell_TrayWnd", null); - var success = PostMessage(taskbarHandle, 0x5B4, IntPtr.Zero, IntPtr.Zero); - - if (!success) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - } - - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); - - [DllImport("user32.dll", SetLastError = true)] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [return: MarshalAs(UnmanagedType.Bool)] - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - } -} diff --git a/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml b/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml index 277fccb0..67808356 100644 --- a/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml +++ b/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml @@ -27,10 +27,10 @@ - + - + diff --git a/SafeExamBrowser.UserInterface/Controls/ApplicationInstanceButton.xaml b/SafeExamBrowser.UserInterface/Controls/ApplicationInstanceButton.xaml index 223aadbf..58d24d64 100644 --- a/SafeExamBrowser.UserInterface/Controls/ApplicationInstanceButton.xaml +++ b/SafeExamBrowser.UserInterface/Controls/ApplicationInstanceButton.xaml @@ -14,10 +14,10 @@ - + - + diff --git a/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml b/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml index 75d33930..3a292f13 100644 --- a/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml +++ b/SafeExamBrowser.UserInterface/Controls/QuitButton.xaml @@ -15,10 +15,10 @@ - + - + diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs index e6c03749..b78b85a3 100644 --- a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs @@ -45,24 +45,20 @@ namespace SafeExamBrowser.UserInterface model.MaxProgress = max; } - public void StartBusyIndication() - { - model.StartBusyIndication(); - } - - public void StopBusyIndication() - { - model.StopBusyIndication(); - } - public void UpdateProgress(int amount = 1) { model.CurrentProgress += amount; } - public void UpdateText(Key key) + public void UpdateText(Key key, bool showBusyIndication = false) { + model.StopBusyIndication(); model.Status = text.Get(key); + + if (showBusyIndication) + { + model.StartBusyIndication(); + } } private void InitializeSplashScreen() diff --git a/SafeExamBrowser.UserInterface/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface/Taskbar.xaml.cs index 6ed2a950..3ece46dd 100644 --- a/SafeExamBrowser.UserInterface/Taskbar.xaml.cs +++ b/SafeExamBrowser.UserInterface/Taskbar.xaml.cs @@ -8,6 +8,8 @@ using System.Windows; using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; using SafeExamBrowser.Contracts.UserInterface; namespace SafeExamBrowser.UserInterface @@ -17,6 +19,15 @@ namespace SafeExamBrowser.UserInterface public Taskbar() { InitializeComponent(); + + Loaded += Taskbar_Loaded; + } + + private void Taskbar_Loaded(object sender, RoutedEventArgs e) + { + Width = SystemParameters.WorkArea.Right; + Left = SystemParameters.WorkArea.Right - Width; + Top = SystemParameters.WorkArea.Bottom; } public void AddButton(ITaskbarButton button) @@ -35,16 +46,28 @@ namespace SafeExamBrowser.UserInterface } } - public void SetPosition(int x, int y) + public int GetAbsoluteHeight() { - Left = x; - Top = y; - } + // WPF works with device-independent pixels. The following code is required + // to get the real height of the taskbar (in absolute, device-specific pixels). + // Source: https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels - public void SetSize(int width, int height) - { - Width = width; - Height = height; + Matrix transformToDevice; + var source = PresentationSource.FromVisual(this); + + if (source != null) + { + transformToDevice = source.CompositionTarget.TransformToDevice; + } + else + { + using (var newSource = new HwndSource(new HwndSourceParameters())) + { + transformToDevice = newSource.CompositionTarget.TransformToDevice; + } + } + + return (int) transformToDevice.Transform((Vector) new Size(Width, Height)).Y; } private void ApplicationScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) diff --git a/SafeExamBrowser.WindowsApi/Constants/SPI.cs b/SafeExamBrowser.WindowsApi/Constants/SPI.cs new file mode 100644 index 00000000..a64bd0d1 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Constants/SPI.cs @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017 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.WindowsApi.Constants +{ + /// + /// See http://www.pinvoke.net/default.aspx/Enums/SPI.html?diff=y. + /// + internal enum SPI : uint + { + /// + /// Sets the size of the work area. The work area is the portion of the screen not obscured by the system taskbar + /// or by application desktop toolbars. The pvParam parameter is a pointer to a RECT structure that specifies the + /// new work area rectangle, expressed in virtual screen coordinates. In a system with multiple display monitors, + /// the function sets the work area of the monitor that contains the specified rectangle. + /// + SETWORKAREA = 0x002F, + + /// + /// Retrieves the size of the work area on the primary display monitor. The work area is the portion of the screen + /// not obscured by the system taskbar or by application desktop toolbars. The pvParam parameter must point to a + /// RECT structure that receives the coordinates of the work area, expressed in virtual screen coordinates. To get + /// the work area of a monitor other than the primary display monitor, call the GetMonitorInfo function. + /// + GETWORKAREA = 0x0030, + } +} diff --git a/SafeExamBrowser.WindowsApi/Constants/SPIF.cs b/SafeExamBrowser.WindowsApi/Constants/SPIF.cs new file mode 100644 index 00000000..f62887d5 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Constants/SPIF.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 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; + +namespace SafeExamBrowser.WindowsApi.Constants +{ + /// + /// See http://www.pinvoke.net/default.aspx/Enums/SPIF.html. + /// + [Flags] + internal enum SPIF + { + NONE = 0x00, + + /// + /// Writes the new system-wide parameter setting to the user profile. + /// + UPDATEINIFILE = 0x01, + + /// + /// Broadcasts the WM_SETTINGCHANGE message after updating the user profile. + /// + SENDCHANGE = 0x02, + + /// + /// Performs UPDATEINIFILE and SENDCHANGE. + /// + UPDATEANDCHANGE = 0x03 + } +} diff --git a/SafeExamBrowser.WindowsApi/Properties/AssemblyInfo.cs b/SafeExamBrowser.WindowsApi/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..9f4bb183 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SafeExamBrowser.WindowsApi")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SafeExamBrowser.WindowsApi")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("73724659-4150-4792-a94e-42f5f3c1b696")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj new file mode 100644 index 00000000..d7a99b2d --- /dev/null +++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj @@ -0,0 +1,50 @@ + + + + + Debug + AnyCPU + {73724659-4150-4792-A94E-42F5F3C1B696} + Library + Properties + SafeExamBrowser.WindowsApi + SafeExamBrowser.WindowsApi + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SafeExamBrowser.WindowsApi/Types/RECT.cs b/SafeExamBrowser.WindowsApi/Types/RECT.cs new file mode 100644 index 00000000..44077323 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Types/RECT.cs @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2017 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.Runtime.InteropServices; + +namespace SafeExamBrowser.WindowsApi.Types +{ + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx. + /// + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } +} diff --git a/SafeExamBrowser.WindowsApi/User32.cs b/SafeExamBrowser.WindowsApi/User32.cs new file mode 100644 index 00000000..f8e71093 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/User32.cs @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017 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 System.ComponentModel; +using System.Runtime.InteropServices; +using SafeExamBrowser.WindowsApi.Constants; +using SafeExamBrowser.WindowsApi.Types; + +namespace SafeExamBrowser.WindowsApi +{ + /// + /// Provides access to the native Windows API exposed by user32.dll. + /// + public static class User32 + { + /// + /// Retrieves a window handle to the Windows taskbar. Returns IntPtr.Zero + /// if the taskbar could not be found (i.e. if it isn't running). + /// + public static IntPtr GetShellWindowHandle() + { + return FindWindow("Shell_TrayWnd", null); + } + + /// + /// Retrieves the process ID of the main Windows explorer instance controlling + /// desktop and taskbar or 0, if the process isn't running. + /// + /// + public static uint GetShellProcessId() + { + var handle = FindWindow("Shell_TrayWnd", null); + var threadId = GetWindowThreadProcessId(handle, out uint processId); + + return processId; + } + + /// + /// Retrieves the currently configured working area of the primary screen. + /// + /// + /// If the working area could not be retrieved. + /// + public static RECT GetWorkingArea() + { + var workingArea = new RECT(); + var success = SystemParametersInfo(SPI.GETWORKAREA, 0, ref workingArea, SPIF.NONE); + + if (!success) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + return workingArea; + } + + /// + /// Instructs the main Windows explorer process to shut down. + /// + /// + /// If the messge could not be successfully posted. Does not apply if the process isn't running! + /// + /// + /// The close message 0x5B4 posted to the shell is undocumented and not officially supported: + /// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965 + /// + public static void PostCloseMessageToShell() + { + var taskbarHandle = FindWindow("Shell_TrayWnd", null); + var success = PostMessage(taskbarHandle, 0x5B4, IntPtr.Zero, IntPtr.Zero); + + if (!success) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + + /// + /// Sets the working area of the primary screen according to the given dimensions. + /// + /// + /// If the working area could not be set. + /// + public static void SetWorkingArea(RECT workingArea) + { + var success = SystemParametersInfo(SPI.SETWORKAREA, 0, ref workingArea, SPIF.UPDATEANDCHANGE); + + if (!success) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll", SetLastError = true)] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool SystemParametersInfo(SPI uiAction, uint uiParam, ref RECT pvParam, SPIF fWinIni); + } +} diff --git a/SafeExamBrowser.sln b/SafeExamBrowser.sln index 70768e7c..1ad47d35 100644 --- a/SafeExamBrowser.sln +++ b/SafeExamBrowser.sln @@ -23,6 +23,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Browser", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Monitoring", "SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj", "{EF563531-4EB5-44B9-A5EC-D6D6F204469B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Configuration", "SafeExamBrowser.Configuration\SafeExamBrowser.Configuration.csproj", "{C388C4DD-A159-457D-AF92-89F7AD185109}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.WindowsApi", "SafeExamBrowser.WindowsApi\SafeExamBrowser.WindowsApi.csproj", "{73724659-4150-4792-A94E-42F5F3C1B696}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +61,14 @@ Global {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Debug|Any CPU.Build.0 = Debug|Any CPU {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF563531-4EB5-44B9-A5EC-D6D6F204469B}.Release|Any CPU.Build.0 = Release|Any CPU + {C388C4DD-A159-457D-AF92-89F7AD185109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C388C4DD-A159-457D-AF92-89F7AD185109}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C388C4DD-A159-457D-AF92-89F7AD185109}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C388C4DD-A159-457D-AF92-89F7AD185109}.Release|Any CPU.Build.0 = Release|Any CPU + {73724659-4150-4792-A94E-42F5F3C1B696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73724659-4150-4792-A94E-42F5F3C1B696}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73724659-4150-4792-A94E-42F5F3C1B696}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73724659-4150-4792-A94E-42F5F3C1B696}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs index 7b7af9fe..6b0cea9a 100644 --- a/SafeExamBrowser/CompositionRoot.cs +++ b/SafeExamBrowser/CompositionRoot.cs @@ -7,6 +7,7 @@ */ using SafeExamBrowser.Browser; +using SafeExamBrowser.Configuration; using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.I18n; @@ -14,7 +15,6 @@ using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Core.Behaviour; -using SafeExamBrowser.Core.Configuration; using SafeExamBrowser.Core.I18n; using SafeExamBrowser.Core.Logging; using SafeExamBrowser.Monitoring.Processes; @@ -32,8 +32,9 @@ namespace SafeExamBrowser private IProcessMonitor processMonitor; private ISettings settings; private IText text; - private IUiElementFactory uiFactory; private ITextResource textResource; + private IUiElementFactory uiFactory; + private IWorkingArea workingArea; public IShutdownController ShutdownController { get; private set; } public IStartupController StartupController { get; private set; } @@ -54,9 +55,10 @@ namespace SafeExamBrowser text = new Text(textResource); aboutInfo = new AboutNotificationInfo(text); - processMonitor = new ProcessMonitor(logger); - ShutdownController = new ShutdownController(logger, messageBox, processMonitor, settings, text, uiFactory); - StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, processMonitor, settings, Taskbar, text, uiFactory); + processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor))); + workingArea = new WorkingArea(new ModuleLogger(logger, typeof(WorkingArea))); + ShutdownController = new ShutdownController(logger, messageBox, processMonitor, settings, text, uiFactory, workingArea); + StartupController = new StartupController(browserController, browserInfo, logger, messageBox, aboutInfo, processMonitor, settings, Taskbar, text, uiFactory, workingArea); } } } diff --git a/SafeExamBrowser/SafeExamBrowser.csproj b/SafeExamBrowser/SafeExamBrowser.csproj index 044aa236..b65858a8 100644 --- a/SafeExamBrowser/SafeExamBrowser.csproj +++ b/SafeExamBrowser/SafeExamBrowser.csproj @@ -103,6 +103,10 @@ {04E653F1-98E6-4E34-9DD7-7F2BC1A8B767} SafeExamBrowser.Browser + + {c388c4dd-a159-457d-af92-89f7ad185109} + SafeExamBrowser.Configuration + {47DA5933-BEF8-4729-94E6-ABDE2DB12262} SafeExamBrowser.Contracts