SEBWIN-219: Continued implementing kiosk operation and realized that further changes to startup/runtime/shutdown-mechanism are necessary...

This commit is contained in:
dbuechel 2018-01-30 14:41:36 +01:00
parent b22093e6a2
commit 18b8f66300
30 changed files with 505 additions and 82 deletions

View file

@ -15,6 +15,7 @@ namespace SafeExamBrowser.Configuration.Settings
internal class Settings : ISettings internal class Settings : ISettings
{ {
public ConfigurationMode ConfigurationMode { get; set; } public ConfigurationMode ConfigurationMode { get; set; }
public KioskMode KioskMode { get; set; }
public ServicePolicy ServicePolicy { get; set; } public ServicePolicy ServicePolicy { get; set; }
public IBrowserSettings Browser { get; set; } public IBrowserSettings Browser { get; set; }

View file

@ -14,5 +14,10 @@ namespace SafeExamBrowser.Contracts.Behaviour
/// Reverts any changes performed during the startup or runtime and releases all used resources. /// Reverts any changes performed during the startup or runtime and releases all used resources.
/// </summary> /// </summary>
void FinalizeApplication(); void FinalizeApplication();
/// <summary>
/// Initializes a new session and starts performing the runtime logic / event handling.
/// </summary>
void StartSession();
} }
} }

View file

@ -25,13 +25,18 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
/// </summary> /// </summary>
IKeyboardSettings Keyboard { get; } IKeyboardSettings Keyboard { get; }
/// <summary>
/// The kiosk mode which determines how the computer is locked down.
/// </summary>
KioskMode KioskMode { get; }
/// <summary> /// <summary>
/// All mouse-related settings. /// All mouse-related settings.
/// </summary> /// </summary>
IMouseSettings Mouse { get; } IMouseSettings Mouse { get; }
/// <summary> /// <summary>
/// The active service policy. /// The active policy for the service component.
/// </summary> /// </summary>
ServicePolicy ServicePolicy { get; } ServicePolicy ServicePolicy { get; }

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2018 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.Contracts.Configuration.Settings
{
public enum KioskMode
{
/// <summary>
/// No kiosk mode - should only be used for testing / debugging.
/// </summary>
None,
/// <summary>
/// Creates a new desktop and runs the client application on it, without modifying the default desktop.
/// </summary>
CreateNewDesktop,
/// <summary>
/// Terminates the Windows explorer shell and runs the client application on the default desktop.
/// </summary>
DisableExplorerShell
}
}

View file

@ -26,16 +26,20 @@ namespace SafeExamBrowser.Contracts.I18n
Notification_AboutTooltip, Notification_AboutTooltip,
Notification_LogTooltip, Notification_LogTooltip,
RuntimeWindow_ApplicationRunning, RuntimeWindow_ApplicationRunning,
RuntimeWindow_StartSession,
RuntimeWindow_StopSession,
SplashScreen_CloseServiceConnection, SplashScreen_CloseServiceConnection,
SplashScreen_EmptyClipboard, SplashScreen_EmptyClipboard,
SplashScreen_InitializeBrowser, SplashScreen_InitializeBrowser,
SplashScreen_InitializeConfiguration, SplashScreen_InitializeConfiguration,
SplashScreen_InitializeKioskMode,
SplashScreen_InitializeProcessMonitoring, SplashScreen_InitializeProcessMonitoring,
SplashScreen_InitializeServiceConnection, SplashScreen_InitializeServiceConnection,
SplashScreen_InitializeTaskbar, SplashScreen_InitializeTaskbar,
SplashScreen_InitializeWindowMonitoring, SplashScreen_InitializeWindowMonitoring,
SplashScreen_InitializeWorkingArea, SplashScreen_InitializeWorkingArea,
SplashScreen_RestoreWorkingArea, SplashScreen_RestoreWorkingArea,
SplashScreen_RevertKioskMode,
SplashScreen_ShutdownProcedure, SplashScreen_ShutdownProcedure,
SplashScreen_StartEventHandling, SplashScreen_StartEventHandling,
SplashScreen_StartKeyboardInterception, SplashScreen_StartKeyboardInterception,

View file

@ -81,6 +81,7 @@
<Compile Include="Behaviour\IStartupController.cs" /> <Compile Include="Behaviour\IStartupController.cs" />
<Compile Include="Configuration\Settings\ISettingsRepository.cs" /> <Compile Include="Configuration\Settings\ISettingsRepository.cs" />
<Compile Include="Configuration\Settings\ITaskbarSettings.cs" /> <Compile Include="Configuration\Settings\ITaskbarSettings.cs" />
<Compile Include="Configuration\Settings\KioskMode.cs" />
<Compile Include="Configuration\Settings\ServicePolicy.cs" /> <Compile Include="Configuration\Settings\ServicePolicy.cs" />
<Compile Include="I18n\IText.cs" /> <Compile Include="I18n\IText.cs" />
<Compile Include="I18n\TextKey.cs" /> <Compile Include="I18n\TextKey.cs" />

View file

@ -7,15 +7,16 @@
*/ */
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Contracts.UserInterface namespace SafeExamBrowser.Contracts.UserInterface
{ {
public interface IRuntimeWindow : IWindow public interface IRuntimeWindow : ILogObserver, IWindow
{ {
/// <summary> /// <summary>
/// Updates the status text of the runtime window. If the busy flag is set, /// Updates the status text of the runtime window. If the busy flag is set,
/// the window will show an animation to indicate a long-running operation. /// the window will show an animation to indicate a long-running operation.
/// </summary> /// </summary>
void UpdateStatus(TextKey key); void UpdateStatus(TextKey key, bool showBusyIndication = false);
} }
} }

View file

@ -10,18 +10,8 @@ using SafeExamBrowser.Contracts.I18n;
namespace SafeExamBrowser.Contracts.UserInterface namespace SafeExamBrowser.Contracts.UserInterface
{ {
public interface ISplashScreen public interface ISplashScreen : IWindow
{ {
/// <summary>
/// Closes the splash screen on its own thread.
/// </summary>
void InvokeClose();
/// <summary>
/// Shows the splash screen on its own thread.
/// </summary>
void InvokeShow();
/// <summary> /// <summary>
/// Updates the progress bar of the splash screen according to the specified amount. /// Updates the progress bar of the splash screen according to the specified amount.
/// </summary> /// </summary>

View file

@ -51,6 +51,12 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary> /// </summary>
ISystemPowerSupplyControl CreatePowerSupplyControl(); ISystemPowerSupplyControl CreatePowerSupplyControl();
/// <summary>
/// Creates a new runtime window which runs on its own thread.
/// </summary>
/// <returns></returns>
IRuntimeWindow CreateRuntimeWindow(IRuntimeInfo runtimeInfo, IText text);
/// <summary> /// <summary>
/// Creates a new splash screen which runs on its own thread. /// Creates a new splash screen which runs on its own thread.
/// </summary> /// </summary>

View file

@ -27,6 +27,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary> /// </summary>
void Close(); void Close();
/// <summary>
/// Hides the window.
/// </summary>
void Hide();
/// <summary> /// <summary>
/// Shows the window to the user. /// Shows the window to the user.
/// </summary> /// </summary>

View file

@ -72,7 +72,7 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text); splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text);
splashScreen.SetIndeterminate(); splashScreen.SetIndeterminate();
splashScreen.UpdateText(TextKey.SplashScreen_ShutdownProcedure); splashScreen.UpdateText(TextKey.SplashScreen_ShutdownProcedure);
splashScreen.InvokeShow(); splashScreen.Show();
} }
private void LogAndShowException(Exception e) private void LogAndShowException(Exception e)
@ -92,7 +92,7 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("--- Shutdown procedure failed! ---"); logger.Info("--- Shutdown procedure failed! ---");
} }
splashScreen?.InvokeClose(); splashScreen?.Close();
} }
} }
} }

View file

@ -117,7 +117,7 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text); splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text);
splashScreen.SetMaxProgress(operationCount); splashScreen.SetMaxProgress(operationCount);
splashScreen.UpdateText(TextKey.SplashScreen_StartupProcedure); splashScreen.UpdateText(TextKey.SplashScreen_StartupProcedure);
splashScreen.InvokeShow(); splashScreen.Show();
} }
private void LogAndShowException(Exception e) private void LogAndShowException(Exception e)
@ -139,7 +139,7 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("--- Startup procedure aborted! ---"); logger.Info("--- Startup procedure aborted! ---");
} }
splashScreen?.InvokeClose(); splashScreen?.Close();
} }
} }
} }

View file

@ -33,6 +33,12 @@
<Entry key="RuntimeWindow_ApplicationRunning"> <Entry key="RuntimeWindow_ApplicationRunning">
The application is running. The application is running.
</Entry> </Entry>
<Entry key="RuntimeWindow_StartSession">
Starting new session
</Entry>
<Entry key="RuntimeWindow_StopSession">
Stopping current session
</Entry>
<Entry key="SplashScreen_CloseServiceConnection"> <Entry key="SplashScreen_CloseServiceConnection">
Closing service connection Closing service connection
</Entry> </Entry>
@ -45,6 +51,9 @@
<Entry key="SplashScreen_InitializeConfiguration"> <Entry key="SplashScreen_InitializeConfiguration">
Initializing application configuration Initializing application configuration
</Entry> </Entry>
<Entry key="SplashScreen_InitializeKioskMode">
Initializing kiosk mode
</Entry>
<Entry key="SplashScreen_InitializeProcessMonitoring"> <Entry key="SplashScreen_InitializeProcessMonitoring">
Initializing process monitoring Initializing process monitoring
</Entry> </Entry>
@ -63,6 +72,9 @@
<Entry key="SplashScreen_RestoreWorkingArea"> <Entry key="SplashScreen_RestoreWorkingArea">
Restoring working area Restoring working area
</Entry> </Entry>
<Entry key="SplashScreen_RevertKioskMode">
Reverting kiosk mode
</Entry>
<Entry key="SplashScreen_ShutdownProcedure"> <Entry key="SplashScreen_ShutdownProcedure">
Initiating shutdown procedure Initiating shutdown procedure
</Entry> </Entry>

View file

@ -7,7 +7,6 @@
*/ */
using System; using System;
using System.ComponentModel;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
@ -56,6 +55,8 @@ namespace SafeExamBrowser.Runtime
{ {
base.OnStartup(e); base.OnStartup(e);
ShutdownMode = ShutdownMode.OnExplicitShutdown;
instances.BuildObjectGraph(); instances.BuildObjectGraph();
instances.LogStartupInformation(); instances.LogStartupInformation();
@ -63,9 +64,7 @@ namespace SafeExamBrowser.Runtime
if (success) if (success)
{ {
MainWindow = instances.RuntimeWindow; instances.RuntimeController.StartSession();
MainWindow.Closing += MainWindow_Closing;
MainWindow.Show();
} }
else else
{ {
@ -75,15 +74,10 @@ namespace SafeExamBrowser.Runtime
protected override void OnExit(ExitEventArgs e) protected override void OnExit(ExitEventArgs e)
{ {
instances.RuntimeController.FinalizeApplication();
instances.LogShutdownInformation(); instances.LogShutdownInformation();
base.OnExit(e); base.OnExit(e);
} }
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
MainWindow.Hide();
instances.RuntimeController.FinalizeApplication();
}
} }
} }

View file

@ -6,25 +6,79 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal class KioskModeOperation : IOperation internal class KioskModeOperation : IOperation
{ {
private ILogger logger;
private ISettingsRepository settingsRepository;
private KioskMode kioskMode;
public bool AbortStartup { get; private set; } public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; } public ISplashScreen SplashScreen { private get; set; }
public KioskModeOperation(ILogger logger, ISettingsRepository settingsRepository)
{
this.logger = logger;
this.settingsRepository = settingsRepository;
}
public void Perform() public void Perform()
{ {
// TODO kioskMode = settingsRepository.Current.KioskMode;
logger.Info($"Initializing kiosk mode '{kioskMode}'...");
SplashScreen.UpdateText(TextKey.SplashScreen_InitializeKioskMode);
if (kioskMode == KioskMode.CreateNewDesktop)
{
CreateNewDesktop();
}
else
{
DisableExplorerShell();
}
} }
public void Revert() public void Revert()
{ {
// TODO logger.Info($"Reverting kiosk mode '{kioskMode}'...");
SplashScreen.UpdateText(TextKey.SplashScreen_RevertKioskMode);
if (kioskMode == KioskMode.CreateNewDesktop)
{
CloseNewDesktop();
}
else
{
RestartExplorerShell();
}
}
private void CreateNewDesktop()
{
}
private void CloseNewDesktop()
{
}
private void DisableExplorerShell()
{
}
private void RestartExplorerShell()
{
} }
} }
} }

View file

@ -6,10 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -19,66 +21,107 @@ namespace SafeExamBrowser.Runtime.Behaviour
{ {
internal class RuntimeController : IRuntimeController internal class RuntimeController : IRuntimeController
{ {
private ICommunication serviceProxy;
private Queue<IOperation> operations; private Queue<IOperation> operations;
private ILogger logger; private ILogger logger;
private IRuntimeInfo runtimeInfo;
private IRuntimeWindow runtimeWindow; private IRuntimeWindow runtimeWindow;
private IServiceProxy serviceProxy;
private ISettingsRepository settingsRepository; private ISettingsRepository settingsRepository;
private IShutdownController shutdownController; private IShutdownController shutdownController;
private IStartupController startupController; private IStartupController startupController;
private Action terminationCallback;
public ISettings Settings { private get; set; } private IText text;
private IUserInterfaceFactory uiFactory;
public RuntimeController( public RuntimeController(
ICommunication serviceProxy,
ILogger logger, ILogger logger,
IRuntimeWindow runtimeWindow, IRuntimeInfo runtimeInfo,
IServiceProxy serviceProxy,
ISettingsRepository settingsRepository, ISettingsRepository settingsRepository,
IShutdownController shutdownController, IShutdownController shutdownController,
IStartupController startupController) IStartupController startupController,
Action terminationCallback,
IText text,
IUserInterfaceFactory uiFactory)
{ {
this.serviceProxy = serviceProxy;
this.logger = logger; this.logger = logger;
this.runtimeWindow = runtimeWindow; this.runtimeInfo = runtimeInfo;
this.serviceProxy = serviceProxy;
this.settingsRepository = settingsRepository; this.settingsRepository = settingsRepository;
this.shutdownController = shutdownController; this.shutdownController = shutdownController;
this.startupController = startupController; this.startupController = startupController;
this.terminationCallback = terminationCallback;
this.text = text;
this.uiFactory = uiFactory;
operations = new Queue<IOperation>(); operations = new Queue<IOperation>();
} }
public bool TryInitializeApplication(Queue<IOperation> operations) public bool TryInitializeApplication(Queue<IOperation> operations)
{ {
operations = new Queue<IOperation>(operations);
var success = startupController.TryInitializeApplication(operations); var success = startupController.TryInitializeApplication(operations);
runtimeWindow = uiFactory.CreateRuntimeWindow(runtimeInfo, text);
if (success) if (success)
{ {
Start(); this.operations = new Queue<IOperation>(operations);
logger.Subscribe(runtimeWindow);
} }
return success; return success;
} }
public void StartSession()
{
runtimeWindow.Show();
logger.Info("Starting new session...");
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_StartSession, true);
// TODO:
// - Initialize configuration
// - Initialize kiosk mode
// - Initialize session data
// - Start runtime communication host
// - Create and connect to client
// - Initialize session with service
// - Verify session integrity and start event handling
System.Threading.Thread.Sleep(10000);
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning);
if (settingsRepository.Current.KioskMode == KioskMode.DisableExplorerShell)
{
runtimeWindow.Hide();
}
}
public void FinalizeApplication() public void FinalizeApplication()
{ {
Stop(); StopSession();
// TODO:
// - Disconnect from service
// - Terminate runtime communication host
// - Revert kiosk mode (or do that when stopping session?)
logger.Unsubscribe(runtimeWindow);
runtimeWindow.Close();
shutdownController.FinalizeApplication(new Queue<IOperation>(operations.Reverse())); shutdownController.FinalizeApplication(new Queue<IOperation>(operations.Reverse()));
} }
private void Start() private void StopSession()
{ {
logger.Info("Starting event handling..."); logger.Info("Stopping current session...");
// TODO SplashScreen.UpdateText(TextKey.SplashScreen_StartEventHandling); runtimeWindow.Show();
runtimeWindow.BringToForeground();
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_StopSession, true);
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning); // TODO:
} // - Terminate client (or does it terminate itself?)
// - Finalize session with service
private void Stop() // - Stop event handling and close session
{
logger.Info("Stopping event handling...");
// TODO SplashScreen.UpdateText(TextKey.SplashScreen_StopEventHandling);
} }
} }
} }

View file

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Windows;
using SafeExamBrowser.Configuration; using SafeExamBrowser.Configuration;
using SafeExamBrowser.Configuration.Settings; using SafeExamBrowser.Configuration.Settings;
using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Behaviour;
@ -35,7 +36,6 @@ namespace SafeExamBrowser.Runtime
internal IRuntimeController RuntimeController { get; private set; } internal IRuntimeController RuntimeController { get; private set; }
internal Queue<IOperation> StartupOperations { get; private set; } internal Queue<IOperation> StartupOperations { get; private set; }
internal RuntimeWindow RuntimeWindow { get; private set; }
internal void BuildObjectGraph() internal void BuildObjectGraph()
{ {
@ -56,16 +56,13 @@ namespace SafeExamBrowser.Runtime
var shutdownController = new ShutdownController(logger, runtimeInfo, text, uiFactory); var shutdownController = new ShutdownController(logger, runtimeInfo, text, uiFactory);
var startupController = new StartupController(logger, runtimeInfo, systemInfo, text, uiFactory); var startupController = new StartupController(logger, runtimeInfo, systemInfo, text, uiFactory);
RuntimeWindow = new RuntimeWindow(new DefaultLogFormatter(), runtimeInfo, text); RuntimeController = new RuntimeController(new ModuleLogger(logger, typeof(RuntimeController)), runtimeInfo, serviceProxy, settingsRepository, shutdownController, startupController, Application.Current.Shutdown, text, uiFactory);
RuntimeController = new RuntimeController(serviceProxy, new ModuleLogger(logger, typeof(RuntimeController)), RuntimeWindow, settingsRepository, shutdownController, startupController);
logger.Subscribe(RuntimeWindow);
StartupOperations = new Queue<IOperation>(); StartupOperations = new Queue<IOperation>();
StartupOperations.Enqueue(new I18nOperation(logger, text)); StartupOperations.Enqueue(new I18nOperation(logger, text));
StartupOperations.Enqueue(new ConfigurationOperation(logger, runtimeInfo, settingsRepository, text, uiFactory, args)); StartupOperations.Enqueue(new ConfigurationOperation(logger, runtimeInfo, settingsRepository, text, uiFactory, args));
StartupOperations.Enqueue(new ServiceOperation(logger, serviceProxy, settingsRepository, text)); StartupOperations.Enqueue(new ServiceOperation(logger, serviceProxy, settingsRepository, text));
StartupOperations.Enqueue(new KioskModeOperation()); StartupOperations.Enqueue(new KioskModeOperation(logger, settingsRepository));
} }
internal void LogStartupInformation() internal void LogStartupInformation()

View file

@ -55,12 +55,30 @@ namespace SafeExamBrowser.UserInterface.Classic
public void BringToForeground() public void BringToForeground()
{ {
if (WindowState == WindowState.Minimized) Dispatcher.Invoke(() =>
{ {
WindowState = WindowState.Normal; if (WindowState == WindowState.Minimized)
} {
WindowState = WindowState.Normal;
}
Activate(); Activate();
});
}
public new void Close()
{
Dispatcher.Invoke(base.Close);
}
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void Show()
{
Dispatcher.Invoke(base.Show);
} }
public void UpdateAddress(string url) public void UpdateAddress(string url)

View file

@ -47,6 +47,11 @@ namespace SafeExamBrowser.UserInterface.Classic
Dispatcher.Invoke(base.Close); Dispatcher.Invoke(base.Close);
} }
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void Show() public new void Show()
{ {
Dispatcher.Invoke(base.Show); Dispatcher.Invoke(base.Show);

View file

@ -8,7 +8,7 @@
mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="750" WindowStyle="None" WindowStartupLocation="CenterScreen" mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="750" WindowStyle="None" WindowStartupLocation="CenterScreen"
Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True"> Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
<Grid> <Grid>
<Border Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5"> <Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5">
<Border.Effect> <Border.Effect>
<BlurEffect Radius="10" /> <BlurEffect Radius="10" />
</Border.Effect> </Border.Effect>
@ -18,7 +18,7 @@
<EventTrigger RoutedEvent="FrameworkElement.Loaded"> <EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard> <BeginStoryboard>
<Storyboard> <Storyboard>
<DoubleAnimation AutoReverse="True" Storyboard.TargetProperty="Opacity" From="1.0" To="0.2" Duration="0:0:2.5" RepeatBehavior="Forever" /> <DoubleAnimation AutoReverse="True" Storyboard.TargetProperty="Opacity" From="0.2" To="1.0" Duration="0:0:2.5" RepeatBehavior="Forever" />
</Storyboard> </Storyboard>
</BeginStoryboard> </BeginStoryboard>
</EventTrigger> </EventTrigger>
@ -41,8 +41,8 @@
<Image Grid.Column="0" Grid.ColumnSpan="2" Margin="-25,0,0,0" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Classic;component/Images/SplashScreen.png" /> <Image Grid.Column="0" Grid.ColumnSpan="2" Margin="-25,0,0,0" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Classic;component/Images/SplashScreen.png" />
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,175,10" TextWrapping="Wrap" /> <TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,175,10" TextWrapping="Wrap" />
</Grid> </Grid>
<!--<ProgressBar x:Name="ProgressBar" Grid.Row="1" IsIndeterminate="True" BorderThickness="0" />--> <ProgressBar x:Name="ProgressBar" Grid.Row="1" IsIndeterminate="True" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue" Visibility="Hidden" />
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" Text="Application is running..." FontSize="12" FontWeight="Bold" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center" /> <TextBlock x:Name="StatusTextBlock" Grid.Row="1" FontSize="12" FontWeight="Bold" Foreground="Black" HorizontalAlignment="Center" Text="{Binding Status}" VerticalAlignment="Center" />
<Border Grid.Row="2" BorderBrush="DodgerBlue" BorderThickness="0,0.5,0,0"> <Border Grid.Row="2" BorderBrush="DodgerBlue" BorderThickness="0,0.5,0,0">
<ScrollViewer x:Name="LogScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,10,0,0"> <ScrollViewer x:Name="LogScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,10,0,0">
<ScrollViewer.Resources> <ScrollViewer.Resources>

View file

@ -9,18 +9,22 @@
using System; using System;
using System.Windows; using System.Windows;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.Classic.ViewModels;
namespace SafeExamBrowser.UserInterface.Classic namespace SafeExamBrowser.UserInterface.Classic
{ {
public partial class RuntimeWindow : Window, ILogObserver, IRuntimeWindow public partial class RuntimeWindow : Window, IRuntimeWindow
{ {
private bool allowClose;
private ILogContentFormatter formatter; private ILogContentFormatter formatter;
private IRuntimeInfo runtimeInfo; private IRuntimeInfo runtimeInfo;
private IText text; private IText text;
private RuntimeWindowViewModel model;
private WindowClosingEventHandler closing; private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing event WindowClosingEventHandler IWindow.Closing
@ -44,6 +48,20 @@ namespace SafeExamBrowser.UserInterface.Classic
Dispatcher.Invoke(Activate); Dispatcher.Invoke(Activate);
} }
public new void Close()
{
Dispatcher.Invoke(() =>
{
allowClose = true;
base.Close();
});
}
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public void Notify(ILogContent content) public void Notify(ILogContent content)
{ {
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
@ -53,18 +71,41 @@ namespace SafeExamBrowser.UserInterface.Classic
}); });
} }
public void UpdateStatus(TextKey key) public new void Show()
{ {
Dispatcher.Invoke(() => StatusTextBlock.Text = text.Get(key)); Dispatcher.Invoke(base.Show);
}
public void UpdateStatus(TextKey key, bool showBusyIndication = false)
{
Dispatcher.Invoke(() =>
{
AnimatedBorder.Visibility = showBusyIndication ? Visibility.Hidden : Visibility.Visible;
ProgressBar.Visibility = showBusyIndication ? Visibility.Visible : Visibility.Hidden;
model.StopBusyIndication();
model.Status = text.Get(key);
if (showBusyIndication)
{
model.StartBusyIndication();
}
});
} }
private void InitializeRuntimeWindow() private void InitializeRuntimeWindow()
{ {
Title = $"{runtimeInfo.ProgramTitle} - Version {runtimeInfo.ProgramVersion}"; Title = $"{runtimeInfo.ProgramTitle} - Version {runtimeInfo.ProgramVersion}";
InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic }); InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak()); InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 }); InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 });
model = new RuntimeWindowViewModel();
StatusTextBlock.DataContext = model;
Closing += (o, args) => args.Cancel = !allowClose;
} }
} }
} }

View file

@ -113,10 +113,12 @@
</Compile> </Compile>
<Compile Include="UserInterfaceFactory.cs" /> <Compile Include="UserInterfaceFactory.cs" />
<Compile Include="Utilities\IconResourceLoader.cs" /> <Compile Include="Utilities\IconResourceLoader.cs" />
<Compile Include="Utilities\RuntimeWindowLogFormatter.cs" />
<Compile Include="Utilities\VisualExtensions.cs" /> <Compile Include="Utilities\VisualExtensions.cs" />
<Compile Include="Utilities\XamlIconResource.cs" /> <Compile Include="Utilities\XamlIconResource.cs" />
<Compile Include="ViewModels\DateTimeViewModel.cs" /> <Compile Include="ViewModels\DateTimeViewModel.cs" />
<Compile Include="ViewModels\LogViewModel.cs" /> <Compile Include="ViewModels\LogViewModel.cs" />
<Compile Include="ViewModels\RuntimeWindowViewModel.cs" />
<Compile Include="ViewModels\SplashScreenViewModel.cs" /> <Compile Include="ViewModels\SplashScreenViewModel.cs" />
<Page Include="AboutWindow.xaml"> <Page Include="AboutWindow.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>

View file

@ -17,9 +17,17 @@ namespace SafeExamBrowser.UserInterface.Classic
{ {
public partial class SplashScreen : Window, ISplashScreen public partial class SplashScreen : Window, ISplashScreen
{ {
private bool allowClose;
private SplashScreenViewModel model = new SplashScreenViewModel(); private SplashScreenViewModel model = new SplashScreenViewModel();
private IRuntimeInfo runtimeInfo; private IRuntimeInfo runtimeInfo;
private IText text; private IText text;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public SplashScreen(IRuntimeInfo runtimeInfo, IText text) public SplashScreen(IRuntimeInfo runtimeInfo, IText text)
{ {
@ -30,14 +38,28 @@ namespace SafeExamBrowser.UserInterface.Classic
InitializeSplashScreen(); InitializeSplashScreen();
} }
public void InvokeClose() public void BringToForeground()
{ {
Dispatcher.Invoke(Close); Dispatcher.Invoke(Activate);
} }
public void InvokeShow() public new void Close()
{ {
Dispatcher.Invoke(Show); Dispatcher.Invoke(() =>
{
allowClose = true;
base.Close();
});
}
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void Show()
{
Dispatcher.Invoke(base.Show);
} }
public void Progress(int amount = 1) public void Progress(int amount = 1)
@ -83,6 +105,8 @@ namespace SafeExamBrowser.UserInterface.Classic
// To prevent the progress bar going from max to min value at startup... // To prevent the progress bar going from max to min value at startup...
model.MaxProgress = 1; model.MaxProgress = 1;
Closing += (o, args) => args.Cancel = !allowClose;
} }
} }
} }

View file

@ -15,6 +15,7 @@ using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using SafeExamBrowser.UserInterface.Classic.Controls; using SafeExamBrowser.UserInterface.Classic.Controls;
using SafeExamBrowser.UserInterface.Classic.Utilities;
using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBoxResult; using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBoxResult;
namespace SafeExamBrowser.UserInterface.Classic namespace SafeExamBrowser.UserInterface.Classic
@ -76,6 +77,30 @@ namespace SafeExamBrowser.UserInterface.Classic
return new PowerSupplyControl(); return new PowerSupplyControl();
} }
public IRuntimeWindow CreateRuntimeWindow(IRuntimeInfo runtimeInfo, IText text)
{
RuntimeWindow runtimeWindow = null;
var windowReadyEvent = new AutoResetEvent(false);
var runtimeWindowThread = new Thread(() =>
{
runtimeWindow = new RuntimeWindow(new RuntimeWindowLogFormatter(), runtimeInfo, text);
runtimeWindow.Closed += (o, args) => runtimeWindow.Dispatcher.InvokeShutdown();
windowReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run();
});
runtimeWindowThread.SetApartmentState(ApartmentState.STA);
runtimeWindowThread.Name = nameof(RuntimeWindow);
runtimeWindowThread.IsBackground = true;
runtimeWindowThread.Start();
windowReadyEvent.WaitOne();
return runtimeWindow;
}
public ISplashScreen CreateSplashScreen(IRuntimeInfo runtimeInfo, IText text) public ISplashScreen CreateSplashScreen(IRuntimeInfo runtimeInfo, IText text)
{ {
SplashScreen splashScreen = null; SplashScreen splashScreen = null;

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2018 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.Contracts.Logging;
namespace SafeExamBrowser.UserInterface.Classic.Utilities
{
internal class RuntimeWindowLogFormatter : ILogContentFormatter
{
public string Format(ILogContent content)
{
if (content is ILogText)
{
return (content as ILogText).Text;
}
if (content is ILogMessage)
{
return FormatLogMessage(content as ILogMessage);
}
throw new NotImplementedException($"The runtime window formatter is not yet implemented for log content of type {content.GetType()}!");
}
private string FormatLogMessage(ILogMessage message)
{
var time = message.DateTime.ToString("HH:mm:ss.fff");
var severity = message.Severity.ToString().ToUpper();
return $"{time} - {severity}: {message.Message}";
}
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2018 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.ComponentModel;
using System.Timers;
namespace SafeExamBrowser.UserInterface.Classic.ViewModels
{
internal class RuntimeWindowViewModel : INotifyPropertyChanged
{
private string status;
private Timer timer;
public event PropertyChangedEventHandler PropertyChanged;
public string Status
{
get
{
return status;
}
set
{
status = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
}
}
public void StartBusyIndication()
{
StopBusyIndication();
timer = new Timer
{
AutoReset = true,
Interval = 750
};
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
public void StopBusyIndication()
{
timer?.Stop();
timer?.Close();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var next = Status ?? string.Empty;
if (next.EndsWith("..."))
{
next = Status.Substring(0, Status.Length - 3);
}
else
{
next += ".";
}
Status = next;
}
}
}

View file

@ -53,12 +53,30 @@ namespace SafeExamBrowser.UserInterface.Windows10
public void BringToForeground() public void BringToForeground()
{ {
if (WindowState == WindowState.Minimized) Dispatcher.Invoke(() =>
{ {
WindowState = WindowState.Normal; if (WindowState == WindowState.Minimized)
} {
WindowState = WindowState.Normal;
}
Activate(); Activate();
});
}
public new void Close()
{
Dispatcher.Invoke(base.Close);
}
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void Show()
{
Dispatcher.Invoke(base.Show);
} }
public void UpdateAddress(string url) public void UpdateAddress(string url)

View file

@ -47,6 +47,11 @@ namespace SafeExamBrowser.UserInterface.Windows10
Dispatcher.Invoke(base.Close); Dispatcher.Invoke(base.Close);
} }
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void Show() public new void Show()
{ {
Dispatcher.Invoke(base.Show); Dispatcher.Invoke(base.Show);

View file

@ -17,9 +17,17 @@ namespace SafeExamBrowser.UserInterface.Windows10
{ {
public partial class SplashScreen : Window, ISplashScreen public partial class SplashScreen : Window, ISplashScreen
{ {
private bool allowClose;
private SplashScreenViewModel model = new SplashScreenViewModel(); private SplashScreenViewModel model = new SplashScreenViewModel();
private IRuntimeInfo runtimeInfo; private IRuntimeInfo runtimeInfo;
private IText text; private IText text;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public SplashScreen(IRuntimeInfo runtimeInfo, IText text) public SplashScreen(IRuntimeInfo runtimeInfo, IText text)
{ {
@ -30,14 +38,28 @@ namespace SafeExamBrowser.UserInterface.Windows10
InitializeSplashScreen(); InitializeSplashScreen();
} }
public void InvokeClose() public void BringToForeground()
{ {
Dispatcher.Invoke(Close); Dispatcher.Invoke(Activate);
} }
public void InvokeShow() public new void Close()
{ {
Dispatcher.Invoke(Show); Dispatcher.Invoke(() =>
{
allowClose = true;
base.Close();
});
}
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void Show()
{
Dispatcher.Invoke(base.Show);
} }
public void Progress(int amount = 1) public void Progress(int amount = 1)
@ -83,6 +105,8 @@ namespace SafeExamBrowser.UserInterface.Windows10
// To prevent the progress bar going from max to min value at startup... // To prevent the progress bar going from max to min value at startup...
model.MaxProgress = 1; model.MaxProgress = 1;
Closing += (o, args) => args.Cancel = !allowClose;
} }
} }
} }

View file

@ -77,6 +77,12 @@ namespace SafeExamBrowser.UserInterface.Windows10
return new PowerSupplyControl(); return new PowerSupplyControl();
} }
public IRuntimeWindow CreateRuntimeWindow(IRuntimeInfo runtimeInfo, IText text)
{
// TODO
throw new System.NotImplementedException();
}
public ISplashScreen CreateSplashScreen(IRuntimeInfo runtimeInfo, IText text) public ISplashScreen CreateSplashScreen(IRuntimeInfo runtimeInfo, IText text)
{ {
SplashScreen splashScreen = null; SplashScreen splashScreen = null;