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);
}
}