diff --git a/SafeExamBrowser.Contracts/I18n/TextKey.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs index abd23e2c..eeb9d86f 100644 --- a/SafeExamBrowser.Contracts/I18n/TextKey.cs +++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs @@ -109,7 +109,9 @@ namespace SafeExamBrowser.Contracts.I18n Shell_QuitButton, SystemControl_AudioDeviceInfo, SystemControl_AudioDeviceInfoMuted, + SystemControl_AudioDeviceMuteTooltip, SystemControl_AudioDeviceNotFound, + SystemControl_AudioDeviceUnmuteTooltip, SystemControl_BatteryCharged, SystemControl_BatteryCharging, SystemControl_BatteryChargeCriticalWarning, diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index dae5766e..993cd2c1 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -208,6 +208,8 @@ + + diff --git a/SafeExamBrowser.Contracts/UserInterface/Shell/Events/AudioMuteRequestedEventHandler.cs b/SafeExamBrowser.Contracts/UserInterface/Shell/Events/AudioMuteRequestedEventHandler.cs new file mode 100644 index 00000000..36e18f8a --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/Shell/Events/AudioMuteRequestedEventHandler.cs @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 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.UserInterface.Shell.Events +{ + /// + /// Indicates that the user would like to change the audio mute status to the given value. + /// + public delegate void AudioMuteRequestedEventHandler(bool mute); +} diff --git a/SafeExamBrowser.Contracts/UserInterface/Shell/Events/AudioVolumeSelectedEventHandler.cs b/SafeExamBrowser.Contracts/UserInterface/Shell/Events/AudioVolumeSelectedEventHandler.cs new file mode 100644 index 00000000..ae0a88dc --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/Shell/Events/AudioVolumeSelectedEventHandler.cs @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2019 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.UserInterface.Shell.Events +{ + /// + /// Indicates that the user would like to set the audio volume to the given value, where 0.0 is the lowest and 1.0 the highest + /// possible value. + /// + public delegate void AudioVolumeSelectedEventHandler(double volume); +} diff --git a/SafeExamBrowser.Contracts/UserInterface/Shell/ISystemAudioControl.cs b/SafeExamBrowser.Contracts/UserInterface/Shell/ISystemAudioControl.cs index f78b11ed..0ee4f2a9 100644 --- a/SafeExamBrowser.Contracts/UserInterface/Shell/ISystemAudioControl.cs +++ b/SafeExamBrowser.Contracts/UserInterface/Shell/ISystemAudioControl.cs @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; + namespace SafeExamBrowser.Contracts.UserInterface.Shell { /// @@ -32,5 +34,15 @@ namespace SafeExamBrowser.Contracts.UserInterface.Shell /// Shows the current audio output volume, where 0.0 is the lowest and 1.0 the highest possible value. /// double OutputDeviceVolume { set; } + + /// + /// Event fired when the user requests to mute the current output device. + /// + event AudioMuteRequestedEventHandler MuteRequested; + + /// + /// Event fired when the user requests to set the volume of the current output device. + /// + event AudioVolumeSelectedEventHandler VolumeSelected; } } diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 9445d13e..77795678 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -285,9 +285,15 @@ %%NAME%%: Muted + + Click to mute audio + No audio device found + + Click to unmute audio + Plugged in, charging... (%%CHARGE%%%) diff --git a/SafeExamBrowser.SystemComponents/Audio.cs b/SafeExamBrowser.SystemComponents/Audio.cs index 93b78114..889d2c6f 100644 --- a/SafeExamBrowser.SystemComponents/Audio.cs +++ b/SafeExamBrowser.SystemComponents/Audio.cs @@ -49,6 +49,9 @@ namespace SafeExamBrowser.SystemComponents public void Register(ISystemAudioControl control) { + control.MuteRequested += Control_MuteRequested; + control.VolumeSelected += Control_VolumeSelected; + lock (@lock) { controls.Add(control); @@ -108,6 +111,8 @@ namespace SafeExamBrowser.SystemComponents { var info = BuildInfoText(data.MasterVolume, data.Muted); + logger.Debug($"Detected audio device change: Volume {data.MasterVolume * 100}, {(data.Muted ? "muted" : "unmuted")}"); + foreach (var control in controls) { control.OutputDeviceMuted = data.Muted; @@ -117,6 +122,16 @@ namespace SafeExamBrowser.SystemComponents } } + private void Control_MuteRequested(bool mute) + { + audioDevice.AudioEndpointVolume.Mute = mute; + } + + private void Control_VolumeSelected(double volume) + { + audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar = (float) volume; + } + private void UpdateControls() { lock (@lock) @@ -154,7 +169,7 @@ namespace SafeExamBrowser.SystemComponents var info = text.Get(muted ? TextKey.SystemControl_AudioDeviceInfoMuted : TextKey.SystemControl_AudioDeviceInfo); info = info.Replace("%%NAME%%", audioDeviceShortName); - info = info.Replace("%%VOLUME%%", Convert.ToString(volume * 100)); + info = info.Replace("%%VOLUME%%", Convert.ToString(Math.Round(volume * 100))); return info; } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml index 505b3356..ae299a64 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml @@ -21,11 +21,13 @@ - - - + + diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs index 3f036f42..0c944b9b 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarAudioControl.xaml.cs @@ -10,20 +10,29 @@ using System; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Media; +using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.UserInterface.Shell; +using SafeExamBrowser.Contracts.UserInterface.Shell.Events; using SafeExamBrowser.UserInterface.Shared.Utilities; namespace SafeExamBrowser.UserInterface.Desktop.Controls { public partial class TaskbarAudioControl : UserControl, ISystemAudioControl { + private readonly IText text; private bool muted; private XamlIconResource MutedIcon; private XamlIconResource NoDeviceIcon; - public TaskbarAudioControl() + public event AudioMuteRequestedEventHandler MuteRequested; + public event AudioVolumeSelectedEventHandler VolumeSelected; + + public TaskbarAudioControl(IText text) { + this.text = text; + InitializeComponent(); InitializeAudioControl(); } @@ -54,11 +63,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls if (value) { + MuteButton.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceUnmuteTooltip); PopupIcon.Content = IconResourceLoader.Load(MutedIcon); TaskbarIcon.Content = IconResourceLoader.Load(MutedIcon); } else { + MuteButton.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceMuteTooltip); TaskbarIcon.Content = LoadIcon(Volume.Value / 100); } }); @@ -79,7 +90,9 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls { Dispatcher.InvokeAsync(() => { + Volume.ValueChanged -= Volume_ValueChanged; Volume.Value = Math.Round(value * 100); + Volume.ValueChanged += Volume_ValueChanged; if (!muted) { @@ -106,9 +119,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); + MuteButton.Click += (o, args) => MuteRequested?.Invoke(!muted); MutedIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/AudioMuted.xaml")); NoDeviceIcon = new XamlIconResource(new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/AudioNoDevice.xaml")); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); + Volume.ValueChanged += Volume_ValueChanged; Popup.Opened += (o, args) => { @@ -123,6 +138,22 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls }; } + private void Volume_DragStarted(object sender, DragStartedEventArgs e) + { + Volume.ValueChanged -= Volume_ValueChanged; + } + + private void Volume_DragCompleted(object sender, DragCompletedEventArgs e) + { + VolumeSelected?.Invoke(Volume.Value / 100); + Volume.ValueChanged += Volume_ValueChanged; + } + + private void Volume_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + VolumeSelected?.Invoke(Volume.Value / 100); + } + private UIElement LoadIcon(double volume) { var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33"); diff --git a/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs index 5632742f..394253a3 100644 --- a/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Desktop/UserInterfaceFactory.cs @@ -61,7 +61,7 @@ namespace SafeExamBrowser.UserInterface.Desktop //} //else { - return new TaskbarAudioControl(); + return new TaskbarAudioControl(text); } }