Skip to content

Commit e977268

Browse files
feat: tracking the holding of modifier keys
1 parent 1a6963e commit e977268

File tree

5 files changed

+84
-17
lines changed

5 files changed

+84
-17
lines changed

src/TwitchStreamingTools/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static void Main(string[] args) {
5555
// file)
5656
_mutex = new Mutex(true, MutexName, out bool onlyAppInstance);
5757
if (!onlyAppInstance) {
58-
Log.Info("Application instance already running. Exiting.");
58+
LOG.Info("Application instance already running. Exiting.");
5959
return;
6060
}
6161

src/TwitchStreamingTools/Services/GlobalKeyPressService.cs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Runtime.InteropServices;
4+
using System.Text;
35
using System.Threading;
46

57
using log4net;
@@ -24,6 +26,28 @@ public class GlobalKeyPressService {
2426
/// </summary>
2527
private static User32.SafeHookHandle? s_hook;
2628

29+
/// <summary>
30+
/// Maps each modifier key to whether or not it's being held down. This is kind overkill but it's at least accurate.
31+
/// </summary>
32+
private static readonly Dictionary<Keys, bool> holding = new() {
33+
{ Keys.Control, false },
34+
{ Keys.ControlKey, false },
35+
{ Keys.LControlKey, false },
36+
{ Keys.RControlKey, false },
37+
{ Keys.Shift, false },
38+
{ Keys.ShiftKey, false },
39+
{ Keys.LShiftKey, false },
40+
{ Keys.RShiftKey, false }
41+
};
42+
43+
/// <summary>
44+
/// The possible modifier keys for quick comparison at runtime.
45+
/// </summary>
46+
private static readonly HashSet<Keys> modifiers = [
47+
Keys.Control, Keys.ControlKey, Keys.LControlKey, Keys.RControlKey,
48+
Keys.Shift, Keys.ShiftKey, Keys.LShiftKey, Keys.RShiftKey
49+
];
50+
2751
/// <summary>
2852
/// The thread to execute on.
2953
/// </summary>
@@ -43,7 +67,7 @@ public GlobalKeyPressService() {
4367
/// <summary>
4468
/// Gets or sets the callbacks to invoke when a keystroke is pressed.
4569
/// </summary>
46-
public static Action<string>? OnKeystroke { get; set; }
70+
public static Action<Keys, bool, bool, bool>? OnKeystroke { get; set; }
4771

4872
/// <summary>
4973
/// The main loop which registers for keystrokes on the system and flushes the message buffer.
@@ -69,13 +93,52 @@ private void Main() {
6993
private static int KeystrokeCallback(int nCode, IntPtr wParam, IntPtr lParam) {
7094
var keyboardEvent = Marshal.PtrToStructure<KeyboardLowLevelHookStruct>(lParam);
7195
var whatHappened = (KeyboardMessage)wParam;
96+
var key = (Keys)keyboardEvent.vkCode;
7297

7398
if (whatHappened == KeyboardMessage.KEY_DOWN) {
74-
var key = (Keys)keyboardEvent.vkCode;
75-
LOGGER.Debug($"Key pressed: {key}");
76-
OnKeystroke?.Invoke(key.ToString());
99+
if (modifiers.Contains(key)) {
100+
holding[key] = true;
101+
}
102+
103+
// You don't get a keyboard event for ALT, you have to check it this way.
104+
bool holdingAlt = (keyboardEvent.flags & 0b_0001_0000) != 0;
105+
bool holdingCtrl = holding[Keys.Control] || holding[Keys.ControlKey] || holding[Keys.LControlKey] || holding[Keys.RControlKey];
106+
bool holdingShift = holding[Keys.Shift] || holding[Keys.ShiftKey] || holding[Keys.LShiftKey] || holding[Keys.RShiftKey];
107+
LogKey(key, holdingCtrl, holdingAlt, holdingShift);
108+
OnKeystroke?.Invoke(key, holdingCtrl, holdingAlt, holdingShift);
109+
}
110+
else if (whatHappened == KeyboardMessage.KEY_UP) {
111+
if (modifiers.Contains(key)) {
112+
holding[key] = false;
113+
}
77114
}
78115

79116
return User32.CallNextHookEx(s_hook?.DangerousGetHandle() ?? 0, nCode, wParam, lParam);
80117
}
118+
119+
/// <summary>
120+
/// Logs the keystroke for debugging.
121+
/// </summary>
122+
/// <param name="key">The key pressed.</param>
123+
/// <param name="holdingCtrl">True if control is held down.</param>
124+
/// <param name="holdingAlt">True if alt is held down.</param>
125+
/// <param name="holdingShift">True if shift is held down.</param>
126+
private static void LogKey(Keys key, bool holdingCtrl, bool holdingAlt, bool holdingShift) {
127+
var sb = new StringBuilder("Key Pressed: ");
128+
if (holdingCtrl) {
129+
sb.Append("Ctrl + ");
130+
}
131+
132+
if (holdingShift) {
133+
sb.Append("Shift + ");
134+
}
135+
136+
if (holdingAlt) {
137+
sb.Append("Alt + ");
138+
}
139+
140+
sb.Append(key);
141+
142+
LOGGER.Debug(sb.ToString());
143+
}
81144
}

src/TwitchStreamingTools/TwitchStreamingTools.csproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,20 @@
4949
</ItemGroup>
5050

5151
<ItemGroup>
52-
<PackageReference Include="Avalonia" Version="11.3.3" />
53-
<PackageReference Include="Avalonia.Desktop" Version="11.3.3" />
54-
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.3" />
55-
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.3" />
52+
<PackageReference Include="Avalonia" Version="11.3.4"/>
53+
<PackageReference Include="Avalonia.Desktop" Version="11.3.4"/>
54+
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.4"/>
55+
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.4"/>
5656
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
57-
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.3" />
58-
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.3" />
57+
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.4"/>
58+
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.4"/>
5959
<PackageReference Include="DynamicData" Version="9.4.1"/>
6060
<PackageReference Include="log4net" Version="3.1.0"/>
61-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
61+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8"/>
6262
<PackageReference Include="NAudio.Wasapi" Version="2.2.1"/>
6363
<PackageReference Include="NAudio.WinMM" Version="2.2.1"/>
6464
<PackageReference Include="PInvoke.User32" Version="0.7.124"/>
65-
<PackageReference Include="System.Speech" Version="9.0.8" />
65+
<PackageReference Include="System.Speech" Version="9.0.8"/>
6666
<PackageReference Include="Xaml.Behaviors.Avalonia" Version="11.3.2"/>
6767
<PackageReference Include="Xaml.Behaviors.Interactivity" Version="11.3.2"/>
6868
</ItemGroup>

src/TwitchStreamingTools/Utilities/ITwitchChatLog.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@ public interface ITwitchChatLog {
1313
/// The maximum number of messages to keep in the log, anything larger than this number will be deleted starting with
1414
/// the oldest messages.
1515
/// </summary>
16-
public int MaximumMessageCount { get; set; }
16+
int MaximumMessageCount { get; set; }
1717

1818
/// <summary>
1919
/// The maximum message age before the message should be deleted, anything older than this will be deleted starting
2020
/// with the oldest messages.
2121
/// </summary>
22-
public TimeSpan MaximumMessageAge { get; set; }
22+
TimeSpan MaximumMessageAge { get; set; }
2323

2424
/// <summary>
2525
/// Retrieves all messages.
2626
/// </summary>
2727
/// <param name="channel">If set, filter messages to those sent in a channel, otherwise all channels.</param>
2828
/// <returns>All known twitch messages.</returns>
29-
public IEnumerable<TwitchChatMessage> GetMessages(string? channel = null);
29+
IEnumerable<TwitchChatMessage> GetMessages(string? channel = null);
3030

3131
/// <summary>
3232
/// Adds a twitch chat log.
3333
/// </summary>
3434
/// <param name="message">The chat messages.</param>
35-
public void AddMessage(TwitchChatMessage message);
35+
void AddMessage(TwitchChatMessage message);
3636
}

src/TwitchStreamingTools/log4net.debug.config

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@
99
<level value="DEBUG"/>
1010
<appender-ref ref="ConsoleAppender"/>
1111
</root>
12+
<!-- Set to debug to see key presses in the log -->
13+
<logger name="TwitchStreamingTools.Services.GlobalKeyPressService">
14+
<level value="INFO"/>
15+
</logger>
1216
</log4net>

0 commit comments

Comments
 (0)