Skip to content

Commit

Permalink
feat(Profile::Hotkey): Profile can now share the same hotkey. Doing s…
Browse files Browse the repository at this point in the history
…o let you switch between profile. A quick menu is also displayed.

Fixes #409
  • Loading branch information
Belphemur committed Sep 4, 2021
1 parent 0c72939 commit 85a623e
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 66 deletions.
131 changes: 68 additions & 63 deletions SoundSwitch/Framework/Profile/ProfileManager.cs
Expand Up @@ -13,9 +13,9 @@
using SoundSwitch.Audio.Manager.Interop.Enum;
using SoundSwitch.Common.Framework.Audio.Device;
using SoundSwitch.Framework.Configuration;
using SoundSwitch.Framework.Profile.Hotkey;
using SoundSwitch.Framework.Profile.Trigger;
using SoundSwitch.Framework.WinApi;
using SoundSwitch.Framework.WinApi.Keyboard;
using SoundSwitch.Localization;
using SoundSwitch.Model;
using SoundSwitch.Util;
Expand All @@ -26,75 +26,94 @@ public class ProfileManager
{
public delegate void ShowError(string errorMessage, string errorTitle);

private readonly WindowMonitor _windowMonitor;
private readonly AudioSwitcher _audioSwitcher;
private readonly IAudioDeviceLister _activeDeviceLister;
private readonly ShowError _showError;
private readonly TriggerFactory _triggerFactory;
private readonly WindowMonitor _windowMonitor;
private readonly AudioSwitcher _audioSwitcher;
private readonly IAudioDeviceLister _activeDeviceLister;
private readonly ShowError _showError;
private readonly TriggerFactory _triggerFactory;
private readonly NotificationManager.NotificationManager _notificationManager;

private Profile? _steamProfile;

private readonly Dictionary<User32.NativeMethods.HWND, Profile> _activeWindowsTrigger = new();

private readonly Dictionary<HotKey, Profile> _profilesByHotkey = new();
private readonly Dictionary<string, (Profile Profile, Trigger.Trigger Trigger)> _profileByApplication = new();
private readonly Dictionary<string, (Profile Profile, Trigger.Trigger Trigger)> _profilesByWindowName = new();
private readonly Dictionary<string, (Profile Profile, Trigger.Trigger Trigger)> _profilesByUwpApp = new();
private readonly Dictionary<string, (Profile Profile, Trigger.Trigger Trigger)> _profilesByUwpApp = new();

private readonly ProfileHotkeyManager _profileHotkeyManager;


public IReadOnlyCollection<Profile> Profiles => AppConfigs.Configuration.Profiles;

public ProfileManager(WindowMonitor windowMonitor,
AudioSwitcher audioSwitcher,
IAudioDeviceLister activeDeviceLister,
ShowError showError,
TriggerFactory triggerFactory,
public ProfileManager(WindowMonitor windowMonitor,
AudioSwitcher audioSwitcher,
IAudioDeviceLister activeDeviceLister,
ShowError showError,
TriggerFactory triggerFactory,
NotificationManager.NotificationManager notificationManager)
{
_windowMonitor = windowMonitor;
_audioSwitcher = audioSwitcher;
_activeDeviceLister = activeDeviceLister;
_showError = showError;
_triggerFactory = triggerFactory;
_windowMonitor = windowMonitor;
_audioSwitcher = audioSwitcher;
_activeDeviceLister = activeDeviceLister;
_showError = showError;
_triggerFactory = triggerFactory;
_notificationManager = notificationManager;
_profileHotkeyManager = new(this);
}

private void RegisterTriggers(Profile profile, bool onInit = false)
private bool RegisterTriggers(Profile profile, bool onInit = false)
{
var success = true;
foreach (var trigger in profile.Triggers)
{
trigger.Type.Switch(() => { _profilesByHotkey.Add(trigger.HotKey, profile); },
() => { _profilesByWindowName.Add(trigger.WindowName.ToLower(), (profile, trigger)); },
() => { _profileByApplication.Add(trigger.ApplicationPath.ToLower(), (profile, trigger)); },
() => { _steamProfile = profile; },
success &= trigger.Type.Match(() => _profileHotkeyManager.Add(trigger.HotKey, profile),
() =>
{
_profilesByWindowName.Add(trigger.WindowName.ToLower(), (profile, trigger));
return true;
},
() =>
{
_profileByApplication.Add(trigger.ApplicationPath.ToLower(), (profile, trigger));
return true;
},
() =>
{
_steamProfile = profile;
return true;
},
() =>
{
if (!onInit)
{
return;
return true;
}
SwitchAudio(profile);
}, () => { _profilesByUwpApp.Add(trigger.WindowName.ToLower(), (profile, trigger)); },
() => {});
return true;
}, () =>
{
_profilesByUwpApp.Add(trigger.WindowName.ToLower(), (profile, trigger));
return true;
},
() => true);

}

return success;
}

private void UnRegisterTriggers(Profile profile)
{
foreach (var trigger in profile.Triggers)
{
trigger.Type.Switch(() =>
{
WindowsAPIAdapter.UnRegisterHotKey(trigger.HotKey);
_profilesByHotkey.Remove(trigger.HotKey);
},
trigger.Type.Switch(() => { _profileHotkeyManager.Remove(trigger.HotKey, profile); },
() => { _profilesByWindowName.Remove(trigger.WindowName.ToLower()); },
() => { _profileByApplication.Remove(trigger.ApplicationPath.ToLower()); },
() => { _steamProfile = null; }, () => { },
() => { _profilesByUwpApp.Remove(trigger.WindowName.ToLower()); },
() => {});
() => { });
}
}

Expand All @@ -104,18 +123,10 @@ private void UnRegisterTriggers(Profile profile)
/// <returns></returns>
public Result<Profile[], VoidSuccess> Init()
{
foreach (var profile in AppConfigs.Configuration.Profiles)
{
RegisterTriggers(profile, true);
}
var errors = AppConfigs.Configuration.Profiles.Where(profile => !RegisterTriggers(profile, true)).ToArray();

RegisterEvents();

var errors = _profilesByHotkey
.Where(pair => !WindowsAPIAdapter.RegisterHotKey(pair.Key))
.Select(pair => pair.Value)
.ToArray();

InitializeProfileExistingProcess();

if (errors.Length > 0)
Expand All @@ -139,12 +150,6 @@ private void RegisterEvents()
if (HandleWindowName(@event)) return;
};

WindowsAPIAdapter.HotKeyPressed += (sender, args) =>
{
if (!_profilesByHotkey.TryGetValue(args.HotKey, out var profile))
return;
SwitchAudio(profile);
};
WindowsAPIAdapter.WindowDestroyed += (sender, @event) => { RestoreState(@event.Hwnd); };
}

Expand Down Expand Up @@ -209,18 +214,18 @@ private bool SaveCurrentState(User32.NativeMethods.HWND windowHandle, Profile pr
return false;
}

var communication = _audioSwitcher.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eCommunications);
var playback = _audioSwitcher.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
var recording = _audioSwitcher.GetDefaultAudioEndpoint(EDataFlow.eCapture, ERole.eMultimedia);
var communication = _audioSwitcher.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eCommunications);
var playback = _audioSwitcher.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
var recording = _audioSwitcher.GetDefaultAudioEndpoint(EDataFlow.eCapture, ERole.eMultimedia);

var currentState = new Profile
{
AlsoSwitchDefaultDevice = true,
Name = SettingsStrings.profile_trigger_restoreDevices_title,
Communication = communication,
Playback = playback,
Recording = recording,
NotifyOnActivation = profile.NotifyOnActivation
Name = SettingsStrings.profile_trigger_restoreDevices_title,
Communication = communication,
Playback = playback,
Recording = recording,
NotifyOnActivation = profile.NotifyOnActivation
};
_activeWindowsTrigger.Add(windowHandle, currentState);
return true;
Expand Down Expand Up @@ -283,7 +288,7 @@ private void SwitchAudio(Profile profile, uint processId)
_audioSwitcher.SwitchProcessTo(
deviceToUse.Id,
device.Role,
(EDataFlow) deviceToUse.Type,
(EDataFlow)deviceToUse.Type,
processId);

if (profile.AlsoSwitchDefaultDevice)
Expand Down Expand Up @@ -317,7 +322,7 @@ public void SwitchAudio(Profile profile)
/// <returns></returns>
public IEnumerable<ITriggerDefinition> AvailableTriggers()
{
var triggers = Profiles.SelectMany(profile => profile.Triggers).GroupBy(trigger => trigger.Type).ToDictionary(grouping => grouping.Key, grouping => grouping.Count());
var triggers = Profiles.SelectMany(profile => profile.Triggers).GroupBy(trigger => trigger.Type).ToDictionary(grouping => grouping.Key, grouping => grouping.Count());
var triggerFactory = new TriggerFactory();
return triggerFactory.AllImplementations
.Where(pair =>
Expand Down Expand Up @@ -353,7 +358,7 @@ public IEnumerable<ITriggerDefinition> AvailableTriggers()
/// </summary>
public Result<string, VoidSuccess> UpdateProfile(Profile oldProfile, Profile newProfile)
{
DeleteProfiles(new[] {oldProfile});
DeleteProfiles(new[] { oldProfile });
return ValidateProfile(newProfile)
.Map(success =>
{
Expand Down Expand Up @@ -394,7 +399,7 @@ public IEnumerable<ITriggerDefinition> AvailableTriggers()
{
var error = trigger.Type.Match(() =>
{
if (trigger.HotKey == null || _profilesByHotkey.ContainsKey(trigger.HotKey) || !WindowsAPIAdapter.RegisterHotKey(trigger.HotKey))
if (trigger.HotKey == null || !_profileHotkeyManager.IsValidHotkey(trigger.HotKey))
{
return string.Format(SettingsStrings.profile_error_hotkey, trigger.HotKey);
}
Expand Down Expand Up @@ -454,7 +459,7 @@ public IEnumerable<ITriggerDefinition> AvailableTriggers()
return SettingsStrings.profile_error_needPlaybackOrRecording;
}

if (profile.HotKey != null && _profilesByHotkey.ContainsKey(profile.HotKey))
if (profile.HotKey != null && !_profileHotkeyManager.IsValidHotkey(profile.HotKey))
{
return string.Format(SettingsStrings.profile_error_hotkey, profile.HotKey);
}
Expand Down Expand Up @@ -484,8 +489,8 @@ public IEnumerable<ITriggerDefinition> AvailableTriggers()
/// </summary>
public Result<Profile[], VoidSuccess> DeleteProfiles(IEnumerable<Profile> profilesToDelete)
{
var errors = new List<Profile>();
var profiles = profilesToDelete.ToArray();
var errors = new List<Profile>();
var profiles = profilesToDelete.ToArray();
var resetProcessAudio = profiles.Any(profile => profile.Triggers.Any(trigger => trigger.Type == TriggerFactory.Enum.Process || trigger.Type == TriggerFactory.Enum.Window));
foreach (var profile in profiles)
{
Expand Down Expand Up @@ -540,7 +545,7 @@ private void InitializeProfileExistingProcess()
{
var handle = User32.NativeMethods.HWND.Cast(process.Handle);
SaveCurrentState(handle, profile.Profile, profile.Trigger);
SwitchAudio(profile.Profile, (uint) process.Id);
SwitchAudio(profile.Profile, (uint)process.Id);
}
}
catch (Win32Exception)
Expand Down
52 changes: 49 additions & 3 deletions SoundSwitch/UI/Component/HotKeyTextBox.cs
Expand Up @@ -3,8 +3,8 @@
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using HotKey = SoundSwitch.Framework.WinApi.Keyboard.HotKey;
using KeyboardWindowsAPI = SoundSwitch.Framework.WinApi.Keyboard.KeyboardWindowsAPI;
using SoundSwitch.Framework.WinApi;
using SoundSwitch.Framework.WinApi.Keyboard;

namespace SoundSwitch.UI.Component
{
Expand All @@ -27,13 +27,59 @@ public HotKey HotKey
}

private HotKey _hotKey;
[Browsable(true)] public event EventHandler<Event> HotKeyChanged;
private bool _listenToHotkey;

[Browsable(true)]
public event EventHandler<Event> HotKeyChanged;

[Browsable(true)]
public bool ListenToHotkey
{
get => _listenToHotkey;
set
{
_listenToHotkey = value;
if (value)
{
WindowsAPIAdapter.HotKeyPressed += WindowsAPIAdapterOnHotKeyPressed;
}
else
{
WindowsAPIAdapter.HotKeyPressed -= WindowsAPIAdapterOnHotKeyPressed;
}
}
}

public void CleanHotKeyChangedHandler()
{
HotKeyChanged = null;
}


protected override void Dispose(bool disposing)
{
if (disposing && ListenToHotkey)
{
WindowsAPIAdapter.HotKeyPressed -= WindowsAPIAdapterOnHotKeyPressed;
}
base.Dispose(disposing);
}

private void WindowsAPIAdapterOnHotKeyPressed(object? sender, WindowsAPIAdapter.KeyPressedEventArgs e)
{
Invoke(new Action(() =>
{
if (!Visible)
{
return;
}
HotKey = e.HotKey;
ForeColor = Color.Green;
HotKeyChanged?.Invoke(this, new Event());
}), null);
}

protected override void OnKeyDown(KeyEventArgs e)
{
HotKey.ModifierKeys modifierKeys = 0;
Expand Down
1 change: 1 addition & 0 deletions SoundSwitch/UI/Forms/UpsertProfileExtended.cs
Expand Up @@ -38,6 +38,7 @@ public UpsertProfileExtended(Profile profile, IEnumerable<DeviceFullInfo> playba
HideTriggerComponents();

hotKeyControl.Location = textInput.Location;
hotKeyControl.ListenToHotkey = true;

LocalizeForm();
using var iconBitmap = new Bitmap(Resources.profile_menu_icon);
Expand Down

0 comments on commit 85a623e

Please sign in to comment.