mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
Detect Shortcut: Hold Esc/Enter to Cancel/Accept (#2135)
* Detect Shortcut: Hold Esc/Enter to Discard/Apply changes Bypass shorcut/single key remapping by holding the navigation keys
This commit is contained in:
committed by
GitHub
parent
5d9b71b038
commit
c37884bdb7
165
src/modules/keyboardmanager/common/KeyDelay.cpp
Normal file
165
src/modules/keyboardmanager/common/KeyDelay.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "pch.h"
|
||||
#include "KeyDelay.h"
|
||||
|
||||
KeyDelay::~KeyDelay()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_queueMutex);
|
||||
_quit = true;
|
||||
_cv.notify_all();
|
||||
l.unlock();
|
||||
_delayThread.join();
|
||||
}
|
||||
|
||||
void KeyDelay::KeyEvent(LowlevelKeyboardEvent* ev)
|
||||
{
|
||||
std::lock_guard guard(_queueMutex);
|
||||
_queue.push({ ev->lParam->time, ev->wParam });
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
KeyTimedEvent KeyDelay::NextEvent()
|
||||
{
|
||||
auto ev = _queue.front();
|
||||
_queue.pop();
|
||||
return ev;
|
||||
}
|
||||
|
||||
bool KeyDelay::CheckIfMillisHaveElapsed(DWORD first, DWORD last, DWORD duration)
|
||||
{
|
||||
if (first < last && first <= first + duration)
|
||||
{
|
||||
return first + duration < last;
|
||||
}
|
||||
else
|
||||
{
|
||||
first += ULONG_MAX / 2;
|
||||
last += ULONG_MAX / 2;
|
||||
return first + duration < last;
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyDelay::HasNextEvent()
|
||||
{
|
||||
return !_queue.empty();
|
||||
}
|
||||
|
||||
bool KeyDelay::HandleRelease()
|
||||
{
|
||||
while (HasNextEvent())
|
||||
{
|
||||
auto ev = NextEvent();
|
||||
switch (ev.message)
|
||||
{
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
_state = KeyDelayState::ON_HOLD;
|
||||
_initialHoldKeyDown = ev.time;
|
||||
return false;
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KeyDelay::HandleOnHold(std::unique_lock<std::mutex>& cvLock)
|
||||
{
|
||||
while (HasNextEvent())
|
||||
{
|
||||
auto ev = NextEvent();
|
||||
switch (ev.message)
|
||||
{
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP:
|
||||
if (CheckIfMillisHaveElapsed(_initialHoldKeyDown, ev.time, LONG_PRESS_DELAY_MILLIS))
|
||||
{
|
||||
if (_onLongPressDetected != nullptr)
|
||||
{
|
||||
_onLongPressDetected(_key);
|
||||
}
|
||||
if (_onLongPressReleased != nullptr)
|
||||
{
|
||||
_onLongPressReleased(_key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_onShortPress != nullptr)
|
||||
{
|
||||
_onShortPress(_key);
|
||||
}
|
||||
}
|
||||
_state = KeyDelayState::RELEASED;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckIfMillisHaveElapsed(_initialHoldKeyDown, GetTickCount(), LONG_PRESS_DELAY_MILLIS))
|
||||
{
|
||||
if (_onLongPressDetected != nullptr)
|
||||
{
|
||||
_onLongPressDetected(_key);
|
||||
}
|
||||
_state = KeyDelayState::ON_HOLD_TIMEOUT;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cv.wait_for(cvLock, std::chrono::milliseconds(ON_HOLD_WAIT_TIMEOUT_MILLIS));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyDelay::HandleOnHoldTimeout()
|
||||
{
|
||||
while (HasNextEvent())
|
||||
{
|
||||
auto ev = NextEvent();
|
||||
switch (ev.message)
|
||||
{
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP:
|
||||
if (_onLongPressReleased != nullptr)
|
||||
{
|
||||
_onLongPressReleased(_key);
|
||||
}
|
||||
_state = KeyDelayState::RELEASED;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KeyDelay::DelayThread()
|
||||
{
|
||||
std::unique_lock<std::mutex> qLock(_queueMutex);
|
||||
bool shouldWait = true;
|
||||
while (!_quit)
|
||||
{
|
||||
if (shouldWait)
|
||||
{
|
||||
_cv.wait(qLock);
|
||||
}
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case KeyDelayState::RELEASED:
|
||||
shouldWait = HandleRelease();
|
||||
break;
|
||||
case KeyDelayState::ON_HOLD:
|
||||
shouldWait = HandleOnHold(qLock);
|
||||
break;
|
||||
case KeyDelayState::ON_HOLD_TIMEOUT:
|
||||
shouldWait = HandleOnHoldTimeout();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/modules/keyboardmanager/common/KeyDelay.h
Normal file
91
src/modules/keyboardmanager/common/KeyDelay.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include <interface/lowlevel_keyboard_event_data.h>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
|
||||
// Available states for the KeyDelay state machine.
|
||||
enum class KeyDelayState
|
||||
{
|
||||
RELEASED,
|
||||
ON_HOLD,
|
||||
ON_HOLD_TIMEOUT,
|
||||
};
|
||||
|
||||
// Virtual key + timestamp (in millis since Windows startup)
|
||||
struct KeyTimedEvent
|
||||
{
|
||||
DWORD time;
|
||||
WPARAM message;
|
||||
};
|
||||
|
||||
// Handles delayed key inputs.
|
||||
// Implemented as a state machine running on its own thread.
|
||||
// Thread stops on destruction.
|
||||
class KeyDelay
|
||||
{
|
||||
public:
|
||||
KeyDelay(
|
||||
DWORD key,
|
||||
std::function<void(DWORD)> onShortPress,
|
||||
std::function<void(DWORD)> onLongPressDetected,
|
||||
std::function<void(DWORD)> onLongPressReleased) :
|
||||
_quit(false),
|
||||
_state(KeyDelayState::RELEASED),
|
||||
_initialHoldKeyDown(0),
|
||||
_key(key),
|
||||
_onShortPress(onShortPress),
|
||||
_onLongPressDetected(onLongPressDetected),
|
||||
_onLongPressReleased(onLongPressReleased),
|
||||
_delayThread(&KeyDelay::DelayThread, this){};
|
||||
|
||||
// Enque new KeyTimedEvent and notify the condition variable.
|
||||
void KeyEvent(LowlevelKeyboardEvent* ev);
|
||||
~KeyDelay();
|
||||
|
||||
private:
|
||||
// Runs the state machine, waits if there is no events to process.
|
||||
// Checks for _quit condition.
|
||||
void DelayThread();
|
||||
|
||||
// Manage state transitions and trigger callbacks on certain events.
|
||||
// Returns whether or not the thread should wait on new events.
|
||||
bool HandleRelease();
|
||||
bool HandleOnHold(std::unique_lock<std::mutex>& cvLock);
|
||||
bool HandleOnHoldTimeout();
|
||||
|
||||
// Get next key event in queue.
|
||||
KeyTimedEvent NextEvent();
|
||||
bool HasNextEvent();
|
||||
|
||||
// Check if <duration> milliseconds passed since <first> millisecond.
|
||||
// Also checks for overflow conditions.
|
||||
bool CheckIfMillisHaveElapsed(DWORD first, DWORD last, DWORD duration);
|
||||
|
||||
std::thread _delayThread;
|
||||
bool _quit;
|
||||
KeyDelayState _state;
|
||||
|
||||
// Callback functions, the key provided in the constructor is passed as an argument.
|
||||
std::function<void(DWORD)> _onLongPressDetected;
|
||||
std::function<void(DWORD)> _onLongPressReleased;
|
||||
std::function<void(DWORD)> _onShortPress;
|
||||
|
||||
// Queue holding key events that are not processed yet. Should be kept synchronized
|
||||
// using _queueMutex
|
||||
std::queue<KeyTimedEvent> _queue;
|
||||
std::mutex _queueMutex;
|
||||
|
||||
// DelayThread waits on this condition variable when there is no events to process.
|
||||
std::condition_variable _cv;
|
||||
|
||||
// Keeps track of the time at which the initial KEY_DOWN event happened.
|
||||
DWORD _initialHoldKeyDown;
|
||||
|
||||
// Virtual Key provided in the constructor. Passed to callback functions.
|
||||
DWORD _key;
|
||||
|
||||
static const DWORD LONG_PRESS_DELAY_MILLIS = 900;
|
||||
static const DWORD ON_HOLD_WAIT_TIMEOUT_MILLIS = 50;
|
||||
};
|
||||
@@ -89,6 +89,7 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Helpers.cpp" />
|
||||
<ClCompile Include="KeyboardManagerState.cpp" />
|
||||
<ClCompile Include="KeyDelay.cpp" />
|
||||
<ClCompile Include="LayoutMap.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
@@ -100,6 +101,7 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Helpers.h" />
|
||||
<ClInclude Include="KeyboardManagerState.h" />
|
||||
<ClInclude Include="KeyDelay.h" />
|
||||
<ClInclude Include="LayoutMap.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="RemapShortcut.h" />
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
<ClCompile Include="RemapShortcut.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="KeyDelay.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="KeyboardManagerState.h">
|
||||
@@ -53,5 +56,8 @@
|
||||
<ClInclude Include="RemapShortcut.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="KeyDelay.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -132,7 +132,6 @@ void KeyboardManagerState::ConfigureDetectSingleKeyRemapUI(const StackPanel& tex
|
||||
currentSingleKeyUI = textBlock;
|
||||
}
|
||||
|
||||
|
||||
void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring& key)
|
||||
{
|
||||
// Textblock to display the detected key
|
||||
@@ -140,7 +139,7 @@ void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring
|
||||
Border border;
|
||||
|
||||
border.Padding({ 20, 10, 20, 10 });
|
||||
border.Margin({0, 0, 10, 0 });
|
||||
border.Margin({ 0, 0, 10, 0 });
|
||||
border.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
|
||||
remapKey.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
|
||||
remapKey.FontSize(20);
|
||||
@@ -160,18 +159,17 @@ void KeyboardManagerState::UpdateDetectShortcutUI()
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::unique_lock<std::mutex> detectedShortcut_lock(detectedShortcut_mutex);
|
||||
std::unique_lock<std::mutex> currentShortcut_lock(currentShortcut_mutex);
|
||||
// Save the latest displayed shortcut
|
||||
currentShortcut = detectedShortcut;
|
||||
auto detectedShortcutCopy = detectedShortcut;
|
||||
currentShortcut_lock.unlock();
|
||||
detectedShortcut_lock.unlock();
|
||||
|
||||
// Since this function is invoked from the back-end thread, in order to update the UI the dispatcher must be used.
|
||||
currentShortcutUI.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [this]() {
|
||||
|
||||
std::vector<hstring> shortcut = detectedShortcut.GetKeyVector(keyboardMap);
|
||||
currentShortcutUI.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [this, detectedShortcutCopy]() {
|
||||
std::vector<hstring> shortcut = detectedShortcutCopy.GetKeyVector(keyboardMap);
|
||||
currentShortcutUI.Children().Clear();
|
||||
for (auto& key : shortcut)
|
||||
{
|
||||
@@ -213,20 +211,49 @@ DWORD KeyboardManagerState::GetDetectedSingleRemapKey()
|
||||
return detectedRemapKey;
|
||||
}
|
||||
|
||||
void KeyboardManagerState::SelectDetectedRemapKey(DWORD key)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(detectedRemapKey_mutex);
|
||||
detectedRemapKey = key;
|
||||
UpdateDetectSingleKeyRemapUI();
|
||||
return;
|
||||
}
|
||||
|
||||
void KeyboardManagerState::SelectDetectedShortcut(DWORD key)
|
||||
{
|
||||
// Set the new key and store if a change occured
|
||||
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
|
||||
bool updateUI = detectedShortcut.SetKey(key);
|
||||
lock.unlock();
|
||||
|
||||
if (updateUI)
|
||||
{
|
||||
// Update the UI. This function is called here because it should store the set of keys pressed till the last key which was pressed down.
|
||||
UpdateDetectShortcutUI();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void KeyboardManagerState::ResetDetectedShortcutKey(DWORD key)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(detectedShortcut_mutex);
|
||||
detectedShortcut.ResetKey(key);
|
||||
}
|
||||
|
||||
// Function which can be used in HandleKeyboardHookEvent before the single key remap event to use the UI and suppress events while the remap window is active.
|
||||
bool KeyboardManagerState::DetectSingleRemapKeyUIBackend(LowlevelKeyboardEvent* data)
|
||||
{
|
||||
// Check if the detect key UI window has been activated
|
||||
if (CheckUIState(KeyboardManagerUIState::DetectSingleKeyRemapWindowActivated))
|
||||
{
|
||||
if (HandleKeyDelayEvent(data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// detect the key if it is pressed down
|
||||
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
|
||||
{
|
||||
std::unique_lock<std::mutex> detectedRemapKey_lock(detectedRemapKey_mutex);
|
||||
detectedRemapKey = data->lParam->vkCode;
|
||||
detectedRemapKey_lock.unlock();
|
||||
|
||||
UpdateDetectSingleKeyRemapUI();
|
||||
SelectDetectedRemapKey(data->lParam->vkCode);
|
||||
}
|
||||
|
||||
// Suppress the keyboard event
|
||||
@@ -242,25 +269,20 @@ bool KeyboardManagerState::DetectShortcutUIBackend(LowlevelKeyboardEvent* data)
|
||||
// Check if the detect shortcut UI window has been activated
|
||||
if (CheckUIState(KeyboardManagerUIState::DetectShortcutWindowActivated))
|
||||
{
|
||||
if (HandleKeyDelayEvent(data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add the key if it is pressed down
|
||||
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
|
||||
{
|
||||
// Set the new key and store if a change occured
|
||||
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
|
||||
bool updateUI = detectedShortcut.SetKey(data->lParam->vkCode);
|
||||
lock.unlock();
|
||||
|
||||
if (updateUI)
|
||||
{
|
||||
// Update the UI. This function is called here because it should store the set of keys pressed till the last key which was pressed down.
|
||||
UpdateDetectShortcutUI();
|
||||
}
|
||||
SelectDetectedShortcut(data->lParam->vkCode);
|
||||
}
|
||||
// Remove the key if it has been released
|
||||
else if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(detectedShortcut_mutex);
|
||||
detectedShortcut.ResetKey(data->lParam->vkCode);
|
||||
ResetDetectedShortcutKey(data->lParam->vkCode);
|
||||
}
|
||||
|
||||
// Suppress the keyboard event
|
||||
@@ -278,4 +300,48 @@ bool KeyboardManagerState::DetectShortcutUIBackend(LowlevelKeyboardEvent* data)
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void KeyboardManagerState::RegisterKeyDelay(
|
||||
DWORD key,
|
||||
std::function<void(DWORD)> onShortPress,
|
||||
std::function<void(DWORD)> onLongPressDetected,
|
||||
std::function<void(DWORD)> onLongPressReleased)
|
||||
{
|
||||
std::lock_guard l(keyDelays_mutex);
|
||||
|
||||
if (keyDelays.find(key) != keyDelays.end())
|
||||
{
|
||||
throw std::invalid_argument("This key was already registered.");
|
||||
}
|
||||
keyDelays[key] = std::make_unique<KeyDelay>(key, onShortPress, onLongPressDetected, onLongPressReleased);
|
||||
}
|
||||
|
||||
void KeyboardManagerState::UnregisterKeyDelay(DWORD key)
|
||||
{
|
||||
std::lock_guard l(keyDelays_mutex);
|
||||
|
||||
auto deleted = keyDelays.erase(key);
|
||||
if (deleted == 0)
|
||||
{
|
||||
throw std::invalid_argument("The key was not previously registered.");
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyboardManagerState::HandleKeyDelayEvent(LowlevelKeyboardEvent* ev)
|
||||
{
|
||||
if (currentUIWindow != GetForegroundWindow())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard l(keyDelays_mutex);
|
||||
|
||||
if (keyDelays.find(ev->lParam->vkCode) == keyDelays.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
keyDelays[ev->lParam->vkCode]->KeyEvent(ev);
|
||||
return true;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "LayoutMap.h"
|
||||
#include "Shortcut.h"
|
||||
#include "RemapShortcut.h"
|
||||
#include "KeyDelay.h"
|
||||
#include <interface/lowlevel_keyboard_event_data.h>
|
||||
#include <mutex>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
@@ -51,8 +52,13 @@ private:
|
||||
StackPanel currentShortcutUI;
|
||||
std::mutex currentShortcutUI_mutex;
|
||||
|
||||
// Registered KeyDelay objects, used to notify delayed key events.
|
||||
std::map<DWORD, std::unique_ptr<KeyDelay>> keyDelays;
|
||||
std::mutex keyDelays_mutex;
|
||||
|
||||
// Display a key by appending a border Control as a child of the panel.
|
||||
void AddKeyToLayout(const StackPanel& panel, const winrt::hstring& key);
|
||||
|
||||
public:
|
||||
// The map members and their mutexes are left as public since the maps are used extensively in dllmain.cpp.
|
||||
// Maps which store the remappings for each of the features. The bool fields should be initalised to false. They are used to check the current state of the shortcut (i.e is that particular shortcut currently pressed down or not).
|
||||
@@ -125,4 +131,30 @@ public:
|
||||
|
||||
// Function which can be used in HandleKeyboardHookEvent before the os level shortcut remap event to use the UI and suppress events while the remap window is active.
|
||||
bool DetectShortcutUIBackend(LowlevelKeyboardEvent* data);
|
||||
|
||||
// Add a KeyDelay object to get delayed key presses events for a given virtual key
|
||||
// NOTE: this will throw an exception if a virtual key is registered twice.
|
||||
// NOTE*: the virtual key should represent the original, unmapped virtual key.
|
||||
void RegisterKeyDelay(
|
||||
DWORD key,
|
||||
std::function<void(DWORD)> onShortPress,
|
||||
std::function<void(DWORD)> onLongPressDetected,
|
||||
std::function<void(DWORD)> onLongPressReleased);
|
||||
|
||||
// Remove a KeyDelay.
|
||||
// NOTE: this method will throw if the virtual key is not registered beforehand.
|
||||
// NOTE*: the virtual key should represent the original, unmapped virtual key.
|
||||
void UnregisterKeyDelay(DWORD key);
|
||||
|
||||
// Handle a key event, for a delayed key.
|
||||
bool HandleKeyDelayEvent(LowlevelKeyboardEvent* ev);
|
||||
|
||||
// Update the currently selected single key remap
|
||||
void SelectDetectedRemapKey(DWORD key);
|
||||
|
||||
// Update the currently selected shortcut.
|
||||
void SelectDetectedShortcut(DWORD key);
|
||||
|
||||
// Reset the shortcut (backend) state after releasing a key.
|
||||
void ResetDetectedShortcutKey(DWORD key);
|
||||
};
|
||||
@@ -411,7 +411,7 @@ winrt::hstring Shortcut::ToHstring(LayoutMap& keyboardMap)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<winrt::hstring> Shortcut::GetKeyVector(LayoutMap& keyboardMap)
|
||||
std::vector<winrt::hstring> Shortcut::GetKeyVector(LayoutMap& keyboardMap) const
|
||||
{
|
||||
std::vector<winrt::hstring> keys;
|
||||
if (winKey != ModifierKey::Disabled)
|
||||
|
||||
@@ -140,7 +140,7 @@ public:
|
||||
winrt::hstring ToHstring(LayoutMap& keyboardMap);
|
||||
|
||||
// Function to return a vector of hstring for each key, in the same order as ToHstring()
|
||||
std::vector<winrt::hstring> GetKeyVector(LayoutMap& keyboardMap);
|
||||
std::vector<winrt::hstring> GetKeyVector(LayoutMap& keyboardMap) const;
|
||||
|
||||
// Function to check if all the modifiers in the shortcut have been pressed down
|
||||
bool CheckModifiersKeyboardState() const;
|
||||
|
||||
@@ -72,15 +72,31 @@ void ShortcutControl::createDetectShortcutWindow(IInspectable const& sender, Xam
|
||||
// ContentDialog requires manually setting the XamlRoot (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands)
|
||||
detectShortcutBox.XamlRoot(xamlRoot);
|
||||
detectShortcutBox.Title(box_value(L"Press the keys in shortcut:"));
|
||||
detectShortcutBox.PrimaryButtonText(to_hstring(L"OK"));
|
||||
detectShortcutBox.IsPrimaryButtonEnabled(false);
|
||||
detectShortcutBox.IsSecondaryButtonEnabled(false);
|
||||
detectShortcutBox.CloseButtonText(to_hstring(L"Cancel"));
|
||||
|
||||
// Get the linked text block for the "Type shortcut" button that was clicked
|
||||
TextBlock linkedShortcutText = getSiblingElement(sender).as<TextBlock>();
|
||||
|
||||
// OK button
|
||||
detectShortcutBox.PrimaryButtonClick([=, &shortcutRemapBuffer, &keyboardManagerState](Windows::UI::Xaml::Controls::ContentDialog const& sender, ContentDialogButtonClickEventArgs const&) {
|
||||
auto unregisterKeys = [&keyboardManagerState]() {
|
||||
std::thread t1(&KeyboardManagerState::UnregisterKeyDelay, &keyboardManagerState, VK_ESCAPE);
|
||||
std::thread t2(&KeyboardManagerState::UnregisterKeyDelay, &keyboardManagerState, VK_RETURN);
|
||||
t1.detach();
|
||||
t2.detach();
|
||||
};
|
||||
|
||||
auto selectDetectedShortcutAndResetKeys = [&keyboardManagerState](DWORD key) {
|
||||
keyboardManagerState.SelectDetectedShortcut(key);
|
||||
keyboardManagerState.ResetDetectedShortcutKey(key);
|
||||
};
|
||||
|
||||
auto onAccept = [linkedShortcutText,
|
||||
detectShortcutBox,
|
||||
&keyboardManagerState,
|
||||
&shortcutRemapBuffer,
|
||||
unregisterKeys,
|
||||
rowIndex,
|
||||
colIndex] {
|
||||
// Save the detected shortcut in the linked text block
|
||||
Shortcut detectedShortcutKeys = keyboardManagerState.GetDetectedShortcut();
|
||||
|
||||
@@ -92,14 +108,71 @@ void ShortcutControl::createDetectShortcutWindow(IInspectable const& sender, Xam
|
||||
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
unregisterKeys();
|
||||
detectShortcutBox.Hide();
|
||||
};
|
||||
|
||||
TextBlock primaryButtonText;
|
||||
primaryButtonText.Text(to_hstring(L"OK"));
|
||||
|
||||
Button primaryButton;
|
||||
primaryButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
primaryButton.Margin({ 2, 2, 2, 2 });
|
||||
primaryButton.Content(primaryButtonText);
|
||||
|
||||
// OK button
|
||||
primaryButton.Click([onAccept](IInspectable const& sender, RoutedEventArgs const&) {
|
||||
onAccept();
|
||||
});
|
||||
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_RETURN,
|
||||
selectDetectedShortcutAndResetKeys,
|
||||
[primaryButton, detectShortcutBox](DWORD) {
|
||||
detectShortcutBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[primaryButton] {
|
||||
primaryButton.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::DarkGray() });
|
||||
});
|
||||
},
|
||||
[onAccept, detectShortcutBox](DWORD) {
|
||||
detectShortcutBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[onAccept] {
|
||||
onAccept();
|
||||
});
|
||||
});
|
||||
|
||||
TextBlock cancelButtonText;
|
||||
cancelButtonText.Text(to_hstring(L"Cancel"));
|
||||
|
||||
Button cancelButton;
|
||||
cancelButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
cancelButton.Margin({ 2, 2, 2, 2 });
|
||||
cancelButton.Content(cancelButtonText);
|
||||
// Cancel button
|
||||
detectShortcutBox.CloseButtonClick([&keyboardManagerState](Windows::UI::Xaml::Controls::ContentDialog const& sender, ContentDialogButtonClickEventArgs const&) {
|
||||
cancelButton.Click([detectShortcutBox, unregisterKeys, &keyboardManagerState](IInspectable const& sender, RoutedEventArgs const&) {
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
unregisterKeys();
|
||||
detectShortcutBox.Hide();
|
||||
});
|
||||
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_ESCAPE,
|
||||
selectDetectedShortcutAndResetKeys,
|
||||
[&keyboardManagerState, detectShortcutBox, unregisterKeys](DWORD) {
|
||||
detectShortcutBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[detectShortcutBox] {
|
||||
detectShortcutBox.Hide();
|
||||
});
|
||||
|
||||
keyboardManagerState.ResetUIState();
|
||||
unregisterKeys();
|
||||
},
|
||||
nullptr);
|
||||
|
||||
// StackPanel parent for the displayed text in the dialog
|
||||
Windows::UI::Xaml::Controls::StackPanel stackPanel;
|
||||
detectShortcutBox.Content(stackPanel);
|
||||
@@ -112,9 +185,36 @@ void ShortcutControl::createDetectShortcutWindow(IInspectable const& sender, Xam
|
||||
|
||||
// Target StackPanel to place the selected key
|
||||
Windows::UI::Xaml::Controls::StackPanel keyStackPanel;
|
||||
stackPanel.Children().Append(keyStackPanel);
|
||||
keyStackPanel.Orientation(Orientation::Horizontal);
|
||||
stackPanel.Children().Append(keyStackPanel);
|
||||
|
||||
TextBlock holdEscInfo;
|
||||
holdEscInfo.Text(winrt::to_hstring("Hold Esc to discard"));
|
||||
holdEscInfo.FontSize(12);
|
||||
holdEscInfo.Margin({ 0, 20, 0, 0 });
|
||||
stackPanel.Children().Append(holdEscInfo);
|
||||
|
||||
TextBlock holdEnterInfo;
|
||||
holdEnterInfo.Text(winrt::to_hstring("Hold Enter to apply"));
|
||||
holdEnterInfo.FontSize(12);
|
||||
holdEnterInfo.Margin({ 0, 0, 0, 0 });
|
||||
stackPanel.Children().Append(holdEnterInfo);
|
||||
|
||||
ColumnDefinition primaryButtonColumn;
|
||||
ColumnDefinition cancelButtonColumn;
|
||||
|
||||
Grid buttonPanel;
|
||||
buttonPanel.Margin({ 0, 20, 0, 0 });
|
||||
buttonPanel.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
buttonPanel.ColumnDefinitions().Append(primaryButtonColumn);
|
||||
buttonPanel.ColumnDefinitions().Append(cancelButtonColumn);
|
||||
buttonPanel.SetColumn(primaryButton, 0);
|
||||
buttonPanel.SetColumn(cancelButton, 1);
|
||||
|
||||
buttonPanel.Children().Append(primaryButton);
|
||||
buttonPanel.Children().Append(cancelButton);
|
||||
|
||||
stackPanel.Children().Append(buttonPanel);
|
||||
stackPanel.UpdateLayout();
|
||||
|
||||
// Configure the keyboardManagerState to store the UI information.
|
||||
|
||||
@@ -68,21 +68,32 @@ void SingleKeyRemapControl::createDetectKeyWindow(IInspectable const& sender, Xa
|
||||
{
|
||||
// ContentDialog for detecting remap key. This is the parent UI element.
|
||||
ContentDialog detectRemapKeyBox;
|
||||
|
||||
|
||||
// TODO: Hardcoded light theme, since the app is not theme aware ATM.
|
||||
detectRemapKeyBox.RequestedTheme(ElementTheme::Light);
|
||||
// ContentDialog requires manually setting the XamlRoot (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands)
|
||||
detectRemapKeyBox.XamlRoot(xamlRoot);
|
||||
detectRemapKeyBox.Title(box_value(L"Press a key on selected keyboard:"));
|
||||
detectRemapKeyBox.PrimaryButtonText(to_hstring(L"OK"));
|
||||
detectRemapKeyBox.IsPrimaryButtonEnabled(false);
|
||||
detectRemapKeyBox.IsSecondaryButtonEnabled(false);
|
||||
detectRemapKeyBox.CloseButtonText(to_hstring(L"Cancel"));
|
||||
|
||||
// Get the linked text block for the "Type Key" button that was clicked
|
||||
TextBlock linkedRemapText = getSiblingElement(sender).as<TextBlock>();
|
||||
|
||||
// OK button
|
||||
detectRemapKeyBox.PrimaryButtonClick([=, &singleKeyRemapBuffer, &keyboardManagerState](Windows::UI::Xaml::Controls::ContentDialog const& sender, ContentDialogButtonClickEventArgs const&) {
|
||||
auto unregisterKeys = [&keyboardManagerState]() {
|
||||
std::thread t1(&KeyboardManagerState::UnregisterKeyDelay, &keyboardManagerState, VK_ESCAPE);
|
||||
std::thread t2(&KeyboardManagerState::UnregisterKeyDelay, &keyboardManagerState, VK_RETURN);
|
||||
t1.detach();
|
||||
t2.detach();
|
||||
};
|
||||
|
||||
auto onAccept = [linkedRemapText,
|
||||
detectRemapKeyBox,
|
||||
&keyboardManagerState,
|
||||
&singleKeyRemapBuffer,
|
||||
unregisterKeys,
|
||||
rowIndex,
|
||||
colIndex] {
|
||||
// Save the detected key in the linked text block
|
||||
DWORD detectedKey = keyboardManagerState.GetDetectedSingleRemapKey();
|
||||
|
||||
@@ -94,14 +105,69 @@ void SingleKeyRemapControl::createDetectKeyWindow(IInspectable const& sender, Xa
|
||||
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
unregisterKeys();
|
||||
detectRemapKeyBox.Hide();
|
||||
};
|
||||
|
||||
TextBlock primaryButtonText;
|
||||
primaryButtonText.Text(to_hstring(L"OK"));
|
||||
|
||||
Button primaryButton;
|
||||
primaryButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
primaryButton.Margin({ 2, 2, 2, 2 });
|
||||
primaryButton.Content(primaryButtonText);
|
||||
primaryButton.Click([onAccept](IInspectable const& sender, RoutedEventArgs const&) {
|
||||
onAccept();
|
||||
});
|
||||
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_RETURN,
|
||||
std::bind(&KeyboardManagerState::SelectDetectedRemapKey, &keyboardManagerState, std::placeholders::_1),
|
||||
[primaryButton, detectRemapKeyBox](DWORD) {
|
||||
detectRemapKeyBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[primaryButton] {
|
||||
primaryButton.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::DarkGray() });
|
||||
});
|
||||
},
|
||||
[onAccept, detectRemapKeyBox](DWORD) {
|
||||
detectRemapKeyBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[onAccept] {
|
||||
onAccept();
|
||||
});
|
||||
});
|
||||
|
||||
TextBlock cancelButtonText;
|
||||
cancelButtonText.Text(to_hstring(L"Cancel"));
|
||||
|
||||
Button cancelButton;
|
||||
cancelButton.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
cancelButton.Margin({ 2, 2, 2, 2 });
|
||||
cancelButton.Content(cancelButtonText);
|
||||
// Cancel button
|
||||
detectRemapKeyBox.CloseButtonClick([&keyboardManagerState](Windows::UI::Xaml::Controls::ContentDialog const& sender, ContentDialogButtonClickEventArgs const&) {
|
||||
cancelButton.Click([detectRemapKeyBox, unregisterKeys, &keyboardManagerState](IInspectable const& sender, RoutedEventArgs const&) {
|
||||
// Reset the keyboard manager UI state
|
||||
keyboardManagerState.ResetUIState();
|
||||
unregisterKeys();
|
||||
detectRemapKeyBox.Hide();
|
||||
});
|
||||
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_ESCAPE,
|
||||
std::bind(&KeyboardManagerState::SelectDetectedRemapKey, &keyboardManagerState, std::placeholders::_1),
|
||||
[&keyboardManagerState, detectRemapKeyBox, unregisterKeys](DWORD) {
|
||||
detectRemapKeyBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[detectRemapKeyBox] {
|
||||
detectRemapKeyBox.Hide();
|
||||
});
|
||||
|
||||
keyboardManagerState.ResetUIState();
|
||||
unregisterKeys();
|
||||
},
|
||||
nullptr);
|
||||
|
||||
// StackPanel parent for the displayed text in the dialog
|
||||
Windows::UI::Xaml::Controls::StackPanel stackPanel;
|
||||
detectRemapKeyBox.Content(stackPanel);
|
||||
@@ -114,8 +180,36 @@ void SingleKeyRemapControl::createDetectKeyWindow(IInspectable const& sender, Xa
|
||||
|
||||
// Target StackPanel to place the selected key
|
||||
Windows::UI::Xaml::Controls::StackPanel keyStackPanel;
|
||||
stackPanel.Children().Append(keyStackPanel);
|
||||
keyStackPanel.Orientation(Orientation::Horizontal);
|
||||
stackPanel.Children().Append(keyStackPanel);
|
||||
|
||||
TextBlock holdEscInfo;
|
||||
holdEscInfo.Text(winrt::to_hstring("Hold Esc to discard"));
|
||||
holdEscInfo.FontSize(12);
|
||||
holdEscInfo.Margin({ 0, 20, 0, 0 });
|
||||
stackPanel.Children().Append(holdEscInfo);
|
||||
|
||||
TextBlock holdEnterInfo;
|
||||
holdEnterInfo.Text(winrt::to_hstring("Hold Enter to apply"));
|
||||
holdEnterInfo.FontSize(12);
|
||||
holdEnterInfo.Margin({ 0, 0, 0, 0 });
|
||||
stackPanel.Children().Append(holdEnterInfo);
|
||||
|
||||
ColumnDefinition primaryButtonColumn;
|
||||
ColumnDefinition cancelButtonColumn;
|
||||
|
||||
Grid buttonPanel;
|
||||
buttonPanel.Margin({ 0, 20, 0, 0 });
|
||||
buttonPanel.HorizontalAlignment(HorizontalAlignment::Stretch);
|
||||
buttonPanel.ColumnDefinitions().Append(primaryButtonColumn);
|
||||
buttonPanel.ColumnDefinitions().Append(cancelButtonColumn);
|
||||
buttonPanel.SetColumn(primaryButton, 0);
|
||||
buttonPanel.SetColumn(cancelButton, 1);
|
||||
|
||||
buttonPanel.Children().Append(primaryButton);
|
||||
buttonPanel.Children().Append(cancelButton);
|
||||
|
||||
stackPanel.Children().Append(buttonPanel);
|
||||
stackPanel.UpdateLayout();
|
||||
|
||||
// Configure the keyboardManagerState to store the UI information.
|
||||
|
||||
Reference in New Issue
Block a user