mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-01-18 06:29:44 +08:00
Shortcut guide: add support for hotkeys + comments (#4517)
This commit is contained in:
parent
738b5f5707
commit
16528888df
@ -3,7 +3,8 @@
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
D2DWindow::D2DWindow()
|
||||
D2DWindow::D2DWindow(std::optional<std::function<std::remove_pointer_t<WNDPROC>>> _pre_wnd_proc) :
|
||||
pre_wnd_proc(std::move(_pre_wnd_proc))
|
||||
{
|
||||
static const WCHAR* class_name = L"PToyD2DPopup";
|
||||
WNDCLASS wc = {};
|
||||
@ -36,6 +37,7 @@ void D2DWindow::show(UINT x, UINT y, UINT width, UINT height)
|
||||
}
|
||||
base_resize(width, height);
|
||||
render_empty();
|
||||
hidden = false;
|
||||
on_show();
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, x, y, width, height, 0);
|
||||
ShowWindow(hwnd, SW_SHOWNORMAL);
|
||||
@ -44,6 +46,7 @@ void D2DWindow::show(UINT x, UINT y, UINT width, UINT height)
|
||||
|
||||
void D2DWindow::hide()
|
||||
{
|
||||
hidden = true;
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
on_hide();
|
||||
}
|
||||
@ -185,6 +188,11 @@ D2DWindow* D2DWindow::this_from_hwnd(HWND window)
|
||||
|
||||
LRESULT __stdcall D2DWindow::d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
|
||||
{
|
||||
auto self = this_from_hwnd(window);
|
||||
if (self && self->pre_wnd_proc.has_value())
|
||||
{
|
||||
(*self->pre_wnd_proc)(window, message, wparam, lparam);
|
||||
}
|
||||
switch (message)
|
||||
{
|
||||
case WM_NCCREATE:
|
||||
@ -195,11 +203,12 @@ LRESULT __stdcall D2DWindow::d2d_window_proc(HWND window, UINT message, WPARAM w
|
||||
}
|
||||
case WM_MOVE:
|
||||
case WM_SIZE:
|
||||
this_from_hwnd(window)->base_resize((unsigned)lparam & 0xFFFF, (unsigned)lparam >> 16);
|
||||
// Fall through to call 'base_render()'
|
||||
self->base_resize((unsigned)lparam & 0xFFFF, (unsigned)lparam >> 16);
|
||||
[[fallthrough]];
|
||||
case WM_PAINT:
|
||||
this_from_hwnd(window)->base_render();
|
||||
self->base_render();
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
@ -11,10 +11,13 @@
|
||||
#include <string>
|
||||
#include "d2d_svg.h"
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
class D2DWindow
|
||||
{
|
||||
public:
|
||||
D2DWindow();
|
||||
D2DWindow(std::optional<std::function<std::remove_pointer_t<WNDPROC>>> pre_wnd_proc = std::nullopt);
|
||||
void show(UINT x, UINT y, UINT width, UINT height);
|
||||
void hide();
|
||||
void initialize();
|
||||
@ -43,6 +46,7 @@ protected:
|
||||
void render_empty();
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
bool hidden = true;
|
||||
bool initialized = false;
|
||||
HWND hwnd;
|
||||
UINT window_width, window_height;
|
||||
@ -58,4 +62,6 @@ protected:
|
||||
winrt::com_ptr<ID2D1Factory6> d2d_factory;
|
||||
winrt::com_ptr<ID2D1Device5> d2d_device;
|
||||
winrt::com_ptr<ID2D1DeviceContext5> d2d_dc;
|
||||
|
||||
std::optional<std::function<std::remove_pointer_t<WNDPROC>>> pre_wnd_proc;
|
||||
};
|
||||
|
@ -3,15 +3,21 @@
|
||||
|
||||
bool is_start_visible()
|
||||
{
|
||||
static winrt::com_ptr<IAppVisibility> app_visibility;
|
||||
static const auto app_visibility = []() {
|
||||
winrt::com_ptr<IAppVisibility> result;
|
||||
CoCreateInstance(CLSID_AppVisibility,
|
||||
nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
__uuidof(result),
|
||||
result.put_void());
|
||||
return result;
|
||||
}();
|
||||
|
||||
if (!app_visibility)
|
||||
{
|
||||
winrt::check_hresult(CoCreateInstance(CLSID_AppVisibility,
|
||||
nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
__uuidof(app_visibility),
|
||||
app_visibility.put_void()));
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOL visible;
|
||||
auto result = app_visibility->IsLauncherVisible(&visible);
|
||||
return SUCCEEDED(result) && visible;
|
||||
|
@ -184,8 +184,8 @@ D2D1_RECT_F D2DOverlaySVG::get_snap_right() const
|
||||
return result;
|
||||
}
|
||||
|
||||
D2DOverlayWindow::D2DOverlayWindow() :
|
||||
total_screen({}), animation(0.3)
|
||||
D2DOverlayWindow::D2DOverlayWindow(std::optional<std::function<std::remove_pointer_t<WNDPROC>>> pre_wnd_proc) :
|
||||
total_screen({}), animation(0.3), D2DWindow(std::move(pre_wnd_proc))
|
||||
{
|
||||
tasklist_thread = std::thread([&] {
|
||||
while (running)
|
||||
@ -213,6 +213,7 @@ D2DOverlayWindow::D2DOverlayWindow() :
|
||||
void D2DOverlayWindow::show(HWND active_window, bool snappable)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
hidden = false;
|
||||
tasklist_buttons.clear();
|
||||
this->active_window = active_window;
|
||||
this->active_window_snappable = snappable;
|
||||
@ -473,6 +474,11 @@ void D2DOverlayWindow::quick_hide()
|
||||
}
|
||||
}
|
||||
|
||||
HWND D2DOverlayWindow::get_window_handle()
|
||||
{
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
float D2DOverlayWindow::get_overlay_opacity()
|
||||
{
|
||||
return overlay_opacity;
|
||||
@ -619,12 +625,12 @@ void D2DOverlayWindow::hide_thumbnail()
|
||||
|
||||
void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_dc)
|
||||
{
|
||||
if (!winkey_held() || is_start_visible())
|
||||
if (!hidden && !instance->overlay_visible())
|
||||
{
|
||||
hide();
|
||||
instance->was_hidden();
|
||||
return;
|
||||
}
|
||||
|
||||
d2d_dc->Clear();
|
||||
int x_offset = 0, y_offset = 0, dimension = 0;
|
||||
auto current_anim_value = (float)animation.value(Animation::AnimFunctions::LINEAR);
|
||||
|
@ -46,7 +46,7 @@ struct AnimateKeys
|
||||
class D2DOverlayWindow : public D2DWindow
|
||||
{
|
||||
public:
|
||||
D2DOverlayWindow();
|
||||
D2DOverlayWindow(std::optional<std::function<std::remove_pointer_t<WNDPROC>>> pre_wnd_proc = std::nullopt);
|
||||
void show(HWND active_window, bool snappable);
|
||||
void animate(int vk_code);
|
||||
~D2DOverlayWindow();
|
||||
@ -54,6 +54,8 @@ public:
|
||||
void set_theme(const std::wstring& theme);
|
||||
void quick_hide();
|
||||
|
||||
HWND get_window_handle();
|
||||
|
||||
private:
|
||||
void animate(int vk_code, int offset);
|
||||
bool show_thumbnail(const RECT& rect, double alpha);
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
// TODO: refactor singleton
|
||||
OverlayWindow* instance = nullptr;
|
||||
|
||||
namespace
|
||||
@ -125,12 +126,36 @@ void OverlayWindow::set_config(const wchar_t* config)
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int alternative_switch_hotkey_id = 0x2;
|
||||
constexpr UINT alternative_switch_modifier_mask = MOD_WIN | MOD_SHIFT;
|
||||
constexpr UINT alternative_switch_vk_code = VK_OEM_2;
|
||||
|
||||
void OverlayWindow::enable()
|
||||
{
|
||||
auto switcher = [&](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
|
||||
if (msg == WM_KEYDOWN && wparam == VK_ESCAPE && instance->target_state->active())
|
||||
{
|
||||
instance->target_state->toggle_force_shown();
|
||||
return 0;
|
||||
}
|
||||
if (msg != WM_HOTKEY)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const auto vk_code = HIWORD(lparam);
|
||||
const auto modifiers_mask = LOWORD(lparam);
|
||||
if (alternative_switch_vk_code != vk_code || alternative_switch_modifier_mask != modifiers_mask)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
instance->target_state->toggle_force_shown();
|
||||
return 0;
|
||||
};
|
||||
|
||||
if (!_enabled)
|
||||
{
|
||||
Trace::EnableShortcutGuide(true);
|
||||
winkey_popup = std::make_unique<D2DOverlayWindow>();
|
||||
winkey_popup = std::make_unique<D2DOverlayWindow>(std::move(switcher));
|
||||
winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f);
|
||||
winkey_popup->set_theme(theme.value);
|
||||
target_state = std::make_unique<TargetState>(pressTime.value);
|
||||
@ -148,6 +173,7 @@ void OverlayWindow::enable()
|
||||
MessageBoxW(NULL, L"Cannot install keyboard listener.", L"PowerToys - Shortcut Guide", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
}
|
||||
RegisterHotKey(winkey_popup->get_window_handle(), alternative_switch_hotkey_id, alternative_switch_modifier_mask, alternative_switch_vk_code);
|
||||
}
|
||||
_enabled = true;
|
||||
}
|
||||
@ -161,6 +187,7 @@ void OverlayWindow::disable(bool trace_event)
|
||||
{
|
||||
Trace::EnableShortcutGuide(false);
|
||||
}
|
||||
UnregisterHotKey(winkey_popup->get_window_handle(), alternative_switch_hotkey_id);
|
||||
winkey_popup->hide();
|
||||
target_state->exit();
|
||||
target_state.reset();
|
||||
@ -241,6 +268,11 @@ void OverlayWindow::destroy()
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
bool OverlayWindow::overlay_visible() const
|
||||
{
|
||||
return target_state->active();
|
||||
}
|
||||
|
||||
void OverlayWindow::init_settings()
|
||||
{
|
||||
try
|
||||
|
@ -14,6 +14,7 @@ class OverlayWindow : public PowertoyModuleIface
|
||||
{
|
||||
public:
|
||||
OverlayWindow();
|
||||
|
||||
virtual const wchar_t* get_name() override;
|
||||
virtual const wchar_t** get_events() override;
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override;
|
||||
@ -39,6 +40,8 @@ public:
|
||||
|
||||
virtual void destroy() override;
|
||||
|
||||
bool overlay_visible() const;
|
||||
|
||||
private:
|
||||
std::wstring app_name;
|
||||
std::unique_ptr<TargetState> target_state;
|
||||
|
@ -5,36 +5,39 @@
|
||||
#include "common/shared_constants.h"
|
||||
|
||||
TargetState::TargetState(int ms_delay) :
|
||||
delay(std::chrono::milliseconds(ms_delay)), thread(&TargetState::thread_proc, this)
|
||||
// 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. 0x53 is the VK code of the S key
|
||||
if (key_down && state == Shown && vk_code == 0x53 && (GetKeyState(VK_LSHIFT) || GetKeyState(VK_RSHIFT)))
|
||||
// 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();
|
||||
}
|
||||
bool suppress = false;
|
||||
if (!key_down && (vk_code == VK_LWIN || vk_code == VK_RWIN) &&
|
||||
state == Shown &&
|
||||
std::chrono::system_clock::now() - signal_timestamp > std::chrono::milliseconds(300) &&
|
||||
!key_was_pressed)
|
||||
{
|
||||
suppress = true;
|
||||
}
|
||||
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)
|
||||
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
|
||||
@ -54,12 +57,17 @@ bool TargetState::signal_event(unsigned vk_code, bool key_down)
|
||||
input[2].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG;
|
||||
SendInput(3, input, sizeof(INPUT));
|
||||
}
|
||||
return suppress;
|
||||
return suppress_win_release;
|
||||
}
|
||||
|
||||
void TargetState::was_hidden()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<std::recursive_mutex> lock(mutex);
|
||||
// Ignore callbacks from the D2DOverlayWindow
|
||||
if (state == ForceShown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state = Hidden;
|
||||
events.clear();
|
||||
lock.unlock();
|
||||
@ -98,7 +106,7 @@ void TargetState::handle_hidden()
|
||||
}
|
||||
}
|
||||
|
||||
void TargetState::handle_shown()
|
||||
void TargetState::handle_shown(const bool forced)
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
if (events.empty())
|
||||
@ -110,19 +118,18 @@ void TargetState::handle_shown()
|
||||
return;
|
||||
}
|
||||
auto event = next();
|
||||
if (event.key_down && (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN))
|
||||
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 && (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN) || !winkey_held())
|
||||
{
|
||||
state = Hidden;
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key_down)
|
||||
{
|
||||
key_was_pressed = true;
|
||||
nonwin_key_was_pressed_during_shown = true;
|
||||
lock.unlock();
|
||||
instance->on_held_press(event.vk_code);
|
||||
}
|
||||
@ -141,7 +148,10 @@ void TargetState::thread_proc()
|
||||
handle_timeout();
|
||||
break;
|
||||
case Shown:
|
||||
handle_shown();
|
||||
handle_shown(false);
|
||||
break;
|
||||
case ForceShown:
|
||||
handle_shown(true);
|
||||
break;
|
||||
case Exiting:
|
||||
default:
|
||||
@ -155,9 +165,15 @@ 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();
|
||||
@ -166,15 +182,20 @@ void TargetState::handle_timeout()
|
||||
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();
|
||||
key_was_pressed = false;
|
||||
nonwin_key_was_pressed_during_shown = false;
|
||||
state = Shown;
|
||||
lock.unlock();
|
||||
instance->on_held();
|
||||
@ -182,5 +203,26 @@ void TargetState::handle_timeout()
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -21,24 +21,30 @@ public:
|
||||
void exit();
|
||||
void set_delay(int ms_delay);
|
||||
|
||||
void toggle_force_shown();
|
||||
bool active() const;
|
||||
|
||||
private:
|
||||
KeyEvent next();
|
||||
void handle_hidden();
|
||||
void handle_timeout();
|
||||
void handle_shown();
|
||||
void handle_shown(const bool forced);
|
||||
void thread_proc();
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::recursive_mutex mutex;
|
||||
std::condition_variable_any cv;
|
||||
std::chrono::system_clock::time_point winkey_timestamp, signal_timestamp;
|
||||
std::chrono::milliseconds delay;
|
||||
std::deque<KeyEvent> events;
|
||||
enum
|
||||
enum State
|
||||
{
|
||||
Hidden,
|
||||
Timeout,
|
||||
Shown,
|
||||
ForceShown,
|
||||
Exiting
|
||||
} state = Hidden;
|
||||
bool key_was_pressed = false;
|
||||
};
|
||||
std::atomic<State> state = Hidden;
|
||||
|
||||
bool nonwin_key_was_pressed_during_shown = false;
|
||||
std::thread thread;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user