PowerToys/src/modules/shortcut_guide/target_state.cpp

229 lines
6.2 KiB
C++
Raw Normal View History

#include "pch.h"
#include "target_state.h"
#include "common/start_visible.h"
#include "keyboard_state.h"
#include "common/shared_constants.h"
TargetState::TargetState(int ms_delay) :
// TODO: All this processing should be done w/o a separate thread etc. in pre_wnd_proc of winkey_popup to avoid
// multithreading. Use SetTimer for delayed events
delay(std::chrono::milliseconds(ms_delay)),
thread(&TargetState::thread_proc, this)
{
}
constexpr unsigned VK_S = 0x53;
bool TargetState::signal_event(unsigned vk_code, bool key_down)
{
std::unique_lock lock(mutex);
// Ignore repeated key presses
if (!events.empty() && events.back().key_down == key_down && events.back().vk_code == vk_code)
{
return false;
}
// Hide the overlay when WinKey + Shift + S is pressed
if (key_down && state == Shown && vk_code == VK_S && (GetKeyState(VK_LSHIFT) || GetKeyState(VK_RSHIFT)))
{
// We cannot use normal hide() here, there is stuff that needs deinitialization.
// It can be safely done when the user releases the WinKey.
instance->quick_hide();
}
const bool win_key_released = !key_down && (vk_code == VK_LWIN || vk_code == VK_RWIN);
constexpr auto overlay_fade_in_animation_time = std::chrono::milliseconds(300);
const auto overlay_active = state == Shown && (std::chrono::system_clock::now() - signal_timestamp > overlay_fade_in_animation_time);
const bool suppress_win_release = win_key_released && (state == ForceShown || overlay_active) && !nonwin_key_was_pressed_during_shown;
events.push_back({ key_down, vk_code });
lock.unlock();
cv.notify_one();
if (suppress_win_release)
{
// Send a fake key-stroke to prevent the start menu from appearing.
// We use 0xCF VK code, which is reserved. It still prevents the
// start menu from appearing, but should not interfere with any
// keyboard shortcuts.
INPUT input[3] = { {}, {}, {} };
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = 0xCF;
input[0].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = 0xCF;
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
input[1].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG;
input[2].type = INPUT_KEYBOARD;
input[2].ki.wVk = vk_code;
input[2].ki.dwFlags = KEYEVENTF_KEYUP;
input[2].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG;
SendInput(3, input, sizeof(INPUT));
}
return suppress_win_release;
}
void TargetState::was_hidden()
{
std::unique_lock<std::recursive_mutex> lock(mutex);
// Ignore callbacks from the D2DOverlayWindow
if (state == ForceShown)
{
return;
}
state = Hidden;
events.clear();
lock.unlock();
cv.notify_one();
}
void TargetState::exit()
{
std::unique_lock lock(mutex);
events.clear();
state = Exiting;
lock.unlock();
cv.notify_one();
thread.join();
}
KeyEvent TargetState::next()
{
auto e = events.front();
events.pop_front();
return e;
}
void TargetState::handle_hidden()
{
std::unique_lock lock(mutex);
if (events.empty())
cv.wait(lock);
if (events.empty() || state == Exiting)
return;
auto event = next();
if (event.key_down && (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN))
{
state = Timeout;
winkey_timestamp = std::chrono::system_clock::now();
}
}
void TargetState::handle_shown(const bool forced)
{
std::unique_lock lock(mutex);
if (events.empty())
{
cv.wait(lock);
}
if (events.empty() || state == Exiting)
{
return;
}
auto event = next();
if (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN)
{
if (!forced && (!event.key_down || !winkey_held()))
{
state = Hidden;
}
return;
}
if (event.key_down)
{
nonwin_key_was_pressed_during_shown = true;
lock.unlock();
instance->on_held_press(event.vk_code);
}
}
void TargetState::thread_proc()
{
while (true)
{
switch (state)
{
case Hidden:
handle_hidden();
break;
case Timeout:
handle_timeout();
break;
case Shown:
handle_shown(false);
break;
case ForceShown:
handle_shown(true);
break;
case Exiting:
default:
return;
}
}
}
void TargetState::handle_timeout()
{
std::unique_lock lock(mutex);
auto wait_time = delay - (std::chrono::system_clock::now() - winkey_timestamp);
if (events.empty())
{
cv.wait_for(lock, wait_time);
}
if (state == Exiting)
{
return;
}
// Skip all VK_*WIN-down events
while (!events.empty())
{
auto event = events.front();
if (event.key_down && (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN))
events.pop_front();
else
break;
}
// If we've detected that a user is holding anything other than VK_*WIN or start menu is visible, we should hide
if (!events.empty() || !only_winkey_key_held() || is_start_visible())
{
state = Hidden;
return;
}
if (std::chrono::system_clock::now() - winkey_timestamp < delay)
{
return;
}
signal_timestamp = std::chrono::system_clock::now();
nonwin_key_was_pressed_during_shown = false;
state = Shown;
lock.unlock();
instance->on_held();
}
void TargetState::set_delay(int ms_delay)
{
std::unique_lock lock(mutex);
delay = std::chrono::milliseconds(ms_delay);
}
void TargetState::toggle_force_shown()
{
std::unique_lock lock(mutex);
events.clear();
if (state != ForceShown)
{
state = ForceShown;
instance->on_held();
}
else
{
state = Hidden;
}
}
bool TargetState::active() const
{
return state == ForceShown || state == Shown;
}