KeyDelay.h
 1  #pragma once
 2  #include <functional>
 3  #include <thread>
 4  #include <queue>
 5  #include <mutex>
 6  
 7  #include <common/hooks/LowlevelKeyboardEvent.h>
 8  // Available states for the KeyDelay state machine.
 9  enum class KeyDelayState
10  {
11      RELEASED,
12      ON_HOLD,
13      ON_HOLD_TIMEOUT,
14  };
15  
16  // Virtual key + timestamp (in millis since Windows startup)
17  struct KeyTimedEvent
18  {
19      DWORD64 time;
20      WPARAM message;
21  };
22  
23  // Handles delayed key inputs.
24  // Implemented as a state machine running on its own thread.
25  // Thread stops on destruction.
26  class KeyDelay
27  {
28  public:
29      KeyDelay(
30          DWORD key,
31          std::function<void(DWORD)> onShortPress,
32          std::function<void(DWORD)> onLongPressDetected,
33          std::function<void(DWORD)> onLongPressReleased) :
34          _quit(false),
35          _state(KeyDelayState::RELEASED),
36          _initialHoldKeyDown(0),
37          _key(key),
38          _onShortPress(onShortPress),
39          _onLongPressDetected(onLongPressDetected),
40          _onLongPressReleased(onLongPressReleased),
41          _delayThread(&KeyDelay::DelayThread, this){};
42  
43      // Enqueue new KeyTimedEvent and notify the condition variable.
44      void KeyEvent(LowlevelKeyboardEvent* ev);
45      ~KeyDelay();
46  
47  private:
48      // Runs the state machine, waits if there is no events to process.
49      // Checks for _quit condition.
50      void DelayThread();
51  
52      // Manage state transitions and trigger callbacks on certain events.
53      // Returns whether or not the thread should wait on new events.
54      bool HandleRelease();
55      bool HandleOnHold(std::unique_lock<std::mutex>& cvLock);
56      bool HandleOnHoldTimeout();
57  
58      // Get next key event in queue.
59      KeyTimedEvent NextEvent();
60      bool HasNextEvent();
61  
62      // Check if <duration> milliseconds passed since <first> millisecond.
63      // Also checks for overflow conditions.
64      bool CheckIfMillisHaveElapsed(DWORD64 first, DWORD64 last, DWORD64 duration);
65  
66      bool _quit;
67      KeyDelayState _state;
68  
69      // Callback functions, the key provided in the constructor is passed as an argument.
70      std::function<void(DWORD)> _onLongPressDetected;
71      std::function<void(DWORD)> _onLongPressReleased;
72      std::function<void(DWORD)> _onShortPress;
73  
74      // Queue holding key events that are not processed yet. Should be kept synchronized
75      // using _queueMutex
76      std::queue<KeyTimedEvent> _queue;
77      std::mutex _queueMutex;
78  
79      // DelayThread waits on this condition variable when there is no events to process.
80      std::condition_variable _cv;
81  
82      // Keeps track of the time at which the initial KEY_DOWN event happened.
83      DWORD64 _initialHoldKeyDown;
84  
85      // Virtual Key provided in the constructor. Passed to callback functions.
86      DWORD _key;
87  
88      // Declare _delayThread after all other members so that it is the last to be initialized by the constructor
89      std::thread _delayThread;
90  
91      static const DWORD64 LONG_PRESS_DELAY_MILLIS = 900;
92      static const DWORD64 ON_HOLD_WAIT_TIMEOUT_MILLIS = 50;
93  };