Skip to content

Commit

Permalink
🐛 Add hotkey reset logic to avoid stuck keys (#28)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
haydenmc authored Aug 1, 2024
1 parent 066cbbc commit fb3500b
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 17 deletions.
66 changes: 50 additions & 16 deletions src/FlashCom/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace
{
const std::unordered_set<uint32_t> c_hotkeyCombo{ VK_LWIN, VK_SPACE };
constexpr std::chrono::milliseconds c_hotkeyExpirationTime{ 1000 };
}

namespace FlashCom
Expand Down Expand Up @@ -88,28 +89,53 @@ namespace FlashCom
if (isKeyDown)
{
m_hotkeysPressed.insert(kb->vkCode);
if (m_hotkeysPressed.size() == c_hotkeyCombo.size())
{
SPDLOG_INFO("App::HandleLowLevelKeyboardInput - Hotkey pressed");
ToggleVisibility();

// This dummy event is to prevent Start from thinking that the Win key was
// just pressed.
// See https://github.com/microsoft/PowerToys/pull/4874
INPUT dummyEvent[1] = {};
dummyEvent[0].type = INPUT_KEYBOARD;
dummyEvent[0].ki.wVk = 0xFF;
dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, dummyEvent, sizeof(INPUT));

// Reset hotkey state to avoid any stuck keys or accidental invocations
m_hotkeysPressed.clear();

// Cancel reset timer
if (m_hotkeyResetTimer)
{
m_hotkeyResetTimer.Cancel();
m_hotkeyResetTimer = nullptr;
}

return FlashCom::Input::LowLevelCallbackReturnKind::Handled;
}
else
{
// Every time a new hotkey key is pressed, (re)set a timer.
// Once the timer elapses, reset hotkey key state.
// This is to handle situations where key events are "eaten" before they reach us
// (ex. User invokes lock with Win+L, lock screen eats the key-up events)
// Otherwise keys might get stuck in a "pressed" state.
if (m_hotkeyResetTimer)
{
m_hotkeyResetTimer.Cancel();
}
m_hotkeyResetTimer = winrt::WST::ThreadPoolTimer::CreateTimer(
{ this, &App::OnHotkeyTimerElapsed }, c_hotkeyExpirationTime);
}
}
else
{
m_hotkeysPressed.erase(kb->vkCode);
}

if (m_hotkeysPressed.size() == c_hotkeyCombo.size())
{
SPDLOG_INFO("App::HandleLowLevelKeyboardInput - Hotkey pressed");
ToggleVisibility();

// This dummy event is to prevent Start from thinking that the Win key was
// just pressed.
// See https://github.com/microsoft/PowerToys/pull/4874
INPUT dummyEvent[1] = {};
dummyEvent[0].type = INPUT_KEYBOARD;
dummyEvent[0].ki.wVk = 0xFF;
dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, dummyEvent, sizeof(INPUT));

return FlashCom::Input::LowLevelCallbackReturnKind::Handled;
}

return FlashCom::Input::LowLevelCallbackReturnKind::Unhandled;
}

Expand Down Expand Up @@ -146,6 +172,14 @@ namespace FlashCom
}
}

void App::OnHotkeyTimerElapsed(winrt::WST::ThreadPoolTimer timer)
{
// See comment in App::HandleLowLevelKeyboardInput
SPDLOG_INFO("App::OnHotkeyTimerElapsed - Timer elapsed, clearing hotkey state");
m_hotkeysPressed.clear();
m_hotkeyResetTimer = nullptr;
}

void App::OnKeyDown(uint8_t vkeyCode)
{
if (vkeyCode == VK_ESCAPE)
Expand Down
2 changes: 2 additions & 0 deletions src/FlashCom/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ namespace FlashCom
View::TrayIcon m_trayIcon;
View::Ui m_ui;
std::unordered_set<uint32_t> m_hotkeysPressed;
winrt::Windows::System::Threading::ThreadPoolTimer m_hotkeyResetTimer{ nullptr };
bool m_isShowing{ false };

App(const HINSTANCE& hInstance);
void HandleFocusLost();
void OnHotkeyTimerElapsed(winrt::Windows::System::Threading::ThreadPoolTimer timer);
void OnKeyDown(uint8_t vkeyCode);
void OnKeyUp(uint8_t vkeyCode);
void Show();
Expand Down
2 changes: 2 additions & 0 deletions src/FlashCom/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <winrt/Windows.Graphics.Effects.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.System.Threading.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <windows.ui.composition.interop.h>
Expand Down Expand Up @@ -52,6 +53,7 @@ namespace winrt
namespace WGDX = Windows::Graphics::DirectX;
namespace WStorage = Windows::Storage;
namespace WS = Windows::System;
namespace WST = Windows::System::Threading;
namespace WUI = Windows::UI;
namespace WUIC = Windows::UI::Composition;
namespace WUICD = Windows::UI::Composition::Desktop;
Expand Down
2 changes: 1 addition & 1 deletion src/Packaging/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<Identity
Name="164HaydenMcAfee.FlashCom"
Publisher="CN=5061D826-8E2A-4755-932E-A6DD607C027B"
Version="0.2.0.0" />
Version="0.2.1.0" />

<Properties>
<DisplayName>FlashCom</DisplayName>
Expand Down

0 comments on commit fb3500b

Please sign in to comment.