Skip to content

Commit fb3500b

Browse files
authored
🐛 Add hotkey reset logic to avoid stuck keys (#28)
Occasionally, the hotkey management logic would get "stuck" thinking a key is pressed when it isn't. This can happen if keyboard events are prevented from reaching the FlashCom process for a variety of reasons (ex. if the user invokes the lock screen with Win+L, the key-up event for the Win key will not reach FlashCom). This change adds a hotkey reset timer that resets all hotkey state after 1 second of inactivity. The timer is reset each time a new hotkey key is pressed. This will prevent FlashCom from maintaining a bad keyboard state, even if keyboard events are interrupted. This change also bumps the version to 0.2.1.
1 parent 066cbbc commit fb3500b

4 files changed

Lines changed: 55 additions & 17 deletions

File tree

src/FlashCom/App.cpp

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace
99
{
1010
const std::unordered_set<uint32_t> c_hotkeyCombo{ VK_LWIN, VK_SPACE };
11+
constexpr std::chrono::milliseconds c_hotkeyExpirationTime{ 1000 };
1112
}
1213

1314
namespace FlashCom
@@ -88,28 +89,53 @@ namespace FlashCom
8889
if (isKeyDown)
8990
{
9091
m_hotkeysPressed.insert(kb->vkCode);
92+
if (m_hotkeysPressed.size() == c_hotkeyCombo.size())
93+
{
94+
SPDLOG_INFO("App::HandleLowLevelKeyboardInput - Hotkey pressed");
95+
ToggleVisibility();
96+
97+
// This dummy event is to prevent Start from thinking that the Win key was
98+
// just pressed.
99+
// See https://github.com/microsoft/PowerToys/pull/4874
100+
INPUT dummyEvent[1] = {};
101+
dummyEvent[0].type = INPUT_KEYBOARD;
102+
dummyEvent[0].ki.wVk = 0xFF;
103+
dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP;
104+
SendInput(1, dummyEvent, sizeof(INPUT));
105+
106+
// Reset hotkey state to avoid any stuck keys or accidental invocations
107+
m_hotkeysPressed.clear();
108+
109+
// Cancel reset timer
110+
if (m_hotkeyResetTimer)
111+
{
112+
m_hotkeyResetTimer.Cancel();
113+
m_hotkeyResetTimer = nullptr;
114+
}
115+
116+
return FlashCom::Input::LowLevelCallbackReturnKind::Handled;
117+
}
118+
else
119+
{
120+
// Every time a new hotkey key is pressed, (re)set a timer.
121+
// Once the timer elapses, reset hotkey key state.
122+
// This is to handle situations where key events are "eaten" before they reach us
123+
// (ex. User invokes lock with Win+L, lock screen eats the key-up events)
124+
// Otherwise keys might get stuck in a "pressed" state.
125+
if (m_hotkeyResetTimer)
126+
{
127+
m_hotkeyResetTimer.Cancel();
128+
}
129+
m_hotkeyResetTimer = winrt::WST::ThreadPoolTimer::CreateTimer(
130+
{ this, &App::OnHotkeyTimerElapsed }, c_hotkeyExpirationTime);
131+
}
91132
}
92133
else
93134
{
94135
m_hotkeysPressed.erase(kb->vkCode);
95136
}
96137

97-
if (m_hotkeysPressed.size() == c_hotkeyCombo.size())
98-
{
99-
SPDLOG_INFO("App::HandleLowLevelKeyboardInput - Hotkey pressed");
100-
ToggleVisibility();
101-
102-
// This dummy event is to prevent Start from thinking that the Win key was
103-
// just pressed.
104-
// See https://github.com/microsoft/PowerToys/pull/4874
105-
INPUT dummyEvent[1] = {};
106-
dummyEvent[0].type = INPUT_KEYBOARD;
107-
dummyEvent[0].ki.wVk = 0xFF;
108-
dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP;
109-
SendInput(1, dummyEvent, sizeof(INPUT));
110-
111-
return FlashCom::Input::LowLevelCallbackReturnKind::Handled;
112-
}
138+
113139
return FlashCom::Input::LowLevelCallbackReturnKind::Unhandled;
114140
}
115141

@@ -146,6 +172,14 @@ namespace FlashCom
146172
}
147173
}
148174

175+
void App::OnHotkeyTimerElapsed(winrt::WST::ThreadPoolTimer timer)
176+
{
177+
// See comment in App::HandleLowLevelKeyboardInput
178+
SPDLOG_INFO("App::OnHotkeyTimerElapsed - Timer elapsed, clearing hotkey state");
179+
m_hotkeysPressed.clear();
180+
m_hotkeyResetTimer = nullptr;
181+
}
182+
149183
void App::OnKeyDown(uint8_t vkeyCode)
150184
{
151185
if (vkeyCode == VK_ESCAPE)

src/FlashCom/App.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ namespace FlashCom
2828
View::TrayIcon m_trayIcon;
2929
View::Ui m_ui;
3030
std::unordered_set<uint32_t> m_hotkeysPressed;
31+
winrt::Windows::System::Threading::ThreadPoolTimer m_hotkeyResetTimer{ nullptr };
3132
bool m_isShowing{ false };
3233

3334
App(const HINSTANCE& hInstance);
3435
void HandleFocusLost();
36+
void OnHotkeyTimerElapsed(winrt::Windows::System::Threading::ThreadPoolTimer timer);
3537
void OnKeyDown(uint8_t vkeyCode);
3638
void OnKeyUp(uint8_t vkeyCode);
3739
void Show();

src/FlashCom/pch.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <winrt/Windows.Graphics.Effects.h>
1919
#include <winrt/Windows.Storage.h>
2020
#include <winrt/Windows.System.h>
21+
#include <winrt/Windows.System.Threading.h>
2122
#include <winrt/Windows.UI.Composition.h>
2223
#include <winrt/Windows.UI.Composition.Desktop.h>
2324
#include <windows.ui.composition.interop.h>
@@ -52,6 +53,7 @@ namespace winrt
5253
namespace WGDX = Windows::Graphics::DirectX;
5354
namespace WStorage = Windows::Storage;
5455
namespace WS = Windows::System;
56+
namespace WST = Windows::System::Threading;
5557
namespace WUI = Windows::UI;
5658
namespace WUIC = Windows::UI::Composition;
5759
namespace WUICD = Windows::UI::Composition::Desktop;

src/Packaging/Package.appxmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<Identity
1010
Name="164HaydenMcAfee.FlashCom"
1111
Publisher="CN=5061D826-8E2A-4755-932E-A6DD607C027B"
12-
Version="0.2.0.0" />
12+
Version="0.2.1.0" />
1313

1414
<Properties>
1515
<DisplayName>FlashCom</DisplayName>

0 commit comments

Comments
 (0)