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
{
public ConfigurationMode ConfigurationMode { get; set; }
public KioskMode KioskMode { get; set; }
public ServicePolicy ServicePolicy { 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.
/// </summary>
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>
IKeyboardSettings Keyboard { get; }
/// <summary>
/// The kiosk mode which determines how the computer is locked down.
/// </summary>
KioskMode KioskMode { get; }
/// <summary>
/// All mouse-related settings.
/// </summary>
IMouseSettings Mouse { get; }
/// <summary>
/// The active service policy.
/// The active policy for the service component.
/// </summary>
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_LogTooltip,
RuntimeWindow_ApplicationRunning,
RuntimeWindow_StartSession,
RuntimeWindow_StopSession,
SplashScreen_CloseServiceConnection,
SplashScreen_EmptyClipboard,
SplashScreen_InitializeBrowser,
SplashScreen_InitializeConfiguration,
SplashScreen_InitializeKioskMode,
SplashScreen_InitializeProcessMonitoring,
SplashScreen_InitializeServiceConnection,
SplashScreen_InitializeTaskbar,
SplashScreen_InitializeWindowMonitoring,
SplashScreen_InitializeWorkingArea,
SplashScreen_RestoreWorkingArea,
SplashScreen_RevertKioskMode,
SplashScreen_ShutdownProcedure,
SplashScreen_StartEventHandling,
SplashScreen_StartKeyboardInterception,

View file

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

View file

@ -7,15 +7,16 @@
*/
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Contracts.UserInterface
{
public interface IRuntimeWindow : IWindow
public interface IRuntimeWindow : ILogObserver, IWindow
{
/// <summary>
/// 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.
/// </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
{
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>
/// Updates the progress bar of the splash screen according to the specified amount.
/// </summary>

View file

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

View file

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

View file

@ -72,7 +72,7 @@ namespace SafeExamBrowser.Core.Behaviour
splashScreen = uiFactory.CreateSplashScreen(runtimeInfo, text);
splashScreen.SetIndeterminate();
splashScreen.UpdateText(TextKey.SplashScreen_ShutdownProcedure);
splashScreen.InvokeShow();
splashScreen.Show();
}
private void LogAndShowException(Exception e)
@ -92,7 +92,7 @@ namespace SafeExamBrowser.Core.Behaviour
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.SetMaxProgress(operationCount);
splashScreen.UpdateText(TextKey.SplashScreen_StartupProcedure);
splashScreen.InvokeShow();
splashScreen.Show();
}
private void LogAndShowException(Exception e)
@ -139,7 +139,7 @@ namespace SafeExamBrowser.Core.Behaviour
logger.Info("--- Startup procedure aborted! ---");
}
splashScreen?.InvokeClose();
splashScreen?.Close();
}
}
}

View file

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

View file

@ -7,7 +7,6 @@
*/
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
@ -56,6 +55,8 @@ namespace SafeExamBrowser.Runtime
{
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnExplicitShutdown;
instances.BuildObjectGraph();
instances.LogStartupInformation();
@ -63,9 +64,7 @@ namespace SafeExamBrowser.Runtime
if (success)
{
MainWindow = instances.RuntimeWindow;
MainWindow.Closing += MainWindow_Closing;
MainWindow.Show();
instances.RuntimeController.StartSession();
}
else
{
@ -75,15 +74,10 @@ namespace SafeExamBrowser.Runtime
protected override void OnExit(ExitEventArgs e)
{
instances.RuntimeController.FinalizeApplication();
instances.LogShutdownInformation();
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/.
*/
using System;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
internal class KioskModeOperation : IOperation
{
private ILogger logger;
private ISettingsRepository settingsRepository;
private KioskMode kioskMode;
public bool AbortStartup { get; private set; }
public ISplashScreen SplashScreen { private get; set; }
public KioskModeOperation(ILogger logger, ISettingsRepository settingsRepository)
{
this.logger = logger;
this.settingsRepository = settingsRepository;
}
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()
{
// 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/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
@ -19,66 +21,107 @@ namespace SafeExamBrowser.Runtime.Behaviour
{
internal class RuntimeController : IRuntimeController
{
private ICommunication serviceProxy;
private Queue<IOperation> operations;
private ILogger logger;
private IRuntimeInfo runtimeInfo;
private IRuntimeWindow runtimeWindow;
private IServiceProxy serviceProxy;
private ISettingsRepository settingsRepository;
private IShutdownController shutdownController;
private IStartupController startupController;
public ISettings Settings { private get; set; }
private Action terminationCallback;
private IText text;
private IUserInterfaceFactory uiFactory;
public RuntimeController(
ICommunication serviceProxy,
ILogger logger,
IRuntimeWindow runtimeWindow,
IRuntimeInfo runtimeInfo,
IServiceProxy serviceProxy,
ISettingsRepository settingsRepository,
IShutdownController shutdownController,
IStartupController startupController)
IStartupController startupController,
Action terminationCallback,
IText text,
IUserInterfaceFactory uiFactory)
{
this.serviceProxy = serviceProxy;
this.logger = logger;
this.runtimeWindow = runtimeWindow;
this.runtimeInfo = runtimeInfo;
this.serviceProxy = serviceProxy;
this.settingsRepository = settingsRepository;
this.shutdownController = shutdownController;
this.startupController = startupController;
this.terminationCallback = terminationCallback;
this.text = text;
this.uiFactory = uiFactory;
operations = new Queue<IOperation>();
}
public bool TryInitializeApplication(Queue<IOperation> operations)
{
operations = new Queue<IOperation>(operations);
var success = startupController.TryInitializeApplication(operations);
runtimeWindow = uiFactory.CreateRuntimeWindow(runtimeInfo, text);
if (success)
{
Start();
this.operations = new Queue<IOperation>(operations);
logger.Subscribe(runtimeWindow);
}
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()
{
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()));
}
private void Start()
private void StopSession()
{
logger.Info("Starting event handling...");
// TODO SplashScreen.UpdateText(TextKey.SplashScreen_StartEventHandling);
logger.Info("Stopping current session...");
runtimeWindow.Show();
runtimeWindow.BringToForeground();
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_StopSession, true);
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning);
}
private void Stop()
{
logger.Info("Stopping event handling...");
// TODO SplashScreen.UpdateText(TextKey.SplashScreen_StopEventHandling);
// TODO:
// - Terminate client (or does it terminate itself?)
// - Finalize session with service
// - Stop event handling and close session
}
}
}

View file

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

View file

@ -55,12 +55,30 @@ namespace SafeExamBrowser.UserInterface.Classic
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)

View file

@ -47,6 +47,11 @@ namespace SafeExamBrowser.UserInterface.Classic
Dispatcher.Invoke(base.Close);
}
public new void Hide()
{
Dispatcher.Invoke(base.Hide);
}
public new void 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"
Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
<Grid>
<Border Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5">
<Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5">
<Border.Effect>
<BlurEffect Radius="10" />
</Border.Effect>
@ -18,7 +18,7 @@
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<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>
</BeginStoryboard>
</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" />
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,175,10" TextWrapping="Wrap" />
</Grid>
<!--<ProgressBar x:Name="ProgressBar" Grid.Row="1" IsIndeterminate="True" BorderThickness="0" />-->
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" Text="Application is running..." FontSize="12" FontWeight="Bold" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center" />
<ProgressBar x:Name="ProgressBar" Grid.Row="1" IsIndeterminate="True" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue" Visibility="Hidden" />
<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">
<ScrollViewer x:Name="LogScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,10,0,0">
<ScrollViewer.Resources>

View file

@ -9,18 +9,22 @@
using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.UserInterface.Classic.ViewModels;
namespace SafeExamBrowser.UserInterface.Classic
{
public partial class RuntimeWindow : Window, ILogObserver, IRuntimeWindow
public partial class RuntimeWindow : Window, IRuntimeWindow
{
private bool allowClose;
private ILogContentFormatter formatter;
private IRuntimeInfo runtimeInfo;
private IText text;
private RuntimeWindowViewModel model;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
@ -44,6 +48,20 @@ namespace SafeExamBrowser.UserInterface.Classic
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)
{
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()
{
Title = $"{runtimeInfo.ProgramTitle} - Version {runtimeInfo.ProgramVersion}";
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 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 Include="UserInterfaceFactory.cs" />
<Compile Include="Utilities\IconResourceLoader.cs" />
<Compile Include="Utilities\RuntimeWindowLogFormatter.cs" />
<Compile Include="Utilities\VisualExtensions.cs" />
<Compile Include="Utilities\XamlIconResource.cs" />
<Compile Include="ViewModels\DateTimeViewModel.cs" />
<Compile Include="ViewModels\LogViewModel.cs" />
<Compile Include="ViewModels\RuntimeWindowViewModel.cs" />
<Compile Include="ViewModels\SplashScreenViewModel.cs" />
<Page Include="AboutWindow.xaml">
<SubType>Designer</SubType>

View file

@ -17,9 +17,17 @@ namespace SafeExamBrowser.UserInterface.Classic
{
public partial class SplashScreen : Window, ISplashScreen
{
private bool allowClose;
private SplashScreenViewModel model = new SplashScreenViewModel();
private IRuntimeInfo runtimeInfo;
private IText text;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public SplashScreen(IRuntimeInfo runtimeInfo, IText text)
{
@ -30,14 +38,28 @@ namespace SafeExamBrowser.UserInterface.Classic
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)
@ -83,6 +105,8 @@ namespace SafeExamBrowser.UserInterface.Classic
// To prevent the progress bar going from max to min value at startup...
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.Taskbar;
using SafeExamBrowser.UserInterface.Classic.Controls;
using SafeExamBrowser.UserInterface.Classic.Utilities;
using MessageBoxResult = SafeExamBrowser.Contracts.UserInterface.MessageBoxResult;
namespace SafeExamBrowser.UserInterface.Classic
@ -76,6 +77,30 @@ namespace SafeExamBrowser.UserInterface.Classic
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)
{
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()
{
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)

View file

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

View file

@ -17,9 +17,17 @@ namespace SafeExamBrowser.UserInterface.Windows10
{
public partial class SplashScreen : Window, ISplashScreen
{
private bool allowClose;
private SplashScreenViewModel model = new SplashScreenViewModel();
private IRuntimeInfo runtimeInfo;
private IText text;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public SplashScreen(IRuntimeInfo runtimeInfo, IText text)
{
@ -30,14 +38,28 @@ namespace SafeExamBrowser.UserInterface.Windows10
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)
@ -83,6 +105,8 @@ namespace SafeExamBrowser.UserInterface.Windows10
// To prevent the progress bar going from max to min value at startup...
model.MaxProgress = 1;
Closing += (o, args) => args.Cancel = !allowClose;
}
}
}

View file

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