Skip to content

Commit eff98a8

Browse files
committed
Add global hotkey capability
Todo, expand on it and have presets for sounds
1 parent d739867 commit eff98a8

File tree

5 files changed

+228
-16
lines changed

5 files changed

+228
-16
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// <copyright file="HotKey.cs" company="the-prism">
2+
// Copyright (c) the-prism. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
// </copyright>
5+
6+
#pragma warning disable SA1121 // Use built-in type alias
7+
namespace UnManaged
8+
{
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Diagnostics;
12+
using System.Runtime.InteropServices;
13+
using System.Windows.Input;
14+
using System.Windows.Interop;
15+
16+
/// <summary>Keymodifiers flags</summary>
17+
[Flags]
18+
public enum KeyModifier
19+
{
20+
/// <summary>No modifiers</summary>
21+
None = 0x0000,
22+
23+
/// <summary>Alt key</summary>
24+
Alt = 0x0001,
25+
26+
/// <summary>Ctrl key</summary>
27+
Ctrl = 0x0002,
28+
29+
/// <summary>No repeat key</summary>
30+
NoRepeat = 0x4000,
31+
32+
/// <summary>Shift key</summary>
33+
Shift = 0x0004,
34+
35+
/// <summary>Windows key</summary>
36+
Win = 0x0008,
37+
}
38+
39+
/// <summary>Class to handle win32 hotkeys</summary>
40+
public class HotKey : IDisposable
41+
{
42+
/// <summary>Hotkey event id</summary>
43+
public const int WmHotKey = 0x0312;
44+
45+
private static Dictionary<int, HotKey> dictHotKeyToCalBackProc;
46+
private bool disposed = false;
47+
48+
/// <summary>Initializes a new instance of the <see cref="HotKey"/> class.</summary>
49+
/// <param name="k">Key to register</param>
50+
/// <param name="keyModifiers">Modifiers to register</param>
51+
/// <param name="action">Callback to register</param>
52+
/// <param name="register">Should the keybind be registered with the system</param>
53+
public HotKey(Key k, KeyModifier keyModifiers, Action<HotKey> action, bool register = true)
54+
{
55+
this.Key = k;
56+
this.KeyModifiers = keyModifiers;
57+
this.Action = action;
58+
if (register)
59+
{
60+
this.Register();
61+
}
62+
}
63+
64+
/// <summary>Gets key pressed</summary>
65+
public Key Key { get; private set; }
66+
67+
/// <summary>Gets modfiers pressed</summary>
68+
public KeyModifier KeyModifiers { get; private set; }
69+
70+
/// <summary>Gets callback for the keypress</summary>
71+
public Action<HotKey> Action { get; private set; }
72+
73+
/// <summary>Registered id for the keybind</summary>
74+
public int Id { get; set; }
75+
76+
/// <summary>Register the keybind</summary>
77+
/// <returns>If the registration was successfull</returns>
78+
public bool Register()
79+
{
80+
int virtualKeyCode = KeyInterop.VirtualKeyFromKey(this.Key);
81+
this.Id = virtualKeyCode + ((int)this.KeyModifiers * 0x10000);
82+
bool result = RegisterHotKey(IntPtr.Zero, this.Id, (UInt32)this.KeyModifiers, (UInt32)virtualKeyCode);
83+
84+
if (dictHotKeyToCalBackProc == null)
85+
{
86+
dictHotKeyToCalBackProc = new Dictionary<int, HotKey>();
87+
ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
88+
}
89+
90+
dictHotKeyToCalBackProc.Add(this.Id, this);
91+
92+
Debug.Print(result.ToString() + ", " + this.Id + ", " + virtualKeyCode);
93+
return result;
94+
}
95+
96+
/// <summary>Unregister the keybind</summary>
97+
public void Unregister()
98+
{
99+
HotKey hotKey;
100+
if (dictHotKeyToCalBackProc.TryGetValue(this.Id, out hotKey))
101+
{
102+
UnregisterHotKey(IntPtr.Zero, this.Id);
103+
}
104+
}
105+
106+
/// <summary>
107+
/// Implement IDisposable.
108+
/// Do not make this method virtual.
109+
/// A derived class should not be able to override this method.
110+
/// </summary>
111+
public void Dispose()
112+
{
113+
this.Dispose(true);
114+
// This object will be cleaned up by the Dispose method.
115+
// Therefore, you should call GC.SupressFinalize to
116+
// take this object off the finalization queue
117+
// and prevent finalization code for this object
118+
// from executing a second time.
119+
GC.SuppressFinalize(this);
120+
}
121+
122+
/// <summary>
123+
/// Dispose(bool disposing) executes in two distinct scenarios.
124+
/// If disposing equals true, the method has been called directly
125+
/// or indirectly by a user's code. Managed and unmanaged resources
126+
/// can be _disposed.
127+
/// If disposing equals false, the method has been called by the
128+
/// runtime from inside the finalizer and you should not reference
129+
/// other objects. Only unmanaged resources can be _disposed.
130+
/// </summary>
131+
/// <param name="disposing"></param>
132+
protected virtual void Dispose(bool disposing)
133+
{
134+
// Check to see if Dispose has already been called.
135+
if (!this.disposed)
136+
{
137+
// If disposing equals true, dispose all managed
138+
// and unmanaged resources.
139+
if (disposing)
140+
{
141+
// Dispose managed resources.
142+
this.Unregister();
143+
}
144+
145+
// Note disposing has been done.
146+
this.disposed = true;
147+
}
148+
}
149+
150+
[DllImport("user32.dll")]
151+
private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);
152+
153+
[DllImport("user32.dll")]
154+
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
155+
156+
// ******************************************************************
157+
private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
158+
{
159+
if (!handled)
160+
{
161+
if (msg.message == WmHotKey)
162+
{
163+
HotKey hotKey;
164+
165+
if (dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
166+
{
167+
if (hotKey.Action != null)
168+
{
169+
hotKey.Action.Invoke(hotKey);
170+
}
171+
172+
handled = true;
173+
}
174+
}
175+
}
176+
}
177+
}
178+
}
179+
180+
#pragma warning restore SA1121 // Use built-in type alias

Prism.Soundboard/Prism.Soundboard/MainWindow.xaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
xmlns:local="clr-namespace:Prism.Soundboard"
99
mc:Ignorable="d"
1010
Title="Soundboard" Height="650" Width="800" Closed="Window_Closed">
11-
1211
<Grid>
1312
<Grid.RowDefinitions>
1413
<RowDefinition Height="48"/>
@@ -39,10 +38,10 @@
3938
</ComboBox>
4039
</StackPanel>
4140
<StackPanel Orientation="Horizontal">
42-
<Label Content="Input device :"/>
41+
<Label Content="Input device :"/>
4342
<ComboBox x:Name="InputDeviceSelector" SelectionChanged="InputDeviceSelector_SelectionChanged" Height="24">
44-
<ComboBoxItem IsSelected="True">Default input device</ComboBoxItem>
45-
</ComboBox>
43+
<ComboBoxItem IsSelected="True">Default input device</ComboBoxItem>
44+
</ComboBox>
4645
</StackPanel>
4746
</StackPanel>
4847

Prism.Soundboard/Prism.Soundboard/MainWindow.xaml.cs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,19 @@ namespace Prism.Soundboard
1010
using System.Diagnostics;
1111
using System.IO;
1212
using System.Linq;
13-
using System.Text;
1413
using System.Text.Json;
15-
using System.Threading.Tasks;
1614
using System.Windows;
1715
using System.Windows.Controls;
18-
using System.Windows.Data;
19-
using System.Windows.Documents;
2016
using System.Windows.Input;
2117
using System.Windows.Media;
22-
using System.Windows.Media.Imaging;
23-
using System.Windows.Navigation;
24-
using System.Windows.Shapes;
2518

2619
using NAudio.Wave;
20+
using UnManaged;
2721

2822
/// <summary>
2923
/// Interaction logic for MainWindow.xaml
3024
/// </summary>
31-
public partial class MainWindow : Window
25+
public partial class MainWindow : Window, IDisposable
3226
{
3327
private WaveOutEvent outputDevice;
3428
private WaveOutEvent monitorDevice;
@@ -47,6 +41,8 @@ public partial class MainWindow : Window
4741
private double desiredVolume;
4842
private bool simpleMode;
4943
private List<Tuple<string, string>> lastFilesPlayed = new List<Tuple<string, string>>(10);
44+
private HotKey play = null;
45+
private HotKey stop = null;
5046

5147
/// <summary>
5248
/// Initializes a new instance of the <see cref="MainWindow"/> class.
@@ -89,6 +85,9 @@ public MainWindow()
8985
}
9086

9187
this.LoadSettings();
88+
89+
this.play = new HotKey(Key.F1, KeyModifier.None, this.OnHotKeyHandler);
90+
this.stop = new HotKey(Key.F1, KeyModifier.Ctrl, this.OnHotKeyHandler);
9291
}
9392

9493
private bool SimpleMode
@@ -124,6 +123,17 @@ public override void OnApplyTemplate()
124123
}
125124
}
126125

126+
/// <inheritdoc/>
127+
public void Dispose()
128+
{
129+
this.play?.Dispose();
130+
this.stop?.Dispose();
131+
this.outputDevice?.Dispose();
132+
this.monitorDevice?.Dispose();
133+
this.audioFile?.Dispose();
134+
this.inputMic?.Dispose();
135+
}
136+
127137
private void Play_Click(object sender, RoutedEventArgs e)
128138
{
129139
if (this.outputDevice == null)
@@ -350,5 +360,26 @@ private void Passthrough_Click(object sender, RoutedEventArgs e)
350360
this.inputDevice.Stop();
351361
}
352362
}
363+
364+
private void OnKeyDown(object sender, KeyEventArgs e)
365+
{
366+
if (e.Key == Key.P)
367+
{
368+
this.Play_Click(null, null); // Call your method here
369+
e.Handled = true; // Mark the event as handled
370+
}
371+
}
372+
373+
private void OnHotKeyHandler(HotKey hotKey)
374+
{
375+
if (hotKey == this.play)
376+
{
377+
this.Play_Click(null, null);
378+
}
379+
else if (hotKey == this.stop)
380+
{
381+
this.Stop_Click(null, null);
382+
}
383+
}
353384
}
354385
}

Prism.Soundboard/Prism.Soundboard/SavedSettings.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ namespace Prism.Soundboard
1515
public class SavedSettings
1616
{
1717
/// <summary>Initializes a new instance of the <see cref="SavedSettings"/> class.</summary>
18-
public SavedSettings() { }
18+
public SavedSettings()
19+
{
20+
}
1921

2022
/// <summary>Selected output device index</summary>
2123
public int OutputDeviceIndex { get; set; }

Prism.Soundboard/Prism.Soundboard/Themes/Extensions/ColorExtension.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ private static void AddResources(this ResourceDictionary resources, Color color)
7171
resources.Add(PressedBorderColorDark, color.GetPressedBorderColorDark());
7272
resources.Add(PressedBackgroundColorDark, color.GetPressedBackgroundColorDark());
7373

74-
resources.Add(AccentBrush, new SolidColorBrush((Color) resources[AccentColor]));
74+
resources.Add(AccentBrush, new SolidColorBrush((Color)resources[AccentColor]));
7575
resources.Add(AccentBrush1, new SolidColorBrush((Color)resources[AccentColor1]));
76-
resources.Add(MouseOverBorderBrush, new SolidColorBrush((Color) resources[MouseOverBorderColor]));
77-
resources.Add(MouseOverBackgroundBrush, new SolidColorBrush((Color) resources[MouseOverBackgroundColor]));
76+
resources.Add(MouseOverBorderBrush, new SolidColorBrush((Color)resources[MouseOverBorderColor]));
77+
resources.Add(MouseOverBackgroundBrush, new SolidColorBrush((Color)resources[MouseOverBackgroundColor]));
7878
resources.Add(PressedBorderBrush, new SolidColorBrush((Color)resources[PressedBorderColor]));
7979
resources.Add(PressedBackgroundBrush, new SolidColorBrush((Color)resources[PressedBackgroundColor]));
8080
resources.Add(PressedBorderBrushDark, new SolidColorBrush((Color)resources[PressedBorderColorDark]));

0 commit comments

Comments
 (0)