diff --git a/SafeExamBrowser.Contracts/I18n/TextKey.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs index 00e9bcd0..ab03bb45 100644 --- a/SafeExamBrowser.Contracts/I18n/TextKey.cs +++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs @@ -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 } } diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index d242d5cb..06f174d7 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -86,6 +86,7 @@ + diff --git a/SafeExamBrowser.Contracts/SystemComponents/BatteryChargeStatus.cs b/SafeExamBrowser.Contracts/SystemComponents/BatteryChargeStatus.cs new file mode 100644 index 00000000..2e1b24f9 --- /dev/null +++ b/SafeExamBrowser.Contracts/SystemComponents/BatteryChargeStatus.cs @@ -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, + + /// + /// The battery charge is critical, i.e. below 20%. + /// + Critical, + + /// + /// The battery charge is low, i.e. below 35%. + /// + Low, + + /// + /// The battery charge is okay, i.e. above 35%. + /// + Okay + } +} diff --git a/SafeExamBrowser.Contracts/SystemComponents/ISystemComponent.cs b/SafeExamBrowser.Contracts/SystemComponents/ISystemComponent.cs index 1ad5ed72..a8d27dfc 100644 --- a/SafeExamBrowser.Contracts/SystemComponents/ISystemComponent.cs +++ b/SafeExamBrowser.Contracts/SystemComponents/ISystemComponent.cs @@ -13,17 +13,12 @@ namespace SafeExamBrowser.Contracts.SystemComponents public interface ISystemComponent where TControl : ISystemControl { /// - /// 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. /// - void Initialize(); + void Initialize(TControl control); /// - /// Registers the taskbar control for the system component. - /// - void RegisterControl(TControl control); - - /// - /// 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. /// void Terminate(); } diff --git a/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemControl.cs b/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemControl.cs index e1ff5b05..467ea4f2 100644 --- a/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemControl.cs +++ b/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemControl.cs @@ -10,6 +10,11 @@ namespace SafeExamBrowser.Contracts.UserInterface.Taskbar { public interface ISystemControl { + /// + /// Closes any pop-up windows associated with this control. + /// + void Close(); + /// /// Sets the tooltip text of the system control. /// diff --git a/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemPowerSupplyControl.cs b/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemPowerSupplyControl.cs index 53c3d3cc..a02a357b 100644 --- a/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemPowerSupplyControl.cs +++ b/SafeExamBrowser.Contracts/UserInterface/Taskbar/ISystemPowerSupplyControl.cs @@ -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 { /// - /// Sets the current charge of the system battery, if available. 0.0 means the battery is empty, 1.0 means it's - /// fully charged. Pass null to indicate that the computer system has no battery. + /// Sets the current charge of the system battery: 0.0 means the battery is empty, 1.0 means it's fully charged. /// - void SetBatteryCharge(double? percentage); + void SetBatteryCharge(double charge, BatteryChargeStatus status); /// /// Sets the power supply status, i.e. whether the computer system is connected to the power grid or not. /// void SetPowerGridConnection(bool connected); + + /// + /// Warns the user that the battery charge is critical. + /// + void ShowCriticalBatteryWarning(string warning); + + /// + /// Indicates the user that the battery charge is low. + /// + void ShowLowBatteryInfo(string info); } } diff --git a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs index d10e2c27..b72b8fcd 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs @@ -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); } } diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml index 73fd5e5a..fa38af67 100644 --- a/SafeExamBrowser.Core/I18n/Text.xml +++ b/SafeExamBrowser.Core/I18n/Text.xml @@ -1,32 +1,37 @@  - Open Console - Application Log - An unexpected error occurred during the shutdown procedure! Please consult the application log for more information... - Shutdown Error - An unexpected error occurred during the startup procedure! Please consult the application log for more information... - Startup Error - About Safe Exam Browser - Application Log - Emptying clipboard - Initializing browser - Initializing process monitoring - Initializing taskbar - Initializing window monitoring - Initializing working area - Restoring working area - Initiating shutdown procedure - Starting event handling - Starting keyboard interception - Starting mouse interception - Initiating startup procedure - Stopping event handling - Stopping keyboard interception - Stopping mouse interception - Stopping process monitoring - Stopping window monitoring - Terminating browser - Waiting for Windows explorer to start up - Waiting for Windows explorer to shut down - Version + Open Console + Application Log + An unexpected error occurred during the shutdown procedure! Please consult the application log for more information... + Shutdown Error + An unexpected error occurred during the startup procedure! Please consult the application log for more information... + Startup Error + About Safe Exam Browser + Application Log + Emptying clipboard + Initializing browser + Initializing process monitoring + Initializing taskbar + Initializing window monitoring + Initializing working area + Restoring working area + Initiating shutdown procedure + Starting event handling + Starting keyboard interception + Starting mouse interception + Initiating startup procedure + Stopping event handling + Stopping keyboard interception + Stopping mouse interception + Stopping process monitoring + Stopping window monitoring + Terminating browser + Waiting for Windows explorer to start up + Waiting for Windows explorer to shut down + Charging... (%%CHARGE%%%) + Fully charged (%%CHARGE%%%) + The battery charge is critically low. Please connect your computer to a power supply! + The battery charge is getting low. Consider connecting your computer to a power supply in time... + %%HOURS%%h %%MINUTES%%min remaining (%%CHARGE%%%) + Version \ No newline at end of file diff --git a/SafeExamBrowser.SystemComponents/PowerSupply.cs b/SafeExamBrowser.SystemComponents/PowerSupply.cs index 9641075f..9efe70cc 100644 --- a/SafeExamBrowser.SystemComponents/PowerSupply.cs +++ b/SafeExamBrowser.SystemComponents/PowerSupply.cs @@ -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 { + 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); } } } diff --git a/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj b/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj index cfd6c74e..80d5eb7f 100644 --- a/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj +++ b/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj @@ -50,6 +50,7 @@ + diff --git a/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml b/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml index c81c6a65..50ef9ceb 100644 --- a/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml +++ b/SafeExamBrowser.UserInterface/Controls/ApplicationButton.xaml @@ -9,7 +9,7 @@ - + diff --git a/SafeExamBrowser.UserInterface/Controls/NotificationButton.xaml b/SafeExamBrowser.UserInterface/Controls/NotificationButton.xaml index 5b826044..a8deeb95 100644 --- a/SafeExamBrowser.UserInterface/Controls/NotificationButton.xaml +++ b/SafeExamBrowser.UserInterface/Controls/NotificationButton.xaml @@ -8,7 +8,7 @@ - + diff --git a/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml b/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml index dbbd7983..fca76ce7 100644 --- a/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml +++ b/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml @@ -8,19 +8,46 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml.cs index 13b8fd6a..2c19e22b 100644 --- a/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml.cs +++ b/SafeExamBrowser.UserInterface/Controls/PowerSupplyControl.xaml.cs @@ -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"); } } } diff --git a/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj b/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj index 0799683a..c172c8ab 100644 --- a/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj +++ b/SafeExamBrowser.UserInterface/SafeExamBrowser.UserInterface.csproj @@ -164,7 +164,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/SafeExamBrowser.UserInterface/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface/Taskbar.xaml.cs index adaf30cb..f4e7db12 100644 --- a/SafeExamBrowser.UserInterface/Taskbar.xaml.cs +++ b/SafeExamBrowser.UserInterface/Taskbar.xaml.cs @@ -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(); + } + } + } } } diff --git a/SafeExamBrowser.UserInterface/Styles/ButtonStyles.xaml b/SafeExamBrowser.UserInterface/Templates/Buttons.xaml similarity index 100% rename from SafeExamBrowser.UserInterface/Styles/ButtonStyles.xaml rename to SafeExamBrowser.UserInterface/Templates/Buttons.xaml diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs index a329bf1b..23238abc 100644 --- a/SafeExamBrowser/CompositionRoot.cs +++ b/SafeExamBrowser/CompositionRoot.cs @@ -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);