Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Show recording devices in UI #1592

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,6 @@ _pkginfo.txt
.chocolatey/tools/Release.zip
/artifacts/sideload
/Tools

# JetBrains idea config
.idea
70 changes: 50 additions & 20 deletions EarTrumpet/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public partial class App
public static TimeSpan Duration => s_appTimer.Elapsed;

public FlyoutWindow FlyoutWindow { get; private set; }
public DeviceCollectionViewModel CollectionViewModel { get; private set; }
public DeviceCollectionViewModel PlaybackCollectionViewModel { get; private set; }
public DeviceCollectionViewModel RecordingCollectionViewModel { get; private set; }

private static readonly Stopwatch s_appTimer = Stopwatch.StartNew();
private FlyoutViewModel _flyoutViewModel;
Expand Down Expand Up @@ -79,15 +80,21 @@ private void ContinueStartup()
{
((UI.Themes.Manager)Resources["ThemeManager"]).Load();

var deviceManager = WindowsAudioFactory.Create(AudioDeviceKind.Playback);
deviceManager.Loaded += (_, __) => CompleteStartup();
CollectionViewModel = new DeviceCollectionViewModel(deviceManager, Settings);
var playbackDeviceManager = WindowsAudioFactory.Create(AudioDeviceKind.Playback);
playbackDeviceManager.Loaded += (_, __) => CompleteStartup();
PlaybackCollectionViewModel = new DeviceCollectionViewModel(playbackDeviceManager, Settings);

_trayIcon = new ShellNotifyIcon(new TaskbarIconSource(CollectionViewModel, Settings));
_trayIcon = new ShellNotifyIcon(new TaskbarIconSource(PlaybackCollectionViewModel, Settings));
Exit += (_, __) => _trayIcon.IsVisible = false;
CollectionViewModel.TrayPropertyChanged += () => _trayIcon.SetTooltip(CollectionViewModel.GetTrayToolTip());
PlaybackCollectionViewModel.TrayPropertyChanged +=
() => _trayIcon.SetTooltip(PlaybackCollectionViewModel.GetTrayToolTip());

_flyoutViewModel = new FlyoutViewModel(CollectionViewModel, () => _trayIcon.SetFocus(), Settings);
var recordingDeviceManager = WindowsAudioFactory.Create(AudioDeviceKind.Recording);
RecordingCollectionViewModel = new DeviceCollectionViewModel(recordingDeviceManager, Settings);
RecordingCollectionViewModel.TrayPropertyChanged += delegate {};

_flyoutViewModel = new FlyoutViewModel(PlaybackCollectionViewModel, RecordingCollectionViewModel,
() => _trayIcon.SetFocus(), Settings);
FlyoutWindow = new FlyoutWindow(_flyoutViewModel);
// Initialize the FlyoutWindow last because its Show/Hide cycle will pump messages, causing UI frames
// to be executed, breaking the assumption that startup is complete.
Expand All @@ -109,13 +116,14 @@ private void CompleteStartup()
Settings.SettingsHotkeyTyped += () => _settingsWindow.OpenOrBringToFront();
Settings.AbsoluteVolumeUpHotkeyTyped += AbsoluteVolumeIncrement;
Settings.AbsoluteVolumeDownHotkeyTyped += AbsoluteVolumeDecrement;
Settings.ToggleShowDeviceTypeSwitchInFlyoutHotkeyTyped += ToggleShowDeviceTypeSwitchInFlyout;
Settings.RegisterHotkeys();

_trayIcon.PrimaryInvoke += (_, type) => _flyoutViewModel.OpenFlyout(type);
_trayIcon.SecondaryInvoke += (_, args) => _trayIcon.ShowContextMenu(GetTrayContextMenuItems(), args.Point);
_trayIcon.TertiaryInvoke += (_, __) => CollectionViewModel.Default?.ToggleMute.Execute(null);
_trayIcon.TertiaryInvoke += (_, __) => PlaybackCollectionViewModel.Default?.ToggleMute.Execute(null);
_trayIcon.Scrolled += trayIconScrolled;
_trayIcon.SetTooltip(CollectionViewModel.GetTrayToolTip());
_trayIcon.SetTooltip(PlaybackCollectionViewModel.GetTrayToolTip());
_trayIcon.IsVisible = true;

DisplayFirstRunExperience();
Expand All @@ -128,8 +136,8 @@ private void trayIconScrolled(object _, int wheelDelta)
var hWndTray = WindowsTaskbar.GetTrayToolbarWindowHwnd();
var hWndTooltip = User32.SendMessage(hWndTray, User32.TB_GETTOOLTIPS, IntPtr.Zero, IntPtr.Zero);
User32.SendMessage(hWndTooltip, User32.TTM_POPUP, IntPtr.Zero, IntPtr.Zero);
CollectionViewModel.Default?.IncrementVolume(Math.Sign(wheelDelta) * 2);

PlaybackCollectionViewModel.Default?.IncrementVolume(Math.Sign(wheelDelta) * 2);
}
}

Expand Down Expand Up @@ -179,14 +187,27 @@ private void OnCriticalFontLoadFailure()
new AutoResetEvent(false).WaitOne();
}

private static List<ContextMenuItem> GetTrayContextMenuItemsFromDevices(DeviceCollectionViewModel devices)
{
return new List<ContextMenuItem>(devices.AllDevices.OrderBy(x => x.DisplayName).Select(dev =>
new ContextMenuItem
{
DisplayName = dev.DisplayName,
IsChecked = dev.Id == devices.Default?.Id,
Command = new RelayCommand(() => dev.MakeDefaultDevice()),
}));
}

private IEnumerable<ContextMenuItem> GetTrayContextMenuItems()
{
var ret = new List<ContextMenuItem>(CollectionViewModel.AllDevices.OrderBy(x => x.DisplayName).Select(dev => new ContextMenuItem
var ret = GetTrayContextMenuItemsFromDevices(PlaybackCollectionViewModel);


if (Settings.ShowRecordingDevicesInContextMenu && RecordingCollectionViewModel.AllDevices.Count > 0)
{
DisplayName = dev.DisplayName,
IsChecked = dev.Id == CollectionViewModel.Default?.Id,
Command = new RelayCommand(() => dev.MakeDefaultDevice()),
}));
ret.Add(new ContextMenuSeparator());
ret.AddRange(GetTrayContextMenuItemsFromDevices(RecordingCollectionViewModel));
}

if (!ret.Any())
{
Expand Down Expand Up @@ -275,11 +296,11 @@ private SettingsCategoryViewModel CreateAddonSettingsPage(IEarTrumpetAddonSettin
return category;
}

private Window CreateMixerExperience() => new FullWindow { DataContext = new FullWindowViewModel(CollectionViewModel) };
private Window CreateMixerExperience() => new FullWindow { DataContext = new FullWindowViewModel(PlaybackCollectionViewModel) };

private void AbsoluteVolumeIncrement()
{
foreach (var device in CollectionViewModel.AllDevices.Where(d => !d.IsMuted || d.IsAbsMuted))
foreach (var device in PlaybackCollectionViewModel.AllDevices.Where(d => !d.IsMuted || d.IsAbsMuted))
{
// in any case this device is not abs muted anymore
device.IsAbsMuted = false;
Expand All @@ -289,7 +310,7 @@ private void AbsoluteVolumeIncrement()

private void AbsoluteVolumeDecrement()
{
foreach (var device in CollectionViewModel.AllDevices.Where(d => !d.IsMuted))
foreach (var device in PlaybackCollectionViewModel.AllDevices.Where(d => !d.IsMuted))
{
// if device is not muted but will be muted by
bool wasMuted = device.IsMuted;
Expand All @@ -303,5 +324,14 @@ private void AbsoluteVolumeDecrement()
}
}
}

private void ToggleShowDeviceTypeSwitchInFlyout()
{
bool oldValue = Settings.ShowDeviceTypeSwitchInFlyout;
bool newValue = !oldValue;
Trace.WriteLine($"App ToggleShowDeviceTypeSwitchInFlyout {oldValue} is set to {newValue}");

Settings.ShowDeviceTypeSwitchInFlyout = newValue;
}
}
}
}
35 changes: 35 additions & 0 deletions EarTrumpet/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namespace EarTrumpet
public class AppSettings
{
public event EventHandler<bool> UseLegacyIconChanged;
public event EventHandler<bool> ShowDeviceTypeSwitchInFlyoutChanged;
public event Action FlyoutHotkeyTyped;
public event Action MixerHotkeyTyped;
public event Action SettingsHotkeyTyped;
public event Action AbsoluteVolumeUpHotkeyTyped;
public event Action AbsoluteVolumeDownHotkeyTyped;
public event Action ToggleShowDeviceTypeSwitchInFlyoutHotkeyTyped;

private ISettingsBag _settings = StorageFactory.GetSettings();

Expand All @@ -25,6 +27,7 @@ public void RegisterHotkeys()
HotkeyManager.Current.Register(SettingsHotkey);
HotkeyManager.Current.Register(AbsoluteVolumeUpHotkey);
HotkeyManager.Current.Register(AbsoluteVolumeDownHotkey);
HotkeyManager.Current.Register(ToggleShowDeviceTypeSwitchInFlyoutHotkey);

HotkeyManager.Current.KeyPressed += (hotkey) =>
{
Expand Down Expand Up @@ -53,6 +56,11 @@ public void RegisterHotkeys()
Trace.WriteLine("AppSettings AbsoluteVolumeDownHotkeyTyped");
AbsoluteVolumeDownHotkeyTyped?.Invoke();
}
else if (hotkey.Equals(ToggleShowDeviceTypeSwitchInFlyoutHotkey))
{
Trace.WriteLine("AppSettings ToggleShowDeviceTypeSwitchInFlyoutHotkeyTyped");
ToggleShowDeviceTypeSwitchInFlyoutHotkeyTyped?.Invoke();
}
};
}

Expand Down Expand Up @@ -111,6 +119,17 @@ public HotkeyData AbsoluteVolumeDownHotkey
}
}

public HotkeyData ToggleShowDeviceTypeSwitchInFlyoutHotkey
{
get => _settings.Get("ToggleShowDeviceTypeSwitchInFlyoutHotkey", new HotkeyData { });
set
{
HotkeyManager.Current.Unregister(ToggleShowDeviceTypeSwitchInFlyoutHotkey);
_settings.Set("ToggleShowDeviceTypeSwitchInFlyoutHotkey", value);
HotkeyManager.Current.Register(ToggleShowDeviceTypeSwitchInFlyoutHotkey);
}
}

public bool UseLegacyIcon
{
get
Expand Down Expand Up @@ -166,6 +185,22 @@ public bool UseLogarithmicVolume
set => _settings.Set("UseLogarithmicVolume", value);
}

public bool ShowRecordingDevicesInContextMenu
{
get => _settings.Get("ShowRecordingDevicesInContextMenu", false);
set => _settings.Set("ShowRecordingDevicesInContextMenu", value);
}

public bool ShowDeviceTypeSwitchInFlyout
{
get => _settings.Get("ShowDeviceTypeSwitchInFlyout", false);
set
{
_settings.Set("ShowDeviceTypeSwitchInFlyout", value);
ShowDeviceTypeSwitchInFlyoutChanged?.Invoke(null, ShowDeviceTypeSwitchInFlyout);
}
}

public WINDOWPLACEMENT? FullMixerWindowPlacement
{
get => _settings.Get("FullMixerWindowPlacement", default(WINDOWPLACEMENT?));
Expand Down
1 change: 1 addition & 0 deletions EarTrumpet/Interop/SndVolSSO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum IconId
SpeakerTwoBars = 123,
SpeakerThreeBars = 124,
NoDevice = 125,
Microphone = 140,
}

private static readonly string DllPath = Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\SndVolSSO.dll");
Expand Down
55 changes: 54 additions & 1 deletion EarTrumpet/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions EarTrumpet/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@
<data name="NoDevicesPanelContent" xml:space="preserve">
<value>It doesn't look like you have any playback devices.</value>
</data>
<data name="NoRecordingDevicesPanelContent" xml:space="preserve">
<value>It doesn't look like you have any recording devices.</value>
</data>
<data name="NoDeviceTrayText" xml:space="preserve">
<value>EarTrumpet: No playback devices</value>
</data>
Expand Down Expand Up @@ -665,4 +668,19 @@ Open [https://eartrumpet.app/jmp/fixfonts] now?</value>
<data name="SettingsUseLogarithmicVolume" xml:space="preserve">
<value>Use logarithmic volume scale</value>
</data>
<data name="SettingsShowRecordingDevicesInContextMenu" xml:space="preserve">
<value>Show Recording Devices in Context Menu</value>
</data>
<data name="SettingsShowDeviceTypeSwitchInFlyout" xml:space="preserve">
<value>Show Device Type Switch in Flyout</value>
</data>
<data name="SettingsToggleShowDeviceTypeSwitchInFlyout" xml:space="preserve">
<value>Toggle "Show Device Type Switch in Flyout"</value>
</data>
<data name="SwitchToRecording" xml:space="preserve">
<value>Switch to Recording</value>
</data>
<data name="SwitchToPlayback" xml:space="preserve">
<value>Switch to Playback</value>
</data>
</root>
5 changes: 5 additions & 0 deletions EarTrumpet/UI/Helpers/TaskbarIconSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum IconKind
SpeakerOneBar,
SpeakerTwoBars,
SpeakerThreeBars,
Microphone,
NoDevice,
}

Expand Down Expand Up @@ -134,6 +135,8 @@ private static Icon LoadIcon(IconKind kind)
return IconHelper.LoadIconForTaskbar(SndVolSSO.GetPath(SndVolSSO.IconId.SpeakerTwoBars), dpi);
case IconKind.SpeakerThreeBars:
return IconHelper.LoadIconForTaskbar(SndVolSSO.GetPath(SndVolSSO.IconId.SpeakerThreeBars), dpi);
case IconKind.Microphone:
return IconHelper.LoadIconForTaskbar(SndVolSSO.GetPath(SndVolSSO.IconId.Microphone), dpi);
default: throw new NotImplementedException();
}
}
Expand Down Expand Up @@ -175,6 +178,8 @@ private static IconKind IconKindFromDeviceCollection(DeviceCollectionViewModel c
return IconKind.SpeakerTwoBars;
case DeviceViewModel.DeviceIconKind.Bar3:
return IconKind.SpeakerThreeBars;
case DeviceViewModel.DeviceIconKind.Microphone:
return IconKind.Microphone;
default: throw new NotImplementedException();
}
}
Expand Down
2 changes: 2 additions & 0 deletions EarTrumpet/UI/Mutable.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<win:GridLength x:Key="Mutable_IconCellWidth">65</win:GridLength>
<win:GridLength x:Key="Mutable_VolumeCellWidth">63</win:GridLength>
<bcl:Double x:Key="Mutable_DeviceTitleCellHeight">44</bcl:Double>
<bcl:Double x:Key="Mutable_DeviceDoubleTitleCellHeight">88</bcl:Double>
<Thickness x:Key="Mutable_DeviceTypeSwitchMargin">0,44,0,0</Thickness>
<bcl:Double x:Key="Mutable_AppItemCellHeight">44</bcl:Double>
<bcl:Double x:Key="Mutable_DeviceItemCellHeight">54</bcl:Double>
<bcl:Double x:Key="Mutable_ExpandCollapseButtonGlyphFontSize">12</bcl:Double>
Expand Down
Loading