Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using EarTrumpet.Actions.DataModel.Enum;
using EarTrumpet.Actions.DataModel.Serialization;
using EarTrumpet.DataModel.AppInformation;
using EarTrumpet.DataModel.Audio;
using EarTrumpet.DataModel.WindowsAudio;
using EarTrumpet.Extensions;
using Windows.Win32;

namespace EarTrumpet.Actions.DataModel.Processing;

Expand Down Expand Up @@ -41,7 +38,7 @@ public static void Invoke(BaseAction a)
{
if (setAppVolumeAction.App.Id == AppRef.ForegroundAppId)
{
var app = FindForegroundApp(device.Groups);
var app = ForegroundAppResolver.FindForegroundApp(device.Groups);
if (app != null)
{
DoAudioAction(setAppVolumeAction.Option, app, setAppVolumeAction);
Expand All @@ -66,7 +63,7 @@ public static void Invoke(BaseAction a)
{
if (setAppMuteAction.App.Id == AppRef.ForegroundAppId)
{
var app = FindForegroundApp(device.Groups);
var app = ForegroundAppResolver.FindForegroundApp(device.Groups);
if (app != null)
{
DoAudioAction(setAppMuteAction.Option, app);
Expand Down Expand Up @@ -109,64 +106,6 @@ public static void Invoke(BaseAction a)
}
}

private static IAudioDeviceSession FindForegroundApp(ObservableCollection<IAudioDeviceSession> groups)
{
var hWnd = PInvoke.GetForegroundWindow();
if (hWnd == (HWND)null)
{
Trace.WriteLine($"ActionProcessor FindForegroundApp: No Window (1)");
return null;
}

var className = string.Empty;
unsafe
{
Span<char> classNameBuffer = stackalloc char[(int)PInvoke.MAX_CLASS_NAME_LEN];
fixed (char* pClassNameBuffer = classNameBuffer)
{
_ = PInvoke.GetClassName(hWnd, pClassNameBuffer, classNameBuffer.Length);
className = new PWSTR(pClassNameBuffer).ToString();
}
}

// ApplicationFrameWindow.exe, find the real hosted process in the child CoreWindow.
if (className.ToString() == "ApplicationFrameWindow")
{
hWnd = PInvoke.FindWindowEx(hWnd, (HWND)null, "Windows.UI.Core.CoreWindow", null);
}
if (hWnd == (HWND)null)
{
Trace.WriteLine($"ActionProcessor FindForegroundApp: No Window (2)");
return null;
}

var processId = 0U;
unsafe
{
_ = PInvoke.GetWindowThreadProcessId(hWnd, &processId);
}

try
{
var appInfo = AppInformationFactory.CreateForProcess(processId);

foreach(var group in groups)
{
if (group.AppId == appInfo.PackageInstallPath || group.AppId == appInfo.AppId)
{
Trace.WriteLine($"ActionProcessor FindForegroundApp: {group.DisplayName}");
return group;
}
}
}
catch(Exception ex)
{
Trace.WriteLine(ex);
}
Trace.WriteLine("ActionProcessor FindForegroundApp Didn't locate foreground app");
return null;
}

private static void DoAudioAction(MuteKind action, IStreamWithVolumeControl stream)
{
switch (action)
Expand Down
52 changes: 52 additions & 0 deletions EarTrumpet/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using EarTrumpet.DataModel.Audio;
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -57,6 +58,7 @@ public DeviceCollectionViewModel CollectionViewModel
private static readonly Stopwatch s_appTimer = Stopwatch.StartNew();
private FlyoutViewModel _flyoutViewModel;

private const int c_focusedAppVolumeStep = 2;
private ShellNotifyIcon _trayIcon;
private WindowHolder _mixerWindow;
private WindowHolder _settingsWindow;
Expand Down Expand Up @@ -150,6 +152,9 @@ private void CompleteStartup()
Settings.SettingsHotkeyTyped += () => _settingsWindow.OpenOrBringToFront();
Settings.AbsoluteVolumeUpHotkeyTyped += AbsoluteVolumeIncrement;
Settings.AbsoluteVolumeDownHotkeyTyped += AbsoluteVolumeDecrement;
Settings.FocusedAppVolumeUpHotkeyTyped += FocusedAppVolumeIncrement;
Settings.FocusedAppVolumeDownHotkeyTyped += FocusedAppVolumeDecrement;
Settings.FocusedAppToggleMuteHotkeyTyped += FocusedAppToggleMute;
Settings.RegisterHotkeys();
Settings.UseLogarithmicVolumeChanged += (_, __) => UpdateTrayTooltip();

Expand Down Expand Up @@ -428,4 +433,51 @@ private void AbsoluteVolumeDecrement()
}
}
}

private void FocusedAppVolumeIncrement()
{
ChangeFocusedAppVolume(c_focusedAppVolumeStep);
}

private void FocusedAppVolumeDecrement()
{
ChangeFocusedAppVolume(-c_focusedAppVolumeStep);
}

private void ChangeFocusedAppVolume(int delta)
{
foreach (var app in GetFocusedApps())
{
app.Volume = Math.Max(0, Math.Min(100, app.Volume + delta));
}
}

private void FocusedAppToggleMute()
{
var apps = GetFocusedApps().ToArray();
if (!apps.Any())
{
return;
}

var shouldMute = apps.Any(app => !app.IsMuted);
foreach (var app in apps)
{
app.IsMuted = shouldMute;
}
}

private IEnumerable<IAppItemViewModel> GetFocusedApps()
{
var foregroundAppIds = ForegroundAppResolver.TryGetForegroundAppIds();
if (foregroundAppIds.Count == 0)
{
return Enumerable.Empty<IAppItemViewModel>();
}

return CollectionViewModel.AllDevices
.SelectMany(device => device.Apps)
.Where(app => foregroundAppIds.Contains(app.AppId))
.ToArray();
}
}
54 changes: 54 additions & 0 deletions EarTrumpet/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public class AppSettings
public event Action SettingsHotkeyTyped;
public event Action AbsoluteVolumeUpHotkeyTyped;
public event Action AbsoluteVolumeDownHotkeyTyped;
public event Action FocusedAppVolumeUpHotkeyTyped;
public event Action FocusedAppVolumeDownHotkeyTyped;
public event Action FocusedAppToggleMuteHotkeyTyped;

private readonly ISettingsBag _settings = StorageFactory.GetSettings();

Expand All @@ -26,6 +29,9 @@ public void RegisterHotkeys()
HotkeyManager.Current.Register(SettingsHotkey);
HotkeyManager.Current.Register(AbsoluteVolumeUpHotkey);
HotkeyManager.Current.Register(AbsoluteVolumeDownHotkey);
HotkeyManager.Current.Register(FocusedAppVolumeUpHotkey);
HotkeyManager.Current.Register(FocusedAppVolumeDownHotkey);
HotkeyManager.Current.Register(FocusedAppToggleMuteHotkey);

HotkeyManager.Current.KeyPressed += (hotkey) =>
{
Expand Down Expand Up @@ -54,6 +60,21 @@ public void RegisterHotkeys()
Trace.WriteLine("AppSettings AbsoluteVolumeDownHotkeyTyped");
AbsoluteVolumeDownHotkeyTyped?.Invoke();
}
else if (hotkey.Equals(FocusedAppVolumeUpHotkey))
{
Trace.WriteLine("AppSettings FocusedAppVolumeUpHotkeyTyped");
FocusedAppVolumeUpHotkeyTyped?.Invoke();
}
else if (hotkey.Equals(FocusedAppVolumeDownHotkey))
{
Trace.WriteLine("AppSettings FocusedAppVolumeDownHotkeyTyped");
FocusedAppVolumeDownHotkeyTyped?.Invoke();
}
else if (hotkey.Equals(FocusedAppToggleMuteHotkey))
{
Trace.WriteLine("AppSettings FocusedAppToggleMuteHotkeyTyped");
FocusedAppToggleMuteHotkeyTyped?.Invoke();
}
};
}

Expand Down Expand Up @@ -112,6 +133,39 @@ public HotkeyData AbsoluteVolumeDownHotkey
}
}

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

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

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

public bool UseLegacyIcon
{
get
Expand Down
97 changes: 97 additions & 0 deletions EarTrumpet/DataModel/Audio/ForegroundAppResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using EarTrumpet.DataModel.AppInformation;
using EarTrumpet.Interop;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace EarTrumpet.DataModel.Audio
{
public static class ForegroundAppResolver
{
public static IReadOnlyList<string> TryGetForegroundAppIds()
{
var hWnd = User32.GetForegroundWindow();
var foregroundClassName = new StringBuilder(User32.MAX_CLASSNAME_LENGTH);
User32.GetClassName(hWnd, foregroundClassName, foregroundClassName.Capacity);

if (hWnd == IntPtr.Zero)
{
Trace.WriteLine("ForegroundAppResolver: No Window (1)");
return Array.Empty<string>();
}

if (foregroundClassName.ToString() == "ApplicationFrameWindow")
{
hWnd = User32.FindWindowEx(hWnd, IntPtr.Zero, "Windows.UI.Core.CoreWindow", IntPtr.Zero);
}

if (hWnd == IntPtr.Zero)
{
Trace.WriteLine("ForegroundAppResolver: No Window (2)");
return Array.Empty<string>();
}

User32.GetWindowThreadProcessId(hWnd, out uint processId);

try
{
var appInfo = AppInformationFactory.CreateForProcess((int)processId);
return new[] { appInfo.PackageInstallPath, appInfo.AppId }
.Where(value => !string.IsNullOrWhiteSpace(value))
.Distinct()
.ToArray();
}
catch (Exception ex)
{
Trace.WriteLine(ex);
return Array.Empty<string>();
}
}

public static IAudioDeviceSession FindForegroundApp(ObservableCollection<IAudioDeviceSession> groups)
{
var foregroundAppIds = TryGetForegroundAppIds();
if (foregroundAppIds.Count == 0)
{
return null;
}

var group = groups.FirstOrDefault(candidate => foregroundAppIds.Contains(candidate.AppId));
if (group != null)
{
Trace.WriteLine($"ForegroundAppResolver: {group.DisplayName}");
}
else
{
Trace.WriteLine("ForegroundAppResolver: Didn't locate foreground app");
}

return group;
}

public static IAudioDeviceSession FindForegroundApp(IEnumerable<IAudioDevice> devices)
{
var foregroundAppIds = TryGetForegroundAppIds();
if (foregroundAppIds.Count == 0)
{
return null;
}

foreach (var device in devices)
{
var group = device.Groups.FirstOrDefault(candidate => foregroundAppIds.Contains(candidate.AppId));
if (group != null)
{
Trace.WriteLine($"ForegroundAppResolver: {group.DisplayName}");
return group;
}
}

Trace.WriteLine("ForegroundAppResolver: Didn't locate foreground app");
return null;
}
}
}
27 changes: 27 additions & 0 deletions EarTrumpet/Properties/Resources.Designer.cs

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

Loading