2019-09-05 00:26:26 +08:00
|
|
|
#include "pch.h"
|
|
|
|
#include "shortcut_guide.h"
|
|
|
|
#include "target_state.h"
|
|
|
|
#include "trace.h"
|
2019-12-17 16:21:46 +08:00
|
|
|
|
|
|
|
#include <common/common.h>
|
2019-09-05 00:26:26 +08:00
|
|
|
#include <common/settings_objects.h>
|
2020-06-18 19:27:20 +08:00
|
|
|
#include <common/debug_control.h>
|
2019-09-05 00:26:26 +08:00
|
|
|
|
|
|
|
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
|
|
|
|
2020-07-01 17:37:50 +08:00
|
|
|
// TODO: refactor singleton
|
2019-09-05 00:26:26 +08:00
|
|
|
OverlayWindow* instance = nullptr;
|
|
|
|
|
2020-04-30 17:05:18 +08:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
LowlevelKeyboardEvent event;
|
|
|
|
if (nCode == HC_ACTION)
|
|
|
|
{
|
|
|
|
event.lParam = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
|
|
|
|
event.wParam = wParam;
|
|
|
|
if (instance->signal_event(&event) != 0)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
|
|
|
}
|
2020-08-25 01:38:15 +08:00
|
|
|
|
|
|
|
// Window properties relevant to ShortcutGuide
|
|
|
|
struct ShortcutGuideWindowInfo
|
|
|
|
{
|
|
|
|
HWND hwnd = nullptr; // Handle to the top-level foreground window or nullptr if there is no such window
|
|
|
|
bool snappable = false; // True, if the window can react to Windows Snap keys
|
|
|
|
};
|
|
|
|
|
|
|
|
ShortcutGuideWindowInfo GetShortcutGuideWindowInfo()
|
|
|
|
{
|
|
|
|
ShortcutGuideWindowInfo result;
|
|
|
|
auto active_window = GetForegroundWindow();
|
|
|
|
active_window = GetAncestor(active_window, GA_ROOT);
|
|
|
|
if (!IsWindowVisible(active_window))
|
|
|
|
{
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
auto style = GetWindowLong(active_window, GWL_STYLE);
|
|
|
|
auto exStyle = GetWindowLong(active_window, GWL_EXSTYLE);
|
|
|
|
if ((style & WS_CHILD) == WS_CHILD ||
|
|
|
|
(style & WS_DISABLED) == WS_DISABLED ||
|
|
|
|
(exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW ||
|
|
|
|
(exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE)
|
|
|
|
{
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
std::array<char, 256> class_name;
|
|
|
|
GetClassNameA(active_window, class_name.data(), static_cast<int>(class_name.size()));
|
|
|
|
if (is_system_window(active_window, class_name.data()))
|
|
|
|
{
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
static HWND cortana_hwnd = nullptr;
|
|
|
|
if (cortana_hwnd == nullptr)
|
|
|
|
{
|
|
|
|
if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
|
|
|
|
get_process_path(active_window).ends_with(L"SearchUI.exe"))
|
|
|
|
{
|
|
|
|
cortana_hwnd = active_window;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (cortana_hwnd == active_window)
|
|
|
|
{
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
result.hwnd = active_window;
|
|
|
|
// In reality, Windows Snap works if even one of those styles is set
|
|
|
|
// for a window, it is just limited. If there is no WS_MAXIMIZEBOX using
|
|
|
|
// WinKey + Up just won't maximize the window. Similary, without
|
|
|
|
// WS_MINIMIZEBOX the window will not get minimized. A "Save As..." dialog
|
|
|
|
// is a example of such window - it can be snapped to both sides and to
|
|
|
|
// all screen corners, but will not get maximized nor minimized.
|
|
|
|
// For now, since ShortcutGuide can only disable entire "Windows Controls"
|
|
|
|
// group, we require that the window supports all the options.
|
|
|
|
result.snappable = ((style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) &&
|
|
|
|
((style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX) &&
|
|
|
|
((style & WS_THICKFRAME) == WS_THICKFRAME);
|
|
|
|
return result;
|
|
|
|
}
|
2020-04-30 17:05:18 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
OverlayWindow::OverlayWindow()
|
|
|
|
{
|
2020-01-23 01:43:49 +08:00
|
|
|
app_name = GET_RESOURCE_STRING(IDS_SHORTCUT_GUIDE);
|
2019-12-06 19:07:54 +08:00
|
|
|
init_settings();
|
|
|
|
}
|
|
|
|
|
|
|
|
const wchar_t* OverlayWindow::get_name()
|
|
|
|
{
|
2020-01-23 01:43:49 +08:00
|
|
|
return app_name.c_str();
|
2019-12-06 19:07:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool OverlayWindow::get_config(wchar_t* buffer, int* buffer_size)
|
|
|
|
{
|
|
|
|
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
|
|
|
|
|
|
|
PowerToysSettings::Settings settings(hinstance, get_name());
|
2020-01-23 01:43:49 +08:00
|
|
|
settings.set_description(GET_RESOURCE_STRING(IDS_SETTINGS_DESCRIPTION));
|
2020-06-12 01:16:39 +08:00
|
|
|
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_ShortcutGuide");
|
2019-12-06 19:07:54 +08:00
|
|
|
settings.set_icon_key(L"pt-shortcut-guide");
|
|
|
|
|
|
|
|
settings.add_int_spinner(
|
|
|
|
pressTime.name,
|
|
|
|
pressTime.resourceId,
|
|
|
|
pressTime.value,
|
|
|
|
100,
|
|
|
|
10000,
|
|
|
|
100);
|
|
|
|
|
|
|
|
settings.add_int_spinner(
|
|
|
|
overlayOpacity.name,
|
|
|
|
overlayOpacity.resourceId,
|
|
|
|
overlayOpacity.value,
|
|
|
|
0,
|
|
|
|
100,
|
|
|
|
1);
|
|
|
|
|
|
|
|
settings.add_choice_group(
|
|
|
|
theme.name,
|
|
|
|
theme.resourceId,
|
|
|
|
theme.value,
|
|
|
|
theme.keys_and_texts);
|
|
|
|
|
|
|
|
return settings.serialize_to_buffer(buffer, buffer_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OverlayWindow::set_config(const wchar_t* config)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2020-02-11 14:57:11 +08:00
|
|
|
// save configuration
|
2019-12-06 19:07:54 +08:00
|
|
|
PowerToysSettings::PowerToyValues _values =
|
|
|
|
PowerToysSettings::PowerToyValues::from_json_string(config);
|
2020-02-11 14:57:11 +08:00
|
|
|
_values.save_to_settings_file();
|
|
|
|
Trace::SettingsChanged(pressTime.value, overlayOpacity.value, theme.value);
|
|
|
|
|
|
|
|
// apply new settings if powertoy is enabled
|
|
|
|
if (_enabled)
|
2019-12-06 19:07:54 +08:00
|
|
|
{
|
2020-02-11 14:57:11 +08:00
|
|
|
if (const auto press_delay_time = _values.get_int_value(pressTime.name))
|
2019-12-06 19:07:54 +08:00
|
|
|
{
|
2020-02-11 14:57:11 +08:00
|
|
|
pressTime.value = *press_delay_time;
|
|
|
|
if (target_state)
|
|
|
|
{
|
|
|
|
target_state->set_delay(*press_delay_time);
|
|
|
|
}
|
2019-12-06 19:07:54 +08:00
|
|
|
}
|
2020-02-11 14:57:11 +08:00
|
|
|
if (const auto overlay_opacity = _values.get_int_value(overlayOpacity.name))
|
2019-12-06 19:07:54 +08:00
|
|
|
{
|
2020-02-11 14:57:11 +08:00
|
|
|
overlayOpacity.value = *overlay_opacity;
|
|
|
|
if (winkey_popup)
|
|
|
|
{
|
|
|
|
winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (auto val = _values.get_string_value(theme.name))
|
|
|
|
{
|
|
|
|
theme.value = std::move(*val);
|
|
|
|
if (winkey_popup)
|
|
|
|
{
|
|
|
|
winkey_popup->set_theme(theme.value);
|
|
|
|
}
|
2019-12-06 19:07:54 +08:00
|
|
|
}
|
|
|
|
}
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
2019-12-06 19:07:54 +08:00
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
// Improper JSON. TODO: handle the error.
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
2019-12-06 19:07:54 +08:00
|
|
|
}
|
|
|
|
|
2020-07-01 17:37:50 +08:00
|
|
|
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;
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::enable()
|
|
|
|
{
|
2020-07-01 17:37:50 +08:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
if (!_enabled)
|
|
|
|
{
|
|
|
|
Trace::EnableShortcutGuide(true);
|
2020-07-01 17:37:50 +08:00
|
|
|
winkey_popup = std::make_unique<D2DOverlayWindow>(std::move(switcher));
|
2019-12-06 19:07:54 +08:00
|
|
|
winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f);
|
|
|
|
winkey_popup->set_theme(theme.value);
|
|
|
|
target_state = std::make_unique<TargetState>(pressTime.value);
|
|
|
|
winkey_popup->initialize();
|
2020-06-19 18:27:29 +08:00
|
|
|
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
|
2020-06-18 19:27:20 +08:00
|
|
|
const bool hook_disabled = IsDebuggerPresent();
|
|
|
|
#else
|
|
|
|
const bool hook_disabled = false;
|
|
|
|
#endif
|
|
|
|
if (!hook_disabled)
|
2020-04-30 17:05:18 +08:00
|
|
|
{
|
2020-06-18 19:27:20 +08:00
|
|
|
hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL);
|
|
|
|
if (!hook_handle)
|
|
|
|
{
|
|
|
|
MessageBoxW(NULL, L"Cannot install keyboard listener.", L"PowerToys - Shortcut Guide", MB_OK | MB_ICONERROR);
|
|
|
|
}
|
2020-04-30 17:05:18 +08:00
|
|
|
}
|
2020-07-01 17:37:50 +08:00
|
|
|
RegisterHotKey(winkey_popup->get_window_handle(), alternative_switch_hotkey_id, alternative_switch_modifier_mask, alternative_switch_vk_code);
|
2019-10-02 23:07:12 +08:00
|
|
|
}
|
2019-12-06 19:07:54 +08:00
|
|
|
_enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OverlayWindow::disable(bool trace_event)
|
|
|
|
{
|
|
|
|
if (_enabled)
|
|
|
|
{
|
|
|
|
_enabled = false;
|
|
|
|
if (trace_event)
|
|
|
|
{
|
|
|
|
Trace::EnableShortcutGuide(false);
|
|
|
|
}
|
2020-07-01 17:37:50 +08:00
|
|
|
UnregisterHotKey(winkey_popup->get_window_handle(), alternative_switch_hotkey_id);
|
2019-12-06 19:07:54 +08:00
|
|
|
winkey_popup->hide();
|
|
|
|
target_state->exit();
|
|
|
|
target_state.reset();
|
|
|
|
winkey_popup.reset();
|
2020-04-30 17:05:18 +08:00
|
|
|
if (hook_handle)
|
|
|
|
{
|
|
|
|
bool success = UnhookWindowsHookEx(hook_handle);
|
|
|
|
if (success)
|
|
|
|
{
|
|
|
|
hook_handle = nullptr;
|
|
|
|
}
|
|
|
|
}
|
2019-10-22 14:11:23 +08:00
|
|
|
}
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::disable()
|
|
|
|
{
|
|
|
|
this->disable(true);
|
2019-10-22 14:11:23 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
bool OverlayWindow::is_enabled()
|
|
|
|
{
|
|
|
|
return _enabled;
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2020-04-30 17:05:18 +08:00
|
|
|
intptr_t OverlayWindow::signal_event(LowlevelKeyboardEvent* event)
|
|
|
|
{
|
|
|
|
if (!_enabled)
|
2019-12-06 19:07:54 +08:00
|
|
|
{
|
2020-04-30 17:05:18 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event->wParam == WM_KEYDOWN ||
|
|
|
|
event->wParam == WM_SYSKEYDOWN ||
|
|
|
|
event->wParam == WM_KEYUP ||
|
|
|
|
event->wParam == WM_SYSKEYUP)
|
|
|
|
{
|
|
|
|
bool suppress = target_state->signal_event(event->lParam->vkCode,
|
2020-06-18 19:27:20 +08:00
|
|
|
event->wParam == WM_KEYDOWN || event->wParam == WM_SYSKEYDOWN);
|
2020-04-30 17:05:18 +08:00
|
|
|
return suppress ? 1 : 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::on_held()
|
|
|
|
{
|
2020-08-25 01:38:15 +08:00
|
|
|
auto windowInfo = GetShortcutGuideWindowInfo();
|
|
|
|
winkey_popup->show(windowInfo.hwnd, windowInfo.snappable);
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::on_held_press(DWORD vkCode)
|
|
|
|
{
|
|
|
|
winkey_popup->animate(vkCode);
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::quick_hide()
|
|
|
|
{
|
|
|
|
winkey_popup->quick_hide();
|
2019-10-31 17:26:24 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::was_hidden()
|
|
|
|
{
|
2020-05-28 01:57:19 +08:00
|
|
|
target_state->was_hidden();
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::destroy()
|
|
|
|
{
|
|
|
|
this->disable(false);
|
|
|
|
delete this;
|
|
|
|
instance = nullptr;
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2020-07-01 17:37:50 +08:00
|
|
|
bool OverlayWindow::overlay_visible() const
|
|
|
|
{
|
|
|
|
return target_state->active();
|
|
|
|
}
|
|
|
|
|
2019-12-06 19:07:54 +08:00
|
|
|
void OverlayWindow::init_settings()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
PowerToysSettings::PowerToyValues settings =
|
|
|
|
PowerToysSettings::PowerToyValues::load_from_settings_file(OverlayWindow::get_name());
|
|
|
|
if (const auto val = settings.get_int_value(pressTime.name))
|
|
|
|
{
|
|
|
|
pressTime.value = *val;
|
|
|
|
}
|
|
|
|
if (const auto val = settings.get_int_value(overlayOpacity.name))
|
|
|
|
{
|
|
|
|
overlayOpacity.value = *val;
|
|
|
|
}
|
|
|
|
if (auto val = settings.get_string_value(theme.name))
|
|
|
|
{
|
|
|
|
theme.value = std::move(*val);
|
|
|
|
}
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
2019-12-06 19:07:54 +08:00
|
|
|
catch (std::exception&)
|
|
|
|
{
|
|
|
|
// Error while loading from the settings file. Just let default values stay as they are.
|
2019-10-02 23:07:12 +08:00
|
|
|
}
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|