Implemented basic battery system control.

This commit is contained in:
dbuechel 2017-08-17 12:17:58 +02:00
parent 7baf826e5a
commit 0184eb0fe1
18 changed files with 272 additions and 80 deletions

View file

@ -43,6 +43,11 @@ namespace SafeExamBrowser.Contracts.I18n
SplashScreen_TerminateBrowser,
SplashScreen_WaitExplorerStartup,
SplashScreen_WaitExplorerTermination,
SystemControl_BatteryCharged,
SystemControl_BatteryCharging,
SystemControl_BatteryChargeCriticalWarning,
SystemControl_BatteryChargeLowInfo,
SystemControl_BatteryRemainingCharge,
Version
}
}

View file

@ -86,6 +86,7 @@
<Compile Include="Monitoring\KeyModifier.cs" />
<Compile Include="Monitoring\KeyState.cs" />
<Compile Include="Monitoring\MouseButton.cs" />
<Compile Include="SystemComponents\BatteryChargeStatus.cs" />
<Compile Include="SystemComponents\ISystemComponent.cs" />
<Compile Include="UserInterface\IBrowserControl.cs" />
<Compile Include="UserInterface\IBrowserWindow.cs" />

View file

@ -0,0 +1,30 @@
/*
* 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.Contracts.SystemComponents
{
public enum BatteryChargeStatus
{
Undefined = 0,
/// <summary>
/// The battery charge is critical, i.e. below 20%.
/// </summary>
Critical,
/// <summary>
/// The battery charge is low, i.e. below 35%.
/// </summary>
Low,
/// <summary>
/// The battery charge is okay, i.e. above 35%.
/// </summary>
Okay
}
}

View file

@ -13,17 +13,12 @@ namespace SafeExamBrowser.Contracts.SystemComponents
public interface ISystemComponent<TControl> where TControl : ISystemControl
{
/// <summary>
/// Initializes the resources used by the component and starts its operations, if applicable.
/// Initializes the resources and operations of the component and registers its taskbar control.
/// </summary>
void Initialize();
void Initialize(TControl control);
/// <summary>
/// Registers the taskbar control for the system component.
/// </summary>
void RegisterControl(TControl control);
/// <summary>
/// Instructs the component to stop any running operations and release all used resources.
/// Instructs the component to stop any running operations and releases all used resources.
/// </summary>
void Terminate();
}

View file

@ -10,6 +10,11 @@ namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
{
public interface ISystemControl
{
/// <summary>
/// Closes any pop-up windows associated with this control.
/// </summary>
void Close();
/// <summary>
/// Sets the tooltip text of the system control.
/// </summary>

View file

@ -6,19 +6,30 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.SystemComponents;
namespace SafeExamBrowser.Contracts.UserInterface.Taskbar
{
public interface ISystemPowerSupplyControl : ISystemControl
{
/// <summary>
/// Sets the current charge of the system battery, if available. <c>0.0</c> means the battery is empty, <c>1.0</c> means it's
/// fully charged. Pass <c>null</c> to indicate that the computer system has no battery.
/// Sets the current charge of the system battery: <c>0.0</c> means the battery is empty, <c>1.0</c> means it's fully charged.
/// </summary>
void SetBatteryCharge(double? percentage);
void SetBatteryCharge(double charge, BatteryChargeStatus status);
/// <summary>
/// Sets the power supply status, i.e. whether the computer system is connected to the power grid or not.
/// </summary>
void SetPowerGridConnection(bool connected);
/// <summary>
/// Warns the user that the battery charge is critical.
/// </summary>
void ShowCriticalBatteryWarning(string warning);
/// <summary>
/// Indicates the user that the battery charge is low.
/// </summary>
void ShowLowBatteryInfo(string info);
}
}

View file

@ -60,7 +60,11 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
}
CreateAboutNotification();
CreatePowerSupplyComponent();
if (systemInfo.HasBattery)
{
CreatePowerSupplyComponent();
}
}
public void Revert()
@ -68,7 +72,10 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
logController?.Terminate();
aboutController?.Terminate();
powerSupply.Terminate();
if (systemInfo.HasBattery)
{
powerSupply.Terminate();
}
}
private void CreateLogNotification()
@ -97,9 +104,7 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
{
var control = uiFactory.CreatePowerSupplyControl();
powerSupply.RegisterControl(control);
powerSupply.Initialize();
powerSupply.Initialize(control);
taskbar.AddSystemControl(control);
}
}

View file

@ -1,32 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<Text>
<Browser_ShowDeveloperConsole>Open Console</Browser_ShowDeveloperConsole>
<LogWindow_Title>Application Log</LogWindow_Title>
<MessageBox_ShutdownError>An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...</MessageBox_ShutdownError>
<MessageBox_ShutdownErrorTitle>Shutdown Error</MessageBox_ShutdownErrorTitle>
<MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError>
<MessageBox_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle>
<Notification_AboutTooltip>About Safe Exam Browser</Notification_AboutTooltip>
<Notification_LogTooltip>Application Log</Notification_LogTooltip>
<SplashScreen_EmptyClipboard>Emptying clipboard</SplashScreen_EmptyClipboard>
<SplashScreen_InitializeBrowser>Initializing browser</SplashScreen_InitializeBrowser>
<SplashScreen_InitializeProcessMonitoring>Initializing process monitoring</SplashScreen_InitializeProcessMonitoring>
<SplashScreen_InitializeTaskbar>Initializing taskbar</SplashScreen_InitializeTaskbar>
<SplashScreen_InitializeWindowMonitoring>Initializing window monitoring</SplashScreen_InitializeWindowMonitoring>
<SplashScreen_InitializeWorkingArea>Initializing working area</SplashScreen_InitializeWorkingArea>
<SplashScreen_RestoreWorkingArea>Restoring working area</SplashScreen_RestoreWorkingArea>
<SplashScreen_ShutdownProcedure>Initiating shutdown procedure</SplashScreen_ShutdownProcedure>
<SplashScreen_StartEventHandling>Starting event handling</SplashScreen_StartEventHandling>
<SplashScreen_StartKeyboardInterception>Starting keyboard interception</SplashScreen_StartKeyboardInterception>
<SplashScreen_StartMouseInterception>Starting mouse interception</SplashScreen_StartMouseInterception>
<SplashScreen_StartupProcedure>Initiating startup procedure</SplashScreen_StartupProcedure>
<SplashScreen_StopEventHandling>Stopping event handling</SplashScreen_StopEventHandling>
<SplashScreen_StopKeyboardInterception>Stopping keyboard interception</SplashScreen_StopKeyboardInterception>
<SplashScreen_StopMouseInterception>Stopping mouse interception</SplashScreen_StopMouseInterception>
<SplashScreen_StopProcessMonitoring>Stopping process monitoring</SplashScreen_StopProcessMonitoring>
<SplashScreen_StopWindowMonitoring>Stopping window monitoring</SplashScreen_StopWindowMonitoring>
<SplashScreen_TerminateBrowser>Terminating browser</SplashScreen_TerminateBrowser>
<SplashScreen_WaitExplorerStartup>Waiting for Windows explorer to start up</SplashScreen_WaitExplorerStartup>
<SplashScreen_WaitExplorerTermination>Waiting for Windows explorer to shut down</SplashScreen_WaitExplorerTermination>
<Version>Version</Version>
<Browser_ShowDeveloperConsole>Open Console</Browser_ShowDeveloperConsole>
<LogWindow_Title>Application Log</LogWindow_Title>
<MessageBox_ShutdownError>An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...</MessageBox_ShutdownError>
<MessageBox_ShutdownErrorTitle>Shutdown Error</MessageBox_ShutdownErrorTitle>
<MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError>
<MessageBox_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle>
<Notification_AboutTooltip>About Safe Exam Browser</Notification_AboutTooltip>
<Notification_LogTooltip>Application Log</Notification_LogTooltip>
<SplashScreen_EmptyClipboard>Emptying clipboard</SplashScreen_EmptyClipboard>
<SplashScreen_InitializeBrowser>Initializing browser</SplashScreen_InitializeBrowser>
<SplashScreen_InitializeProcessMonitoring>Initializing process monitoring</SplashScreen_InitializeProcessMonitoring>
<SplashScreen_InitializeTaskbar>Initializing taskbar</SplashScreen_InitializeTaskbar>
<SplashScreen_InitializeWindowMonitoring>Initializing window monitoring</SplashScreen_InitializeWindowMonitoring>
<SplashScreen_InitializeWorkingArea>Initializing working area</SplashScreen_InitializeWorkingArea>
<SplashScreen_RestoreWorkingArea>Restoring working area</SplashScreen_RestoreWorkingArea>
<SplashScreen_ShutdownProcedure>Initiating shutdown procedure</SplashScreen_ShutdownProcedure>
<SplashScreen_StartEventHandling>Starting event handling</SplashScreen_StartEventHandling>
<SplashScreen_StartKeyboardInterception>Starting keyboard interception</SplashScreen_StartKeyboardInterception>
<SplashScreen_StartMouseInterception>Starting mouse interception</SplashScreen_StartMouseInterception>
<SplashScreen_StartupProcedure>Initiating startup procedure</SplashScreen_StartupProcedure>
<SplashScreen_StopEventHandling>Stopping event handling</SplashScreen_StopEventHandling>
<SplashScreen_StopKeyboardInterception>Stopping keyboard interception</SplashScreen_StopKeyboardInterception>
<SplashScreen_StopMouseInterception>Stopping mouse interception</SplashScreen_StopMouseInterception>
<SplashScreen_StopProcessMonitoring>Stopping process monitoring</SplashScreen_StopProcessMonitoring>
<SplashScreen_StopWindowMonitoring>Stopping window monitoring</SplashScreen_StopWindowMonitoring>
<SplashScreen_TerminateBrowser>Terminating browser</SplashScreen_TerminateBrowser>
<SplashScreen_WaitExplorerStartup>Waiting for Windows explorer to start up</SplashScreen_WaitExplorerStartup>
<SplashScreen_WaitExplorerTermination>Waiting for Windows explorer to shut down</SplashScreen_WaitExplorerTermination>
<SystemControl_BatteryCharging>Charging... (%%CHARGE%%%)</SystemControl_BatteryCharging>
<SystemControl_BatteryCharged>Fully charged (%%CHARGE%%%)</SystemControl_BatteryCharged>
<SystemControl_BatteryChargeCriticalWarning>The battery charge is critically low. Please connect your computer to a power supply!</SystemControl_BatteryChargeCriticalWarning>
<SystemControl_BatteryChargeLowInfo>The battery charge is getting low. Consider connecting your computer to a power supply in time...</SystemControl_BatteryChargeLowInfo>
<SystemControl_BatteryRemainingCharge>%%HOURS%%h %%MINUTES%%min remaining (%%CHARGE%%%)</SystemControl_BatteryRemainingCharge>
<Version>Version</Version>
</Text>

View file

@ -6,35 +6,102 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Timers;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using PowerLineStatus = System.Windows.Forms.PowerLineStatus;
using SystemInformation = System.Windows.Forms.SystemInformation;
namespace SafeExamBrowser.SystemComponents
{
public class PowerSupply : ISystemComponent<ISystemPowerSupplyControl>
{
private const int TWO_SECONDS = 2000;
private bool infoShown, warningShown;
private ILogger logger;
private ISystemPowerSupplyControl control;
private IText text;
private Timer timer;
public PowerSupply(ILogger logger)
public PowerSupply(ILogger logger, IText text)
{
this.logger = logger;
this.text = text;
}
public void Initialize()
{
}
public void RegisterControl(ISystemPowerSupplyControl control)
public void Initialize(ISystemPowerSupplyControl control)
{
this.control = control;
UpdateControl();
timer = new Timer(TWO_SECONDS);
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = true;
timer.Start();
logger.Info("Started monitoring the power supply.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
UpdateControl();
}
public void Terminate()
{
timer?.Stop();
control?.Close();
logger.Info("Stopped monitoring the power supply.");
}
private void UpdateControl()
{
var charge = SystemInformation.PowerStatus.BatteryLifePercent;
var percentage = Math.Round(charge * 100);
var status = charge <= 0.35 ? (charge <= 0.2 ? BatteryChargeStatus.Critical : BatteryChargeStatus.Low) : BatteryChargeStatus.Okay;
var online = SystemInformation.PowerStatus.PowerLineStatus == PowerLineStatus.Online;
var tooltip = string.Empty;
if (online)
{
tooltip = text.Get(percentage == 100 ? TextKey.SystemControl_BatteryCharged : TextKey.SystemControl_BatteryCharging);
infoShown = false;
warningShown = false;
}
else
{
var hours = SystemInformation.PowerStatus.BatteryLifeRemaining / 3600;
var minutes = (SystemInformation.PowerStatus.BatteryLifeRemaining - (hours * 3600)) / 60;
if (status == BatteryChargeStatus.Low && !infoShown)
{
control.ShowLowBatteryInfo(text.Get(TextKey.SystemControl_BatteryChargeLowInfo));
infoShown = true;
logger.Info("Informed the user about low battery charge.");
}
if (status == BatteryChargeStatus.Critical && !warningShown)
{
control.ShowCriticalBatteryWarning(text.Get(TextKey.SystemControl_BatteryChargeCriticalWarning));
warningShown = true;
logger.Warn("Warned the user about critical battery charge.");
}
tooltip = text.Get(TextKey.SystemControl_BatteryRemainingCharge);
tooltip = tooltip.Replace("%%HOURS%%", hours.ToString());
tooltip = tooltip.Replace("%%MINUTES%%", minutes.ToString());
}
tooltip = tooltip.Replace("%%CHARGE%%", percentage.ToString());
control.SetBatteryCharge(charge, status);
control.SetPowerGridConnection(online);
control.SetTooltip(tooltip);
}
}
}

View file

@ -50,6 +50,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="PowerSupply.cs" />

View file

@ -9,7 +9,7 @@
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/ButtonStyles.xaml"/>
<ResourceDictionary Source="../Templates/Buttons.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>

View file

@ -8,7 +8,7 @@
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/ButtonStyles.xaml"/>
<ResourceDictionary Source="../Templates/Buttons.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>

View file

@ -8,19 +8,46 @@
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/ButtonStyles.xaml"/>
<ResourceDictionary Source="../Templates/Buttons.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Button x:Name="Button" Background="#00000000" BorderThickness="0" Foreground="White" Padding="5,0"
Template="{StaticResource ResourceKey=TaskbarButton}" Width="28">
<Canvas Height="18" Width="18">
<Path Stroke="White" Data="M3,6 H17 V9 H18 V11 H17 V9 H17 V14 H3 Z" Panel.ZIndex="2" />
<Rectangle x:Name="BatteryCharge" Canvas.Left="3" Canvas.Top="6" Fill="Green" Height="8" Width="14" />
<Grid x:Name="PowerPlug" Panel.ZIndex="3">
<Path Stroke="White" Fill="Black" Data="M2.5,14.5 V10 Q5,10 5,6 H4 V4 H4 V6 H1 V4 H1 V6 H0 Q0,10 2.5,10" Panel.ZIndex="2" />
<Path Stroke="Black" Data="M4,14.5 V10 Q6,10 6,5.5" Panel.ZIndex="1" />
</Grid>
</Canvas>
</Button>
<Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}" AllowsTransparency="True">
<Border BorderBrush="White" BorderThickness="0.5,0.5,0.5,0" MaxWidth="250" Padding="5">
<Border.Background>
<SolidColorBrush Color="Black" Opacity="0.8" />
</Border.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Background="Transparent" Click="Button_Click" Cursor="Hand" FontWeight="Bold"
Foreground="White" Template="{StaticResource ResourceKey=TaskbarButton}" Width="20">X</Button>
</Grid>
<TextBlock Grid.Row="1" x:Name="PopupText" Foreground="White" TextWrapping="Wrap" />
</Grid>
</Border>
</Popup>
<Button x:Name="Button" Background="#00000000" BorderThickness="0" Foreground="White" Padding="5,0"
Template="{StaticResource ResourceKey=TaskbarButton}" Width="28">
<Viewbox Stretch="Uniform" Width="Auto">
<Canvas Height="18" Width="18">
<Path Stroke="White" Data="M3,6 H17 V9 H18 V11 H17 V9 H17 V14 H3 Z" Panel.ZIndex="2" />
<Rectangle x:Name="BatteryCharge" Canvas.Left="3.5" Canvas.Top="6" Fill="Green" Height="8" Width="13" />
<Grid x:Name="PowerPlug" Panel.ZIndex="3">
<Path Stroke="White" Fill="Black" Data="M2.5,14.5 V10 Q5,10 5,6 H4 V4 H4 V6 H1 V4 H1 V6 H0 Q0,10 2.5,10" Panel.ZIndex="2" />
<Path Stroke="Black" Data="M4,14.5 V10 Q6,10 6,5.5" Panel.ZIndex="1" />
</Grid>
<TextBlock x:Name="Warning" FontSize="18" FontWeight="ExtraBold" Foreground="Red" Canvas.Left="7" Canvas.Top="-3.5" Panel.ZIndex="3">!</TextBlock>
</Canvas>
</Viewbox>
</Button>
</Grid>
</UserControl>

View file

@ -6,42 +6,70 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
namespace SafeExamBrowser.UserInterface.Controls
{
public partial class PowerSupplyControl : UserControl, ISystemPowerSupplyControl
{
private double BATTERY_CHARGE_MAX_WIDTH;
public PowerSupplyControl()
{
InitializeComponent();
InitializePowerSupplyControl();
BATTERY_CHARGE_MAX_WIDTH = BatteryCharge.Width;
}
public void SetBatteryCharge(double? percentage)
public void Close()
{
throw new NotImplementedException();
Popup.IsOpen = false;
}
public void SetBatteryCharge(double charge, BatteryChargeStatus status)
{
Dispatcher.Invoke(() =>
{
BatteryCharge.Width = BATTERY_CHARGE_MAX_WIDTH * charge;
BatteryCharge.Fill = status == BatteryChargeStatus.Low ? (status == BatteryChargeStatus.Critical ? Brushes.Red : Brushes.Orange) : Brushes.Green;
Warning.Visibility = status == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed;
});
}
public void SetPowerGridConnection(bool connected)
{
throw new NotImplementedException();
Dispatcher.Invoke(() => PowerPlug.Visibility = connected ? Visibility.Visible : Visibility.Collapsed);
}
public void SetTooltip(string text)
{
throw new NotImplementedException();
Dispatcher.Invoke(() => Button.ToolTip = text);
}
private void InitializePowerSupplyControl()
public void ShowCriticalBatteryWarning(string warning)
{
Button.Resources.MergedDictionaries.Add(new ResourceDictionary
{
Source = (new Uri("/SafeExamBrowser.UserInterface;component/Styles/ButtonStyles.xaml", UriKind.RelativeOrAbsolute))
});
Dispatcher.Invoke(() => ShowPopup(warning));
}
public void ShowLowBatteryInfo(string info)
{
Dispatcher.Invoke(() => ShowPopup(info));
}
private void ShowPopup(string text)
{
Popup.IsOpen = true;
PopupText.Text = text;
Background = (Brush) new BrushConverter().ConvertFrom("#2AFFFFFF");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Popup.IsOpen = false;
Background = (Brush) new BrushConverter().ConvertFrom("#00000000");
}
}
}

View file

@ -164,7 +164,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Styles\ButtonStyles.xaml">
<Page Include="Templates\Buttons.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View file

@ -24,6 +24,7 @@ namespace SafeExamBrowser.UserInterface
InitializeComponent();
Loaded += (o, args) => InitializeBounds();
Closing += Taskbar_Closing;
}
public void AddApplication(IApplicationButton button)
@ -76,5 +77,16 @@ namespace SafeExamBrowser.UserInterface
logger.Info($"Set taskbar bounds to {Width}x{Height} at ({Left}/{Top}), in physical pixels: {size.X}x{size.Y} at ({position.X}/{position.Y}).");
});
}
private void Taskbar_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
foreach (var child in SystemControlStackPanel.Children)
{
if (child is ISystemControl)
{
(child as ISystemControl).Close();
}
}
}
}
}

View file

@ -78,7 +78,7 @@ namespace SafeExamBrowser
displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
keyboardInterceptor = new KeyboardInterceptor(settings.Keyboard, new ModuleLogger(logger, typeof(KeyboardInterceptor)));
mouseInterceptor = new MouseInterceptor(new ModuleLogger(logger, typeof(MouseInterceptor)), settings.Mouse);
powerSupply = new PowerSupply(new ModuleLogger(logger, typeof(PowerSupply)));
powerSupply = new PowerSupply(new ModuleLogger(logger, typeof(PowerSupply)), text);
processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);