SEBWIN-303: Started implementing audio control.

This commit is contained in:
dbuechel 2019-08-15 10:46:47 +02:00
parent b71529da31
commit 768336e381
28 changed files with 575 additions and 5 deletions

View file

@ -34,6 +34,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<INotificationController> aboutController;
private Mock<INotificationInfo> logInfo;
private Mock<INotificationController> logController;
private Mock<ISystemComponent<ISystemAudioControl>> audio;
private Mock<ISystemComponent<ISystemKeyboardLayoutControl>> keyboardLayout;
private Mock<ISystemComponent<ISystemPowerSupplyControl>> powerSupply;
private Mock<ISystemComponent<ISystemWirelessNetworkControl>> wirelessNetwork;
@ -55,6 +56,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
aboutController = new Mock<INotificationController>();
logInfo = new Mock<INotificationInfo>();
logController = new Mock<INotificationController>();
audio = new Mock<ISystemComponent<ISystemAudioControl>>();
keyboardLayout = new Mock<ISystemComponent<ISystemKeyboardLayoutControl>>();
powerSupply = new Mock<ISystemComponent<ISystemPowerSupplyControl>>();
wirelessNetwork = new Mock<ISystemComponent<ISystemWirelessNetworkControl>>();
@ -76,6 +78,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
aboutController.Object,
logInfo.Object,
logController.Object,
audio.Object,
keyboardLayout.Object,
powerSupply.Object,
wirelessNetwork.Object,

View file

@ -67,6 +67,7 @@ namespace SafeExamBrowser.Client
private IProcessMonitor processMonitor;
private INativeMethods nativeMethods;
private IRuntimeProxy runtimeProxy;
private ISystemComponent<ISystemAudioControl> audio;
private ISystemComponent<ISystemKeyboardLayoutControl> keyboardLayout;
private ISystemComponent<ISystemPowerSupplyControl> powerSupply;
private ISystemComponent<ISystemWirelessNetworkControl> wirelessNetwork;
@ -93,6 +94,7 @@ namespace SafeExamBrowser.Client
InitializeText();
actionCenter = BuildActionCenter();
audio = new Audio(new ModuleLogger(logger, nameof(Audio)), text);
keyboardLayout = new KeyboardLayout(new ModuleLogger(logger, nameof(KeyboardLayout)), text);
messageBox = BuildMessageBox();
powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)), text);
@ -277,6 +279,7 @@ namespace SafeExamBrowser.Client
aboutController,
logInfo,
logController,
audio,
keyboardLayout,
powerSupply,
wirelessNetwork,

View file

@ -30,6 +30,7 @@ namespace SafeExamBrowser.Client.Operations
private INotificationController aboutController;
private INotificationInfo logInfo;
private INotificationController logController;
private ISystemComponent<ISystemAudioControl> audio;
private ISystemComponent<ISystemKeyboardLayoutControl> keyboardLayout;
private ISystemComponent<ISystemPowerSupplyControl> powerSupply;
private ISystemComponent<ISystemWirelessNetworkControl> wirelessNetwork;
@ -52,6 +53,7 @@ namespace SafeExamBrowser.Client.Operations
INotificationController aboutController,
INotificationInfo logInfo,
INotificationController logController,
ISystemComponent<ISystemAudioControl> audio,
ISystemComponent<ISystemKeyboardLayoutControl> keyboardLayout,
ISystemComponent<ISystemPowerSupplyControl> powerSupply,
ISystemComponent<ISystemWirelessNetworkControl> wirelessNetwork,
@ -70,6 +72,7 @@ namespace SafeExamBrowser.Client.Operations
this.logger = logger;
this.logInfo = logInfo;
this.logController = logController;
this.audio = audio;
this.keyboardLayout = keyboardLayout;
this.powerSupply = powerSupply;
this.systemInfo = systemInfo;
@ -128,6 +131,7 @@ namespace SafeExamBrowser.Client.Operations
actionCenter.InitializeText(text);
InitializeAboutNotificationForActionCenter();
InitializeAudioForActionCenter();
InitializeClockForActionCenter();
InitializeLogNotificationForActionCenter();
InitializeKeyboardLayoutForActionCenter();
@ -148,11 +152,12 @@ namespace SafeExamBrowser.Client.Operations
taskbar.InitializeText(text);
InitializeAboutNotificationForTaskbar();
InitializeClockForTaskbar();
InitializeLogNotificationForTaskbar();
InitializeKeyboardLayoutForTaskbar();
InitializeWirelessNetworkForTaskbar();
InitializePowerSupplyForTaskbar();
InitializeWirelessNetworkForTaskbar();
InitializeAudioForTaskbar();
InitializeKeyboardLayoutForTaskbar();
InitializeClockForTaskbar();
}
else
{
@ -162,6 +167,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeSystemComponents()
{
audio.Initialize();
keyboardLayout.Initialize();
powerSupply.Initialize();
wirelessNetwork.Initialize();
@ -189,6 +195,28 @@ namespace SafeExamBrowser.Client.Operations
}
}
private void InitializeAudioForActionCenter()
{
if (actionCenterSettings.ShowAudio)
{
var control = uiFactory.CreateAudioControl(Location.ActionCenter);
audio.Register(control);
actionCenter.AddSystemControl(control);
}
}
private void InitializeAudioForTaskbar()
{
if (taskbarSettings.ShowAudio)
{
var control = uiFactory.CreateAudioControl(Location.Taskbar);
audio.Register(control);
taskbar.AddSystemControl(control);
}
}
private void InitializeClockForActionCenter()
{
actionCenter.ShowClock = actionCenterSettings.ShowClock;
@ -308,6 +336,7 @@ namespace SafeExamBrowser.Client.Operations
private void TerminateSystemComponents()
{
audio.Terminate();
keyboardLayout.Terminate();
powerSupply.Terminate();
wirelessNetwork.Terminate();

View file

@ -20,6 +20,15 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
private void MapAudio(Settings settings, object value)
{
if (value is bool show)
{
settings.ActionCenter.ShowAudio = show;
settings.Taskbar.ShowAudio = show;
}
}
private void MapClock(Settings settings, object value)
{
if (value is bool show)

View file

@ -189,6 +189,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
switch (key)
{
case Keys.UserInterface.ShowAudio:
MapAudio(settings, value);
break;
case Keys.UserInterface.ShowKeyboardLayout:
MapKeyboardLayout(settings, value);
break;

View file

@ -121,6 +121,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal static class UserInterface
{
internal const string ShowAudio = "audioControlEnabled";
internal const string ShowClock = "showTime";
internal const string ShowKeyboardLayout = "showInputLanguage";
internal const string ShowWirelessNetwork = "allowWlan";

View file

@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
/// </summary>
public bool ShowApplicationLog { get; set; }
/// <summary>
/// Determines whether the system control for audio is accessible via the action center.
/// </summary>
public bool ShowAudio { get; set; }
/// <summary>
/// Determines whether the current date and time will be rendered in the action center.
/// </summary>

View file

@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
/// </summary>
public bool ShowApplicationLog { get; set; }
/// <summary>
/// Determines whether the system control for audio is accessible via the taskbar.
/// </summary>
public bool ShowAudio { get; set; }
/// <summary>
/// Determines whether the current date and time will be rendered in the taskbar.
/// </summary>

View file

@ -107,6 +107,9 @@ namespace SafeExamBrowser.Contracts.I18n
PasswordDialog_SettingsPasswordRequiredTitle,
RuntimeWindow_ApplicationRunning,
Shell_QuitButton,
SystemControl_AudioDeviceInfo,
SystemControl_AudioDeviceInfoMuted,
SystemControl_AudioDeviceNotFound,
SystemControl_BatteryCharged,
SystemControl_BatteryCharging,
SystemControl_BatteryChargeCriticalWarning,

View file

@ -212,6 +212,7 @@
<Compile Include="UserInterface\Shell\Events\NotificationControlClickedEventHandler.cs" />
<Compile Include="UserInterface\Shell\IActionCenter.cs" />
<Compile Include="UserInterface\Shell\IActionCenterActivator.cs" />
<Compile Include="UserInterface\Shell\ISystemAudioControl.cs" />
<Compile Include="UserInterface\Shell\Location.cs" />
<Compile Include="UserInterface\Windows\Events\WindowClosingEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\WirelessNetworkSelectedEventHandler.cs" />

View file

@ -11,8 +11,8 @@ using SafeExamBrowser.Contracts.UserInterface.Shell;
namespace SafeExamBrowser.Contracts.SystemComponents
{
/// <summary>
/// Defines the functionality of a system component (e.g. the power supply). Each system component will get an <see cref="ISystemControl"/>
/// assigned, via which the user is able to interact with or get information about the underlying system component.
/// Defines the functionality of a system component (e.g. the power supply). Each system component can get multiple <see cref="ISystemControl"/>
/// assigned, which in turn allow the user to interact with or get information about the underlying system component.
/// </summary>
public interface ISystemComponent<TControl> where TControl : ISystemControl
{

View file

@ -33,6 +33,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
IApplicationControl CreateApplicationControl(IApplicationInfo info, Location location);
/// <summary>
/// Creates a system control which allows to change the audio settings of the computer.
/// </summary>
ISystemAudioControl CreateAudioControl(Location location);
/// <summary>
/// Creates a new browser window loaded with the given browser control and settings.
/// </summary>

View file

@ -0,0 +1,36 @@
/*
* 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
{
/// <summary>
/// The control of the audio system component.
/// </summary>
public interface ISystemAudioControl : ISystemControl
{
/// <summary>
/// Defines whether the computer has an audio output device.
/// </summary>
bool HasOutputDevice { set; }
/// <summary>
/// Indicates whether the current output device is muted.
/// </summary>
bool OutputDeviceMuted { set; }
/// <summary>
/// Shows the name of the currently active audio output device.
/// </summary>
string OutputDeviceName { set; }
/// <summary>
/// Shows the current audio output volume, where <c>0.0</c> is the lowest and <c>1.0</c> the highest possible value.
/// </summary>
double OutputDeviceVolume { set; }
}
}

View file

@ -279,6 +279,15 @@
<Entry key="Shell_QuitButton">
Terminate Session
</Entry>
<Entry key="SystemControl_AudioDeviceInfo">
%%NAME%%: %%VOLUME%%%
</Entry>
<Entry key="SystemControl_AudioDeviceInfoMuted">
%%NAME%%: Muted
</Entry>
<Entry key="SystemControl_AudioDeviceNotFound">
No audio device found
</Entry>
<Entry key="SystemControl_BatteryCharging">
Plugged in, charging... (%%CHARGE%%%)
</Entry>

View file

@ -0,0 +1,162 @@
/*
* 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/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using NAudio.CoreAudioApi;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface.Shell;
namespace SafeExamBrowser.SystemComponents
{
public class Audio : ISystemComponent<ISystemAudioControl>
{
private readonly object @lock = new object();
private MMDevice audioDevice;
private string audioDeviceShortName;
private List<ISystemAudioControl> controls;
private ILogger logger;
private IText text;
public Audio(ILogger logger, IText text)
{
this.controls = new List<ISystemAudioControl>();
this.logger = logger;
this.text = text;
}
public void Initialize()
{
if (TryLoadAudioDevice())
{
InitializeAudioDevice();
InitializeSettings();
}
else
{
logger.Warn("Could not find an active audio device!");
}
}
public void Register(ISystemAudioControl control)
{
lock (@lock)
{
controls.Add(control);
}
UpdateControls();
}
public void Terminate()
{
if (audioDevice != default(MMDevice))
{
audioDevice.AudioEndpointVolume.OnVolumeNotification -= AudioEndpointVolume_OnVolumeNotification;
audioDevice.Dispose();
logger.Info("Stopped monitoring the audio device.");
}
foreach (var control in controls)
{
control.Close();
}
}
private bool TryLoadAudioDevice()
{
using (var enumerator = new MMDeviceEnumerator())
{
if (enumerator.HasDefaultAudioEndpoint(DataFlow.Render, Role.Console))
{
audioDevice = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console);
}
else
{
audioDevice = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active).FirstOrDefault();
}
}
return audioDevice != default(MMDevice);
}
private void InitializeAudioDevice()
{
logger.Info($"Found '{audioDevice}' to be the active audio device.");
audioDevice.AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification;
audioDeviceShortName = audioDevice.FriendlyName.Length > 25 ? audioDevice.FriendlyName.Split(' ').First() : audioDevice.FriendlyName;
logger.Info("Started monitoring the audio device.");
}
private void InitializeSettings()
{
// TODO: Mute on startup & initial volume!
}
private void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data)
{
lock (@lock)
{
var info = BuildInfoText(data.MasterVolume, data.Muted);
foreach (var control in controls)
{
control.OutputDeviceMuted = data.Muted;
control.OutputDeviceVolume = data.MasterVolume;
control.SetInformation(info);
}
}
}
private void UpdateControls()
{
lock (@lock)
{
try
{
var info = BuildInfoText(audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar, audioDevice.AudioEndpointVolume.Mute);
foreach (var control in controls)
{
if (audioDevice != default(MMDevice))
{
control.HasOutputDevice = true;
control.OutputDeviceMuted = audioDevice.AudioEndpointVolume.Mute;
control.OutputDeviceName = audioDevice.FriendlyName;
control.OutputDeviceVolume = audioDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
control.SetInformation(info);
}
else
{
control.HasOutputDevice = false;
control.SetInformation(text.Get(TextKey.SystemControl_AudioDeviceNotFound));
}
}
}
catch (Exception e)
{
logger.Error("Failed to update audio device status!", e);
}
}
}
private string BuildInfoText(float volume, bool muted)
{
var info = text.Get(muted ? TextKey.SystemControl_AudioDeviceInfoMuted : TextKey.SystemControl_AudioDeviceInfo);
info = info.Replace("%%NAME%%", audioDeviceShortName);
info = info.Replace("%%VOLUME%%", Convert.ToString(volume * 100));
return info;
}
}
}

View file

@ -48,6 +48,9 @@
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="NAudio, Version=1.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.9.0\lib\net35\NAudio.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="SimpleWifi, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@ -59,6 +62,7 @@
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="Audio.cs" />
<Compile Include="KeyboardLayoutDefinition.cs" />
<Compile Include="KeyboardLayout.cs" />
<Compile Include="PowerSupply.cs" />
@ -74,5 +78,8 @@
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NAudio" version="1.9.0" targetFramework="net472" />
</packages>

View file

@ -0,0 +1,37 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskbarAudioControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Buttons.xaml" />
<ResourceDictionary Source="../Templates/Colors.xaml" />
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="AudioDeviceName" Margin="5" TextAlignment="Center" />
<StackPanel Orientation="Horizontal" Height="40">
<Button x:Name="Mute" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" Width="40">
<ContentControl x:Name="PopupIcon" />
</Button>
<Slider x:Name="Volume" Grid.Column="1" Orientation="Horizontal" TickFrequency="1" IsSnapToTickEnabled="True" Maximum="100" VerticalAlignment="Center" Width="250" />
<TextBlock Grid.Column="2" FontWeight="DemiBold" FontSize="16" Text="{Binding ElementName=Volume, Path=Value}" TextAlignment="Center" VerticalAlignment="Center" Width="40" />
</StackPanel>
</StackPanel>
</Border>
</Popup>
<Button x:Name="Button" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True" Width="40">
<ContentControl x:Name="TaskbarIcon" />
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,135 @@
/*
* 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/.
*/
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using SafeExamBrowser.Contracts.UserInterface.Shell;
using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class TaskbarAudioControl : UserControl, ISystemAudioControl
{
private bool muted;
private XamlIconResource MutedIcon;
private XamlIconResource NoDeviceIcon;
public TaskbarAudioControl()
{
InitializeComponent();
InitializeAudioControl();
}
public bool HasOutputDevice
{
set
{
Dispatcher.InvokeAsync(() =>
{
Button.IsEnabled = value;
if (!value)
{
TaskbarIcon.Content = IconResourceLoader.Load(NoDeviceIcon);
}
});
}
}
public bool OutputDeviceMuted
{
set
{
Dispatcher.InvokeAsync(() =>
{
muted = value;
if (value)
{
PopupIcon.Content = IconResourceLoader.Load(MutedIcon);
TaskbarIcon.Content = IconResourceLoader.Load(MutedIcon);
}
else
{
TaskbarIcon.Content = LoadIcon(Volume.Value / 100);
}
});
}
}
public string OutputDeviceName
{
set
{
Dispatcher.InvokeAsync(() => AudioDeviceName.Text = value);
}
}
public double OutputDeviceVolume
{
set
{
Dispatcher.InvokeAsync(() =>
{
Volume.Value = Math.Round(value * 100);
if (!muted)
{
PopupIcon.Content = LoadIcon(value);
TaskbarIcon.Content = LoadIcon(value);
}
});
}
}
public void Close()
{
Popup.IsOpen = false;
}
public void SetInformation(string text)
{
Dispatcher.InvokeAsync(() => Button.ToolTip = text);
}
private void InitializeAudioControl()
{
var originalBrush = Button.Background;
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
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));
Popup.Opened += (o, args) =>
{
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
};
Popup.Closed += (o, args) =>
{
Background = originalBrush;
Button.Background = originalBrush;
};
}
private UIElement LoadIcon(double volume)
{
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_{icon}.xaml");
var resource = new XamlIconResource(uri);
return IconResourceLoader.Load(resource);
}
}
}

View file

@ -0,0 +1,12 @@
<Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<fa:ImageAwesome Icon="VolumeOff" HorizontalAlignment="Left" />
<Canvas Width="100" Height="100">
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 60,35 L 90,65"/>
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 60,65 L 90,35"/>
</Canvas>
</Grid>
</Viewbox>

View file

@ -0,0 +1,16 @@
<Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Margin="1">
<Grid Margin="5,0,0,0">
<fa:ImageAwesome Icon="VolumeOff" Foreground="DarkGray" HorizontalAlignment="Left" />
<Canvas Width="100" Height="100">
<Path StrokeThickness="5.0" Stroke="DarkGray" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 55,35 C 65,45 65,55 55,65"/>
<Path StrokeThickness="5.0" Stroke="DarkGray" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 65,25 C 80,35 80,65 65,75"/>
<Path StrokeThickness="5.0" Stroke="DarkGray" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 75,15 C 95,30 95,70 75,85"/>
</Canvas>
</Grid>
<fa:ImageAwesome Foreground="Red" Icon="Ban" Opacity="0.3" />
</Grid>
</Viewbox>

View file

@ -0,0 +1,13 @@
<Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<fa:ImageAwesome Icon="VolumeOff" HorizontalAlignment="Left" />
<Canvas Width="100" Height="100">
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 55,35 C 65,45 65,55 55,65"/>
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 65,25 C 80,35 80,65 65,75"/>
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 75,15 C 95,30 95,70 75,85"/>
</Canvas>
</Grid>
</Viewbox>

View file

@ -0,0 +1,13 @@
<Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<fa:ImageAwesome Icon="VolumeOff" HorizontalAlignment="Left" />
<Canvas Width="100" Height="100">
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 55,35 C 65,45 65,55 55,65"/>
<Path StrokeThickness="5.0" Stroke="DarkGray" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 65,25 C 80,35 80,65 65,75"/>
<Path StrokeThickness="5.0" Stroke="DarkGray" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 75,15 C 95,30 95,70 75,85"/>
</Canvas>
</Grid>
</Viewbox>

View file

@ -0,0 +1,13 @@
<Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<fa:ImageAwesome Icon="VolumeOff" HorizontalAlignment="Left" />
<Canvas Width="100" Height="100">
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 55,35 C 65,45 65,55 55,65"/>
<Path StrokeThickness="5.0" Stroke="Black" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 65,25 C 80,35 80,65 65,75"/>
<Path StrokeThickness="5.0" Stroke="DarkGray" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="M 75,15 C 95,30 95,70 75,85"/>
</Canvas>
</Grid>
</Viewbox>

View file

@ -110,6 +110,9 @@
<Compile Include="Controls\TaskbarApplicationInstanceButton.xaml.cs">
<DependentUpon>TaskbarApplicationInstanceButton.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarAudioControl.xaml.cs">
<DependentUpon>TaskbarAudioControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarClock.xaml.cs">
<DependentUpon>TaskbarClock.xaml</DependentUpon>
</Compile>
@ -212,6 +215,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\TaskbarAudioControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\TaskbarClock.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -256,6 +263,26 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Resource Include="Images\AudioNoDevice.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Resource Include="Images\Audio_66.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Resource Include="Images\Audio_33.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Resource Include="Images\Audio_100.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Resource Include="Images\AudioMuted.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="Templates\ScrollViewers.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View file

@ -52,6 +52,19 @@ namespace SafeExamBrowser.UserInterface.Desktop
}
}
public ISystemAudioControl CreateAudioControl(Location location)
{
// TODO
//if (location == Location.ActionCenter)
//{
// return new ActionCenterAudioControl();
//}
//else
{
return new TaskbarAudioControl();
}
}
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)
{
return new BrowserWindow(control, settings, isMainWindow, text);

View file

@ -52,6 +52,12 @@ namespace SafeExamBrowser.UserInterface.Mobile
}
}
public ISystemAudioControl CreateAudioControl(Location location)
{
// TODO
throw new System.NotImplementedException();
}
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)
{
return new BrowserWindow(control, settings, isMainWindow, text);

Binary file not shown.