Implemented basic handling of working area.

This commit is contained in:
Damian Büchel 2017-07-20 14:16:47 +02:00
parent a35fe0811f
commit eb6fbf49b8
33 changed files with 678 additions and 152 deletions

View file

@ -9,7 +9,7 @@
using System;
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Core.Configuration
namespace SafeExamBrowser.Configuration
{
public class AboutNotificationIconResource : IIconResource
{

View file

@ -9,7 +9,7 @@
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
namespace SafeExamBrowser.Core.Configuration
namespace SafeExamBrowser.Configuration
{
public class AboutNotificationInfo : INotificationInfo
{

View file

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

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C388C4DD-A159-457D-AF92-89F7AD185109}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Configuration</RootNamespace>
<AssemblyName>SafeExamBrowser.Configuration</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AboutNotificationIconResource.cs" />
<Compile Include="AboutNotificationInfo.cs" />
<Compile Include="Settings.cs" />
<Compile Include="WorkingArea.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Contracts\SafeExamBrowser.Contracts.csproj">
<Project>{47DA5933-BEF8-4729-94E6-ABDE2DB12262}</Project>
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi\SafeExamBrowser.WindowsApi.csproj">
<Project>{73724659-4150-4792-a94e-42f5f3c1b696}</Project>
<Name>SafeExamBrowser.WindowsApi</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

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

View file

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

View file

@ -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
{
/// <summary>
/// Sets the Windows working area to accommodate to the taskbar's dimensions.
/// </summary>
void InitializeFor(ITaskbar taskbar);
/// <summary>
/// Resets the Windows working area to its previous (initial) state.
/// </summary>
void Reset();
}
}

View file

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

View file

@ -49,6 +49,7 @@
<Compile Include="Configuration\ISettings.cs" />
<Compile Include="Behaviour\IShutdownController.cs" />
<Compile Include="Behaviour\IStartupController.cs" />
<Compile Include="Configuration\IWorkingArea.cs" />
<Compile Include="I18n\IText.cs" />
<Compile Include="I18n\Key.cs" />
<Compile Include="Logging\ILogContent.cs" />

View file

@ -27,24 +27,15 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
void SetMaxProgress(int max);
/// <summary>
/// Starts an animation indicating the user that something is going on.
/// </summary>
void StartBusyIndication();
/// <summary>
/// Stops the busy animation, if it was running.
/// </summary>
void StopBusyIndication();
/// <summary>
/// Updates the progress bar of the splash screen according to the specified amount.
/// </summary>
void UpdateProgress(int amount = 1);
/// <summary>
/// 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.
/// </summary>
void UpdateText(Key key);
void UpdateText(Key key, bool showBusyIndication = false);
}
}

View file

@ -22,13 +22,8 @@ namespace SafeExamBrowser.Contracts.UserInterface
void AddNotification(ITaskbarNotification button);
/// <summary>
/// Moves the taskbar to the given location on the screen.
/// Returns the absolute height of the taskbar (i.e. in physical pixels).
/// </summary>
void SetPosition(int x, int y);
/// <summary>
/// Sets the size of the taskbar.
/// </summary>
void SetSize(int width, int height);
int GetAbsoluteHeight();
}
}

View file

@ -28,13 +28,14 @@ namespace SafeExamBrowser.Core.Behaviour
private ISplashScreen splashScreen;
private IText text;
private IUiElementFactory uiFactory;
private IWorkingArea workingArea;
private IEnumerable<Action> 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")}");
}
}

View file

@ -32,6 +32,7 @@ namespace SafeExamBrowser.Core.Behaviour
private ITaskbar taskbar;
private IText text;
private IUiElementFactory uiFactory;
private IWorkingArea workingArea;
private IEnumerable<Action> 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();
}
}

View file

@ -8,8 +8,8 @@
<SplashScreen_InitializeBrowser>Initializing browser</SplashScreen_InitializeBrowser>
<SplashScreen_InitializeProcessMonitoring>Initializing process monitoring</SplashScreen_InitializeProcessMonitoring>
<SplashScreen_InitializeTaskbar>Initializing taskbar</SplashScreen_InitializeTaskbar>
<SplashScreen_InitializeWorkArea>Initializing work area</SplashScreen_InitializeWorkArea>
<SplashScreen_RestoreWorkArea>Restoring work area</SplashScreen_RestoreWorkArea>
<SplashScreen_InitializeWorkingArea>Initializing working area</SplashScreen_InitializeWorkingArea>
<SplashScreen_RestoreWorkingArea>Restoring working area</SplashScreen_RestoreWorkingArea>
<SplashScreen_ShutdownProcedure>Initiating shutdown procedure</SplashScreen_ShutdownProcedure>
<SplashScreen_StartupProcedure>Initiating startup procedure</SplashScreen_StartupProcedure>
<SplashScreen_StopProcessMonitoring>Stopping process monitoring</SplashScreen_StopProcessMonitoring>

View file

@ -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;
/// <summary>
/// Creates a wrapper around an <c>ILogger</c> that includes information
/// about the specified module when logging messages with a severity.
/// </summary>
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<ILogContent> 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}";
}
}
}

View file

@ -40,9 +40,6 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration\AboutNotificationIconResource.cs" />
<Compile Include="Configuration\AboutNotificationInfo.cs" />
<Compile Include="Configuration\Settings.cs" />
<Compile Include="Behaviour\ShutdownController.cs" />
<Compile Include="Behaviour\StartupController.cs" />
<Compile Include="Logging\LogFileWriter.cs" />
@ -51,6 +48,7 @@
<Compile Include="I18n\XmlTextResource.cs" />
<Compile Include="Logging\Logger.cs" />
<Compile Include="Logging\LogText.cs" />
<Compile Include="Logging\ModuleLogger.cs" />
<Compile Include="Logging\ThreadInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View file

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

View file

@ -42,13 +42,16 @@
<ItemGroup>
<Compile Include="Processes\ProcessMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="User32.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Contracts\SafeExamBrowser.Contracts.csproj">
<Project>{47da5933-bef8-4729-94e6-abde2db12262}</Project>
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi\SafeExamBrowser.WindowsApi.csproj">
<Project>{73724659-4150-4792-a94e-42f5f3c1b696}</Project>
<Name>SafeExamBrowser.WindowsApi</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Keyboard\" />

View file

@ -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
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>user32.dll</c>.
/// </summary>
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;
}
/// <remarks>
/// The close message <c>0x5B4</c> posted to the shell is undocumented and not officially supported:
/// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965
/// </remarks>
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);
}
}

View file

@ -27,10 +27,10 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="#10FFFFFF" />
<Setter TargetName="ButtonContent" Property="Background" Value="#2AFFFFFF" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="#2AFFFFFF" />
<Setter TargetName="ButtonContent" Property="Background" Value="#10FFFFFF" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

View file

@ -14,10 +14,10 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="#10FFFFFF" />
<Setter TargetName="ButtonContent" Property="Background" Value="#2AFFFFFF" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="#2AFFFFFF" />
<Setter TargetName="ButtonContent" Property="Background" Value="#10FFFFFF" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

View file

@ -15,10 +15,10 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="#40FF0000" />
<Setter TargetName="ButtonContent" Property="Background" Value="#50FF0000" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="#60FF0000" />
<Setter TargetName="ButtonContent" Property="Background" Value="#40FF0000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

View file

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

View file

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

View file

@ -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
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Enums/SPI.html?diff=y.
/// </remarks>
internal enum SPI : uint
{
/// <summary>
/// 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.
/// </summary>
SETWORKAREA = 0x002F,
/// <summary>
/// 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.
/// </summary>
GETWORKAREA = 0x0030,
}
}

View file

@ -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
{
/// <remarks>
/// See http://www.pinvoke.net/default.aspx/Enums/SPIF.html.
/// </remarks>
[Flags]
internal enum SPIF
{
NONE = 0x00,
/// <summary>
/// Writes the new system-wide parameter setting to the user profile.
/// </summary>
UPDATEINIFILE = 0x01,
/// <summary>
/// Broadcasts the WM_SETTINGCHANGE message after updating the user profile.
/// </summary>
SENDCHANGE = 0x02,
/// <summary>
/// Performs UPDATEINIFILE and SENDCHANGE.
/// </summary>
UPDATEANDCHANGE = 0x03
}
}

View file

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

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{73724659-4150-4792-A94E-42F5F3C1B696}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.WindowsApi</RootNamespace>
<AssemblyName>SafeExamBrowser.WindowsApi</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Constants\SPI.cs" />
<Compile Include="Constants\SPIF.cs" />
<Compile Include="Types\RECT.cs" />
<Compile Include="User32.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -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
{
/// <summary>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}

View file

@ -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
{
/// <summary>
/// Provides access to the native Windows API exposed by <c>user32.dll</c>.
/// </summary>
public static class User32
{
/// <summary>
/// Retrieves a window handle to the Windows taskbar. Returns <c>IntPtr.Zero</c>
/// if the taskbar could not be found (i.e. if it isn't running).
/// </summary>
public static IntPtr GetShellWindowHandle()
{
return FindWindow("Shell_TrayWnd", null);
}
/// <summary>
/// Retrieves the process ID of the main Windows explorer instance controlling
/// desktop and taskbar or <c>0</c>, if the process isn't running.
/// </summary>
/// <returns></returns>
public static uint GetShellProcessId()
{
var handle = FindWindow("Shell_TrayWnd", null);
var threadId = GetWindowThreadProcessId(handle, out uint processId);
return processId;
}
/// <summary>
/// Retrieves the currently configured working area of the primary screen.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the working area could not be retrieved.
/// </exception>
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;
}
/// <summary>
/// Instructs the main Windows explorer process to shut down.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the messge could not be successfully posted. Does not apply if the process isn't running!
/// </exception>
/// <remarks>
/// The close message <c>0x5B4</c> posted to the shell is undocumented and not officially supported:
/// https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically/5705965#5705965
/// </remarks>
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());
}
}
/// <summary>
/// Sets the working area of the primary screen according to the given dimensions.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">
/// If the working area could not be set.
/// </exception>
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);
}
}

View file

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

View file

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

View file

@ -103,6 +103,10 @@
<Project>{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}</Project>
<Name>SafeExamBrowser.Browser</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration\SafeExamBrowser.Configuration.csproj">
<Project>{c388c4dd-a159-457d-af92-89f7ad185109}</Project>
<Name>SafeExamBrowser.Configuration</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Contracts\SafeExamBrowser.Contracts.csproj">
<Project>{47DA5933-BEF8-4729-94E6-ABDE2DB12262}</Project>
<Name>SafeExamBrowser.Contracts</Name>