FancyZones and Shortcut Guide initial commit

Co-authored-by: Alexis Campailla <alexis@janeasystems.com>
Co-authored-by: Bret Anderson <bretan@microsoft.com>
Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Jeff Bogdan <jeffbog@microsoft.com>
Co-authored-by: March Rogers <marchr@microsoft.com>
Co-authored-by: Mike Harsh <mharsh@microsoft.com>
Co-authored-by: Nachum Bundak <Nachum.Bundak@microsoft.com>
Co-authored-by: Oliver Jones <ojones@microsoft.com>
Co-authored-by: Patrick Little <plittle@microsoft.com>
This commit is contained in:
Bartosz Sosnowski
2019-09-04 18:26:26 +02:00
committed by Bartosz Sosnowski
parent 10c5396099
commit 8431b80e48
341 changed files with 54766 additions and 62 deletions

View File

@@ -0,0 +1,46 @@
# Windows Key Shortcut Guide
# Introduction
The Windows Key Shortcut Guide shows common keyboard shortcuts that use the Windows key.
# Usage
Press and hold the keyboard Windows key for about 1 second, an overlay appears showing keyboard shortcuts that use the Windows Key:
- Shortcuts for changing the position of the active window.
- Common Windows shortcuts.
- Taskbar shortcuts.
Releasing the Windows key will make the overlay disappear. If the shortcut guide was visible for less than a second, the start menu will appear after the shortcut guide is dismissed.
![Image of the Overlay](/doc/images/shortcut_guide/usage.png)
Windows key keyboard shortcuts can be used while the guide is being shown and the result of those shortcuts (active window moved, arrow shortcut behavior changes, etc) will be displayed in the guide.
# Options
These configurations can be edited from the PowerToys Settings screen:
- "How long to press the Windows key before showing the Shortcut Guide (ms)" - How many milliseconds to press the Windows key before the Shortcut Guide is shown.
- "Opacity of the Shortcut Guide's overlay background (%)" - Changing this setting controls the opacity of the Shortcut Guide's overlay background, occluding the work environment beneath the Shortcut Guide to different degrees.
![Image of the Options](/doc/images/shortcut_guide/settings.png)
# Backlog
The backlog for the utility can be found [here](https://github.com/Microsoft/PowerToys/tree/master/doc/planning/ShortcutGuideBacklog.md) and the source code is [here](https://github.com/Microsoft/PowerToys/tree/master/src/modules/shortcut_guide).
# Code organization
#### [`dllmain.cpp`](./dllmain.cpp)
Contains DLL boilerplate code.
#### [`shortcut_guide.cpp`](./shortcut_guide.cpp)
Contains the module interface code. It initializes the settings values and the keyboard event listener.
#### [`overlay_window.cpp`](./overlay_window.cpp)
Contains the code for loading the SVGs, creating and rendering of the overlay window.
#### [`keyboard_state.cpp`](./keyboard_state.cpp)
Contains helper methods for checking the current state of the keyboard.
#### [`target_state.cpp`](./target_state.cpp)
State machine that handles the keyboard events. Its responsible for deciding when to show the overlay, when to suppress the Start menu (if the overlay is displayed long enough), etc.
#### [`trace.cpp`](./trace.cpp)
Contains code for telemetry.

View File

@@ -0,0 +1,30 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <mutex>
#include "shortcut_guide.h"
#include "overlay_window.h"
#include "trace.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() {
if (!instance) {
instance = new OverlayWindow();
return instance;
} else {
return nullptr;
}
}

View File

@@ -0,0 +1,22 @@
#include "pch.h"
#include "keyboard_state.h"
bool winkey_held() {
auto left = GetAsyncKeyState(VK_LWIN);
auto right = GetAsyncKeyState(VK_RWIN);
return (left & 0x8000) || (right & 0x8000);
}
bool only_winkey_key_held() {
/* There are situations, when some of the keys are not registered correctly by
GetKeyboardState. The M key can get stuck as "pressed" after Win+M, and
Shift etc. keys are not always reported as expected.
*/
for (int vk = 0; vk <= VK_OEM_CLEAR; ++vk) {
if (vk == VK_LWIN || vk == VK_RWIN)
continue;
if (GetAsyncKeyState(vk) & 0x8000)
return false;
}
return true;
}

View File

@@ -0,0 +1,3 @@
#pragma once
bool winkey_held();
bool only_winkey_key_held();

View File

@@ -0,0 +1,717 @@
#include "pch.h"
#include "overlay_window.h"
#include "common/monitors.h"
#include "common/tasklist_positions.h"
#include "common/start_visible.h"
#include "keyboard_state.h"
#include "shortcut_guide.h"
#include "trace.h"
D2DOverlaySVG& D2DOverlaySVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) {
D2DSVG::load(filename, d2d_dc);
window_group = nullptr;
thumbnail_top_left = {};
thumbnail_bottom_right = {};
thumbnail_scaled_rect = {};
return *this;
}
D2DOverlaySVG& D2DOverlaySVG::resize(int x, int y, int width, int height, float fill, float max_scale) {
D2DSVG::resize(x, y, width, height, fill, max_scale);
if (thumbnail_bottom_right.x != 0 && thumbnail_bottom_right.y != 0) {
auto scaled_top_left = transform.TransformPoint(thumbnail_top_left);
auto scanled_bottom_right = transform.TransformPoint(thumbnail_bottom_right);
thumbnail_scaled_rect.left = (int)scaled_top_left.x;
thumbnail_scaled_rect.top = (int)scaled_top_left.y;
thumbnail_scaled_rect.right = (int)scanled_bottom_right.x;
thumbnail_scaled_rect.bottom = (int)scanled_bottom_right.y;
}
return *this;
}
D2DOverlaySVG& D2DOverlaySVG::find_thumbnail(const std::wstring& id) {
winrt::com_ptr<ID2D1SvgElement> thumbnail_box;
winrt::check_hresult(svg->FindElementById(id.c_str(), thumbnail_box.put()));
winrt::check_hresult(thumbnail_box->GetAttributeValue(L"x", &thumbnail_top_left.x));
winrt::check_hresult(thumbnail_box->GetAttributeValue(L"y", &thumbnail_top_left.y));
winrt::check_hresult(thumbnail_box->GetAttributeValue(L"width", &thumbnail_bottom_right.x));
thumbnail_bottom_right.x += thumbnail_top_left.x;
winrt::check_hresult(thumbnail_box->GetAttributeValue(L"height", &thumbnail_bottom_right.y));
thumbnail_bottom_right.y += thumbnail_top_left.y;
return *this;
}
D2DOverlaySVG& D2DOverlaySVG::find_window_group(const std::wstring& id) {
window_group = nullptr;
winrt::check_hresult(svg->FindElementById(id.c_str(), window_group.put()));
return *this;
}
ScaleResult D2DOverlaySVG::get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill) {
if (thumbnail_bottom_right.x == 0 && thumbnail_bottom_right.y == 0) {
return {};
}
int thumbnail_scaled_rect_width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left;
int thumbnail_scaled_rect_heigh = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top;
if (thumbnail_scaled_rect_heigh == 0 || thumbnail_scaled_rect_width == 0 ||
window_cx == 0 || window_cy == 0) {
return {};
}
float scale_h = fill * thumbnail_scaled_rect_width / window_cx;
float scale_v = fill * thumbnail_scaled_rect_heigh / window_cy;
float use_scale = min(scale_h, scale_v);
RECT thumb_rect;
thumb_rect.left = thumbnail_scaled_rect.left + (int)(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset;
thumb_rect.right = thumbnail_scaled_rect.right - (int)(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset;
thumb_rect.top = thumbnail_scaled_rect.top + (int)(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset;
thumb_rect.bottom = thumbnail_scaled_rect.bottom - (int)(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset;
ScaleResult result;
result.scale = use_scale;
result.rect = thumb_rect;
return result;
}
winrt::com_ptr<ID2D1SvgElement> D2DOverlaySVG::find_element(const std::wstring& id) {
winrt::com_ptr< ID2D1SvgElement> element;
winrt::check_hresult(svg->FindElementById(id.c_str(), element.put()));
return element;
}
D2DOverlaySVG& D2DOverlaySVG::toggle_window_group(bool active) {
if (window_group) {
window_group->SetAttributeValue(L"fill-opacity", active ? 1.0f : 0.3f);
}
return *this;
}
D2D1_RECT_F D2DOverlaySVG::get_maximize_label() const {
D2D1_RECT_F result;
auto height = (float)(thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top);
auto width = (float)(thumbnail_scaled_rect.right - thumbnail_scaled_rect.left);
if (width >= height) {
result.top = thumbnail_scaled_rect.bottom + height * 0.210f;
result.bottom = thumbnail_scaled_rect.bottom + height * 0.310f;
result.left = thumbnail_scaled_rect.left + width * 0.009f;
result.right = thumbnail_scaled_rect.right + width * 0.009f;
} else {
result.top = thumbnail_scaled_rect.top + height * 0.323f;
result.bottom = thumbnail_scaled_rect.top + height * 0.398f;
result.left = (float)thumbnail_scaled_rect.right;
result.right = thumbnail_scaled_rect.right + width * 1.45f;
}
return result;
}
D2D1_RECT_F D2DOverlaySVG::get_minimize_label() const {
D2D1_RECT_F result;
auto height = (float)(thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top);
auto width = (float)(thumbnail_scaled_rect.right - thumbnail_scaled_rect.left);
if (width >= height) {
result.top = thumbnail_scaled_rect.bottom + height * 0.8f;
result.bottom = thumbnail_scaled_rect.bottom + height * 0.9f;
result.left = thumbnail_scaled_rect.left + width * 0.009f;
result.right = thumbnail_scaled_rect.right + width * 0.009f;
} else {
result.top = thumbnail_scaled_rect.top + height * 0.725f;
result.bottom = thumbnail_scaled_rect.top + height * 0.800f;
result.left = (float)thumbnail_scaled_rect.right;
result.right = thumbnail_scaled_rect.right + width * 1.45f;
}
return result;
}
D2D1_RECT_F D2DOverlaySVG::get_snap_left() const {
D2D1_RECT_F result;
auto height = (float)(thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top);
auto width = (float)(thumbnail_scaled_rect.right - thumbnail_scaled_rect.left);
if (width >= height) {
result.top = thumbnail_scaled_rect.bottom + height * 0.5f;
result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f;
result.left = thumbnail_scaled_rect.left + width * 0.009f;
result.right = thumbnail_scaled_rect.left + width * 0.339f;
} else {
result.top = thumbnail_scaled_rect.top + height * 0.523f;
result.bottom = thumbnail_scaled_rect.top + height * 0.598f;
result.left = (float)thumbnail_scaled_rect.right;
result.right = thumbnail_scaled_rect.right + width * 0.450f;
}
return result;
}
D2D1_RECT_F D2DOverlaySVG::get_snap_right() const {
D2D1_RECT_F result;
auto height = (float)(thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top);
auto width = (float)(thumbnail_scaled_rect.right - thumbnail_scaled_rect.left);
if (width >= height) {
result.top = thumbnail_scaled_rect.bottom + height * 0.5f;
result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f;
result.left = thumbnail_scaled_rect.left + width * 0.679f;
result.right = thumbnail_scaled_rect.right + width * 1.009f;
} else {
result.top = thumbnail_scaled_rect.top + height * 0.523f;
result.bottom = thumbnail_scaled_rect.top + height * 0.598f;
result.left = (float)thumbnail_scaled_rect.right + width;
result.right = thumbnail_scaled_rect.right + width * 1.45f;
}
return result;
}
D2DOverlayWindow::D2DOverlayWindow() : animation(0.3), total_screen({}) {
tasklist_thread = std::thread([&] {
while (running) {
// Removing <std::mutex> causes C3538 on std::unique_lock lock(mutex); in show(..)
std::unique_lock<std::mutex> lock(tasklist_cv_mutex);
tasklist_cv.wait(lock, [&] { return !running || tasklist_update; });
if (!running)
return;
lock.unlock();
while (running && tasklist_update) {
std::vector<TasklistButton> buttons;
if (tasklist.update_buttons(buttons)) {
std::unique_lock lock(mutex);
tasklist_buttons.swap(buttons);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
});
}
void D2DOverlayWindow::show(HWND active_window) {
std::unique_lock lock(mutex);
tasklist_buttons.clear();
this->active_window = active_window;
auto old_bck = colors.start_color_menu;
if (initialized && colors.update()) {
// update background colors
landscape.recolor(old_bck, colors.start_color_menu);
portrait.recolor(old_bck, colors.start_color_menu);
for (auto& arrow : arrows) {
arrow.recolor(old_bck, colors.start_color_menu);
}
if (colors.light_mode) {
landscape.recolor(0xDDDDDD, 0x222222);
portrait.recolor(0xDDDDDD, 0x222222);
for (auto& arrow : arrows) {
arrow.recolor(0xDDDDDD, 0x222222);
}
} else {
landscape.recolor(0x222222, 0xDDDDDD);
portrait.recolor(0x222222, 0xDDDDDD);
for (auto& arrow : arrows) {
arrow.recolor(0x222222, 0xDDDDDD);
}
}
}
monitors = MonitorInfo::GetMonitors(true);
// calculate the rect covering all the screens
total_screen = ScreenSize(monitors[0].rect);
for (auto& monitor : monitors) {
total_screen.rect.left = min(total_screen.rect.left, monitor.rect.left);
total_screen.rect.top = min(total_screen.rect.top, monitor.rect.top);
total_screen.rect.right = max(total_screen.rect.right, monitor.rect.right);
total_screen.rect.bottom = max(total_screen.rect.bottom, monitor.rect.bottom);
}
// make sure top-right corner of all the monitor rects is (0,0)
monitor_dx = -total_screen.left();
monitor_dy = -total_screen.top();
total_screen.rect.left += monitor_dx;
total_screen.rect.right += monitor_dx;
total_screen.rect.top += monitor_dy;
total_screen.rect.bottom += monitor_dy;
tasklist.update();
if (active_window) {
// Ignore errors, if this fails we will just not show the thumbnail
DwmRegisterThumbnail(hwnd, active_window, &thumbnail);
}
animation.reset();
auto primary_screen = MonitorInfo::GetPrimaryMonitor();
shown_start_time = std::chrono::steady_clock::now();
lock.unlock();
D2DWindow::show(primary_screen.left(), primary_screen.top(), primary_screen.width(), primary_screen.height());
key_pressed.clear();
tasklist_cv_mutex.lock();
tasklist_update = true;
tasklist_cv_mutex.unlock();
tasklist_cv.notify_one();
Trace::EventShow();
}
void D2DOverlayWindow::animate(int vk_code) {
animate(vk_code, 0);
}
void D2DOverlayWindow::animate(int vk_code, int offset) {
if (!initialized || !use_overlay) {
return;
}
bool done = false;
for (auto& animation : key_animations) {
if (animation.vk_code == vk_code) {
animation.animation.reset(0.1, 0, 1);
done = true;
}
}
if (done) {
return;
}
AnimateKeys animation;
std::wstring id;
animation.vk_code = vk_code;
winrt::com_ptr<ID2D1SvgElement> button_letter, parrent;
if (vk_code >= 0x41 && vk_code <= 0x5A) {
id.push_back('A' + (vk_code - 0x41));
} else {
switch (vk_code) {
case VK_SNAPSHOT:
case VK_PRINT:
id = L"PrnScr";
break;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
id = L"Ctrl";
break;
case VK_UP:
id = L"KeyUp";
break;
case VK_LEFT:
id = L"KeyLeft";
break;
case VK_DOWN:
id = L"KeyDown";
break;
case VK_RIGHT:
id = L"KeyRight";
break;
case VK_OEM_PLUS:
case VK_ADD:
id = L"KeyPlus";
break;
case VK_OEM_MINUS:
case VK_SUBTRACT:
id = L"KeyMinus";
break;
case VK_TAB:
id = L"Tab";
break;
case VK_RETURN:
id = L"Enter";
break;
default:
return;
}
}
if (offset > 0) {
id += L"_" + std::to_wstring(offset);
}
button_letter = use_overlay->find_element(id);
if (!button_letter) {
return;
}
button_letter->GetParent(parrent.put());
if (!parrent) {
return;
}
parrent->GetPreviousChild(button_letter.get(), animation.button.put());
if (!animation.button || !animation.button->IsAttributeSpecified(L"fill")) {
animation.button = nullptr;
parrent->GetNextChild(button_letter.get(), animation.button.put());
}
if (!animation.button || !animation.button->IsAttributeSpecified(L"fill")) {
return;
}
winrt::com_ptr<ID2D1SvgPaint> paint;
animation.button->GetAttributeValue(L"fill", paint.put());
paint->GetColor(&animation.original);
animate(vk_code, offset + 1);
std::unique_lock lock(mutex);
animation.animation.reset(0.1, 0, 1);
key_animations.push_back(animation);
key_pressed.push_back(vk_code);
}
void D2DOverlayWindow::on_show() {
// show override does everything
}
void D2DOverlayWindow::on_hide() {
tasklist_cv_mutex.lock();
tasklist_update = false;
tasklist_cv_mutex.unlock();
tasklist_cv.notify_one();
if (thumbnail) {
DwmUnregisterThumbnail(thumbnail);
}
std::chrono::steady_clock::time_point shown_end_time = std::chrono::steady_clock::now();
Trace::EventHide(std::chrono::duration_cast<std::chrono::milliseconds>(shown_end_time - shown_start_time).count(), key_pressed);
key_pressed.clear();
}
D2DOverlayWindow::~D2DOverlayWindow() {
tasklist_cv_mutex.lock();
running = false;
tasklist_cv_mutex.unlock();
tasklist_cv.notify_one();
tasklist_thread.join();
}
void D2DOverlayWindow::apply_overlay_opacity(float opacity) {
if (opacity <= 0.0f) {
opacity = 0.0f;
}
if (opacity >= 1.0f) {
opacity = 1.0f;
}
overlay_opacity = opacity;
}
float D2DOverlayWindow::get_overlay_opacity() {
return overlay_opacity;
}
void D2DOverlayWindow::init() {
colors.update();
landscape.load(L"svgs\\overlay.svg", d2d_dc.get())
.find_thumbnail(L"path-1")
.find_window_group(L"Group-1")
.recolor(0x000000, colors.start_color_menu);
portrait.load(L"svgs\\overlay_portrait.svg", d2d_dc.get())
.find_thumbnail(L"path-1")
.find_window_group(L"Group-1")
.recolor(0x000000, colors.start_color_menu);
no_active.load(L"svgs\\no_active_window.svg", d2d_dc.get());
arrows.resize(10);
for (unsigned i = 0; i < arrows.size(); ++i) {
arrows[i].load(L"svgs\\" + std::to_wstring((i + 1) % 10) + L".svg", d2d_dc.get())
.recolor(0x000000, colors.start_color_menu);
}
if (!colors.light_mode) {
landscape.recolor(0x222222, 0xDDDDDD);
portrait.recolor(0x222222, 0xDDDDDD);
for (auto& arrow : arrows) {
arrow.recolor(0x222222, 0xDDDDDD);
}
}
}
void D2DOverlayWindow::resize() {
window_rect = *get_window_pos(hwnd);
float no_active_scale, font;
if (window_width >= window_height) { // portriat is broke right now
use_overlay = &landscape;
no_active_scale = 0.3f;
font = 15.0f;
} else {
use_overlay = &portrait;
no_active_scale = 0.5f;
font = 16.0f;
}
use_overlay->resize(0, 0, window_width, window_height, 0.8f);
auto thumb_no_active_rect = use_overlay->get_thumbnail_rect_and_scale(0, 0, no_active.width(), no_active.height(), no_active_scale).rect;
no_active.resize(thumb_no_active_rect.left,
thumb_no_active_rect.top,
thumb_no_active_rect.right - thumb_no_active_rect.left,
thumb_no_active_rect.bottom - thumb_no_active_rect.top,
1.0f);
text.resize(font, use_overlay->get_scale());
}
void render_arrow(D2DSVG& arrow, TasklistButton& button, RECT window, float max_scale, ID2D1DeviceContext5* d2d_dc) {
int dx = 0, dy = 0;
// Calculate taskbar orientation
arrow.toggle_element(L"left", false);
arrow.toggle_element(L"right", false);
arrow.toggle_element(L"top", false);
arrow.toggle_element(L"bottom", false);
if (button.x <= window.left) { // taskbar on left
dx = 1;
arrow.toggle_element(L"left", true);
}
if (button.x >= window.right) { // taskbar on right
dx = -1;
arrow.toggle_element(L"right", true);
}
if (button.y <= window.top) { // taskbar on top
dy = 1;
arrow.toggle_element(L"top", true);
}
if (button.y >= window.bottom) { // taskbar on bottom
dy = -1;
arrow.toggle_element(L"bottom", true);
}
double arrow_ratio = (double)arrow.height() / arrow.width();
if (dy != 0) {
// assume button is 25% wider than taller, +10% to make room for each of the arrows that are hidden
auto render_arrow_width = (int)(button.height * 1.25f * 1.2f);
auto render_arrow_height = (int)(render_arrow_width * arrow_ratio);
auto y_edge = dy == -1 ? button.y : button.y + button.height;
arrow.resize(button.x + (button.width - render_arrow_width) / 2,
dy == -1 ? button.y - render_arrow_height : 0,
render_arrow_width, render_arrow_height, 0.95f, max_scale)
.render(d2d_dc);
}
else {
// same as above - make room for the hidden arrow
auto render_arrow_height = (int)(button.height * 1.2f);
auto render_arrow_width = (int)(render_arrow_height / arrow_ratio);
arrow.resize(dx == -1 ? button.x - render_arrow_width : 0,
button.y + (button.height - render_arrow_height) / 2,
render_arrow_width, render_arrow_height, 0.95f, max_scale)
.render(d2d_dc);
}
}
bool D2DOverlayWindow::show_thumbnail(const RECT& rect, double alpha) {
if (!thumbnail) {
return false;
}
DWM_THUMBNAIL_PROPERTIES thumb_properties;
thumb_properties.dwFlags = DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_OPACITY;
thumb_properties.fSourceClientAreaOnly = FALSE;
thumb_properties.fVisible = TRUE;
thumb_properties.opacity = (BYTE)(255*alpha);
thumb_properties.rcDestination = rect;
if (DwmUpdateThumbnailProperties(thumbnail, &thumb_properties) != S_OK) {
return false;
}
return true;
}
void D2DOverlayWindow::hide_thumbnail() {
DWM_THUMBNAIL_PROPERTIES thumb_properties;
thumb_properties.dwFlags = DWM_TNP_VISIBLE;
thumb_properties.fVisible = FALSE;
DwmUpdateThumbnailProperties(thumbnail, &thumb_properties);
}
void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_dc) {
if (!winkey_held() || is_start_visible()) {
hide();
instance->was_hidden();
return;
}
d2d_dc->Clear();
int x_offset = 0, y_offset = 0, dimention = 0;
auto current_anim_value = (float)animation.value(Animation::AnimFunctions::LINEAR);
SetLayeredWindowAttributes(hwnd, 0, (int)(255*current_anim_value), LWA_ALPHA);
double pos_anim_value = 1 - animation.value(Animation::AnimFunctions::EASE_OUT_EXPO);
if (!tasklist_buttons.empty()) {
if (tasklist_buttons[0].x <= window_rect.left) { // taskbar on left
x_offset = (int)(-pos_anim_value * use_overlay->width() * use_overlay->get_scale());
}
if (tasklist_buttons[0].x >= window_rect.right) { // taskbar on right
x_offset = (int)(pos_anim_value * use_overlay->width() * use_overlay->get_scale());
}
if (tasklist_buttons[0].y <= window_rect.top) { // taskbar on top
y_offset = (int)(-pos_anim_value * use_overlay->height() * use_overlay->get_scale());
}
if (tasklist_buttons[0].y >= window_rect.bottom) { // taskbar on bottom
y_offset = (int)(pos_anim_value * use_overlay->height() * use_overlay->get_scale());
}
} else {
x_offset = 0;
y_offset = (int)(pos_anim_value * use_overlay->height() * use_overlay->get_scale());
}
// Draw background
winrt::com_ptr<ID2D1SolidColorBrush> brush;
float brush_opacity = get_overlay_opacity();
D2D1_COLOR_F brushColor = colors.light_mode ? D2D1::ColorF(1.0f, 1.0f, 1.0f, brush_opacity) : D2D1::ColorF(0, 0, 0, brush_opacity);
winrt::check_hresult(d2d_dc->CreateSolidColorBrush(brushColor, brush.put()));
D2D1_RECT_F background_rect = {};
background_rect.bottom = (float)window_height;
background_rect.right = (float)window_width;
d2d_dc->SetTransform(D2D1::Matrix3x2F::Identity());
d2d_dc->FillRectangle(background_rect, brush.get());
// Thumbnail logic:
auto window_state = get_window_state(active_window);
auto thumb_window = get_window_pos(active_window);
bool minature_shown = active_window != nullptr && thumbnail != nullptr && thumb_window && window_state != MINIMIZED;
RECT client_rect;
if (thumb_window && GetClientRect(active_window, &client_rect)) {
int dx = ((thumb_window->right - thumb_window->left) - (client_rect.right - client_rect.left)) / 2;
int dy = ((thumb_window->bottom - thumb_window->top) - (client_rect.bottom - client_rect.top)) / 2;
thumb_window->left += dx;
thumb_window->right -= dx;
thumb_window->top += dy;
thumb_window->bottom -= dy;
}
if (minature_shown && thumb_window->right - thumb_window->left <= 0 || thumb_window->bottom - thumb_window->top <= 0) {
minature_shown = false;
}
bool render_monitors = true;
auto total_monitor_with_screen = total_screen;
if (thumb_window) {
total_monitor_with_screen.rect.left = min(total_monitor_with_screen.rect.left, thumb_window->left + monitor_dx);
total_monitor_with_screen.rect.top = min(total_monitor_with_screen.rect.top, thumb_window->top + monitor_dy);
total_monitor_with_screen.rect.right = max(total_monitor_with_screen.rect.right, thumb_window->right + monitor_dx);
total_monitor_with_screen.rect.bottom = max(total_monitor_with_screen.rect.bottom, thumb_window->bottom + monitor_dy);
}
// Only allow the new rect beeing slight bigger.
if (total_monitor_with_screen.width() - total_screen.width() > (thumb_window->right - thumb_window->left) / 2 ||
total_monitor_with_screen.height() - total_screen.height() > (thumb_window->bottom - thumb_window->top) / 2) {
render_monitors = false;
}
if (window_state == MINIMIZED) {
total_monitor_with_screen = total_screen;
}
auto rect_and_scale = use_overlay->get_thumbnail_rect_and_scale(0, 0, total_monitor_with_screen.width(), total_monitor_with_screen.height(), 1);
if (minature_shown) {
RECT thumbnail_pos;
if (render_monitors) {
thumbnail_pos.left = (int)((thumb_window->left + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
thumbnail_pos.top = (int)((thumb_window->top + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
thumbnail_pos.right = (int)((thumb_window->right + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
thumbnail_pos.bottom = (int)((thumb_window->bottom + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
} else {
thumbnail_pos = use_overlay->get_thumbnail_rect_and_scale(0, 0, thumb_window->right - thumb_window->left, thumb_window->bottom - thumb_window->top, 1).rect;
}
// If the animation is done show the thumbnail
// we cannot animate the thumbnail, the animation lags behind
minature_shown = show_thumbnail(thumbnail_pos, current_anim_value);
} else {
hide_thumbnail();
}
if (window_state == MINIMIZED) {
render_monitors = true;
}
// render the monitors
if (render_monitors) {
brushColor = D2D1::ColorF(colors.desktop_fill_color, minature_shown ? current_anim_value : current_anim_value * 0.3f);
brush = nullptr;
winrt::check_hresult(d2d_dc->CreateSolidColorBrush(brushColor, brush.put()));
for (auto& monitor : monitors) {
D2D1_RECT_F monitor_rect;
monitor_rect.left = (float)((monitor.rect.left + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
monitor_rect.top = (float)((monitor.rect.top + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
monitor_rect.right = (float)((monitor.rect.right + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
monitor_rect.bottom = (float)((monitor.rect.bottom + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
d2d_dc->SetTransform(D2D1::Matrix3x2F::Identity());
d2d_dc->FillRectangle(monitor_rect, brush.get());
}
}
// Finalize the overlay - dimm the buttons if no thumbnail is present and show "No active window"
use_overlay->toggle_window_group(minature_shown || window_state == MINIMIZED);
if (!minature_shown && window_state != MINIMIZED) {
no_active.render(d2d_dc);
window_state = UNKNONW;
}
// Set the animation - move the draw window according to animation step
auto popin = D2D1::Matrix3x2F::Translation((float)x_offset, (float)y_offset);
d2d_dc->SetTransform(popin);
// Animate keys
for (unsigned id = 0; id < key_animations.size();) {
auto& animation = key_animations[id];
D2D1_COLOR_F color;
auto value = (float)animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO);
color.a = 1.0f;
color.r = animation.original.r + (1.0f - animation.original.r) * value;
color.g = animation.original.g + (1.0f - animation.original.g) * value;
color.b = animation.original.b + (1.0f - animation.original.b) * value;
animation.button->SetAttributeValue(L"fill", color);
if (animation.animation.done()) {
if (value == 1) {
animation.animation.reset(0.05, 1, 0);
animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO);
} else {
key_animations.erase(key_animations.begin() + id);
continue;
}
}
++id;
}
// Finally: render the overlay...
use_overlay->render(d2d_dc);
// ... window arrows texts ...
std::wstring left, right, up, down;
bool left_disabled = false;
bool right_disabled = false;
bool up_disabled = false;
bool down_disabled = false;
switch (window_state) {
case MINIMIZED:
left = L"No action";
left_disabled = true;
right = L"No action";
right_disabled = true;
up = L"Restore";
down = L"No action";
down_disabled = true;
break;
case MAXIMIZED:
left = L"Snap left";
right = L"Snap right";
up = L"No action";
up_disabled = true;
down = L"Restore";
break;
case SNAPED_TOP_LEFT:
left = L"Snap upper right";
right = L"Snap upper right";
up = L"Maximize";
down = L"Snap left";
break;
case SNAPED_LEFT:
left = L"Snap right";
right = L"Restore";
up = L"Snap upper left";
down = L"Snap lower left";
break;
case SNAPED_BOTTOM_LEFT:
left = L"Snap lower right";
right = L"Snap lower right";
up = L"Snap left";
down = L"Minimize";
break;
case SNAPED_TOP_RIGHT:
left = L"Snap upper left";
right = L"Snap upper left";
up = L"Maximize";
down = L"Snap right";
break;
case SNAPED_RIGHT:
left = L"Restore";
right = L"Snap left";
up = L"Snap upper right";
down = L"Snap lower right";
break;
case SNAPED_BOTTOM_RIGHT:
left = L"Snap lower left";
right = L"Snap lower left";
up = L"Snap right";
down = L"Minimize";
break;
case RESTORED:
left = L"Snap left";
right = L"Snap right";
up = L"Maximize";
down = L"Minimize";
break;
default:
left = L"No action";
left_disabled = true;
right = L"No action";
right_disabled = true;
up = L"No action";
up_disabled = true;
down = L"No action";
down_disabled = true;
}
auto text_color = D2D1::ColorF(colors.light_mode ? 0x222222 : 0xDDDDDD, minature_shown || window_state == MINIMIZED ? 1.0f : 0.3f);
use_overlay->find_element(L"KeyUpGroup")->SetAttributeValue(L"fill-opacity", up_disabled ? 0.3f : 1.0f);
text.set_aligment_center().write(d2d_dc, text_color, use_overlay->get_maximize_label(), up);
use_overlay->find_element(L"KeyDownGroup")->SetAttributeValue(L"fill-opacity", down_disabled ? 0.3f : 1.0f);
text.write(d2d_dc, text_color, use_overlay->get_minimize_label(), down);
use_overlay->find_element(L"KeyLeftGroup")->SetAttributeValue(L"fill-opacity", left_disabled ? 0.3f : 1.0f);
text.set_aligment_right().write(d2d_dc, text_color, use_overlay->get_snap_left(), left);
use_overlay->find_element(L"KeyRightGroup")->SetAttributeValue(L"fill-opacity", right_disabled ? 0.3f : 1.0f);
text.set_aligment_left().write(d2d_dc, text_color, use_overlay->get_snap_right(), right);
// ... and the arrows with numbers
for (auto&& button : tasklist_buttons) {
if ((size_t)(button.keynum) - 1 >= arrows.size()) {
continue;
}
render_arrow(arrows[(size_t)(button.keynum) - 1], button, window_rect, use_overlay->get_scale(), d2d_dc);
}
}

View File

@@ -0,0 +1,86 @@
#pragma once
#include "common/d2d_svg.h"
#include "common/d2d_window.h"
#include "common/d2d_text.h"
#include "common/monitors.h"
#include "common/animation.h"
#include "common/windows_colors.h"
#include "common/tasklist_positions.h"
struct ScaleResult {
double scale;
RECT rect;
};
class D2DOverlaySVG : public D2DSVG {
public:
D2DOverlaySVG& load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc);
D2DOverlaySVG& resize(int x, int y, int width, int height, float fill, float max_scale = -1.0f);
D2DOverlaySVG& find_thumbnail(const std::wstring& id);
D2DOverlaySVG& find_window_group(const std::wstring& id);
ScaleResult get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill);
D2DOverlaySVG& toggle_window_group(bool active);
winrt::com_ptr<ID2D1SvgElement> find_element(const std::wstring& id);
D2D1_RECT_F get_maximize_label() const;
D2D1_RECT_F get_minimize_label() const;
D2D1_RECT_F get_snap_left() const;
D2D1_RECT_F get_snap_right() const;
private:
D2D1_POINT_2F thumbnail_top_left = {};
D2D1_POINT_2F thumbnail_bottom_right = {};
RECT thumbnail_scaled_rect = {};
winrt::com_ptr<ID2D1SvgElement> window_group;
};
struct AnimateKeys {
Animation animation;
D2D1_COLOR_F original;
winrt::com_ptr<ID2D1SvgElement> button;
int vk_code;
};
class D2DOverlayWindow : public D2DWindow {
public:
D2DOverlayWindow();
void show(HWND active_window);
void animate(int vk_code);
~D2DOverlayWindow();
void apply_overlay_opacity(float opacity);
private:
void animate(int vk_code, int offset);
bool show_thumbnail(const RECT& rect_and_scale, double alpha);
void hide_thumbnail();
virtual void init() override;
virtual void resize() override;
virtual void render(ID2D1DeviceContext5* d2d_dc) override;
virtual void on_show() override;
virtual void on_hide() override;
float get_overlay_opacity();
bool running = true;
std::vector<AnimateKeys> key_animations;
std::vector<int> key_pressed;
std::vector<MonitorInfo> monitors;
ScreenSize total_screen;
int monitor_dx = 0, monitor_dy = 0;
D2DText text;
WindowsColors colors;
Animation animation;
RECT window_rect = {};
Tasklist tasklist;
std::vector<TasklistButton> tasklist_buttons;
std::thread tasklist_thread;
bool tasklist_update = false;
std::mutex tasklist_cv_mutex;
std::condition_variable tasklist_cv;
HTHUMBNAIL thumbnail;
HWND active_window = nullptr;
D2DOverlaySVG landscape, portrait;
D2DOverlaySVG* use_overlay;
D2DSVG no_active;
std::vector<D2DSVG> arrows;
std::chrono::steady_clock::time_point shown_start_time;
float overlay_opacity = 0.9f;
};

View File

@@ -0,0 +1,9 @@
#include "pch.h"
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "shcore.lib")
#pragma comment(lib, "windowsapp")
#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dcomp")
#pragma comment(lib, "dwmapi")

View File

@@ -0,0 +1,27 @@
#pragma once
#include "resource.h"
#include <winrt/base.h>
#include <Windows.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_3.h>
#include <d2d1_3helper.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <string>
#include <algorithm>
#include <chrono>
#include <mutex>
#include <thread>
#include <functional>
#include <condition_variable>
#include <stdexcept>
#include <tuple>
#include <unordered_set>
#include <string>
#include <common/common.h>
#include <ProjectTelemetry.h>

View File

@@ -0,0 +1,2 @@
#define IDS_SETTING_DESCRIPTION_PRESS_TIME 101
#define IDS_SETTING_DESCRIPTION_OVERLAY_OPACITY 102

View File

@@ -0,0 +1,170 @@
#include "pch.h"
#include "shortcut_guide.h"
#include "target_state.h"
#include "trace.h"
#include <common/settings_objects.h>
extern "C" IMAGE_DOS_HEADER __ImageBase;
OverlayWindow* instance = nullptr;
OverlayWindow::OverlayWindow() {
init_settings();
}
const wchar_t * OverlayWindow::get_name() {
return L"Shortcut Guide";
}
const wchar_t ** OverlayWindow::get_events() {
static const wchar_t* events[2] = { ll_keyboard, 0 };
return events;
}
bool OverlayWindow::get_config(wchar_t* buffer, int *buffer_size) {
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(L"Shows a help overlay with Windows shortcuts when the Windows key is pressed.");
settings.set_overview_link(L"https://github.com/microsoft/PowerToys/blob/master/src/modules/shortcut_guide/README.md");
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
);
return settings.serialize_to_buffer(buffer, buffer_size);
}
void OverlayWindow::set_config(const wchar_t * config) {
try {
PowerToysSettings::PowerToyValues _values =
PowerToysSettings::PowerToyValues::from_json_string(config);
if (_values.is_int_value(pressTime.name)) {
int press_delay_time = _values.get_int_value(pressTime.name);
pressTime.value = press_delay_time;
if (target_state) {
target_state->set_delay(press_delay_time);
}
}
if (_values.is_int_value(overlayOpacity.name)) {
int overlay_opacity = _values.get_int_value(overlayOpacity.name);
overlayOpacity.value = overlay_opacity;
if (winkey_popup) {
winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f);
}
}
_values.save_to_settings_file();
}
catch (std::exception ex) {
// Improper JSON.
}
}
void OverlayWindow::enable() {
if (!_enabled) {
winkey_popup = new D2DOverlayWindow();
winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value)/100.0f);
target_state = new TargetState(pressTime.value);
winkey_popup->initialize();
desktop = GetDesktopWindow();
shell = GetShellWindow();
}
_enabled = true;
}
void OverlayWindow::disable() {
if (_enabled) {
winkey_popup->hide();
target_state->exit();
int a = 0;
delete target_state;
delete winkey_popup;
target_state = nullptr;
winkey_popup = nullptr;
}
_enabled = false;
}
bool OverlayWindow::is_enabled() {
return _enabled;
}
intptr_t OverlayWindow::signal_event(const wchar_t * name, intptr_t data) {
if (_enabled && wcscmp(name, ll_keyboard) == 0) {
auto& event = *(reinterpret_cast<LowlevelKeyboardEvent*>(data));
if (event.wParam == WM_KEYDOWN ||
event.wParam == WM_SYSKEYDOWN ||
event.wParam == WM_KEYUP ||
event.wParam == WM_SYSKEYUP) {
bool supress = target_state->signal_event(event.lParam->vkCode,
event.wParam == WM_KEYDOWN || event.wParam == WM_SYSKEYDOWN);
return supress ? 1 : 0;
}
}
return 0;
}
void OverlayWindow::on_held() {
auto active_window = GetForegroundWindow();
active_window = GetAncestor(active_window, GA_ROOT);
if (active_window == desktop || active_window == shell) {
active_window = nullptr;
}
auto window_styles = active_window ? GetWindowLong(active_window, GWL_STYLE) : 0;
if ((window_styles & WS_CHILD) || (window_styles & WS_DISABLED)) {
active_window = nullptr;
}
char class_name[256] = "";
GetClassNameA(active_window, class_name, 256);
if (strcmp(class_name, "SysListView32") == 0 ||
strcmp(class_name, "WorkerW") == 0 ||
strcmp(class_name, "Shell_TrayWnd") == 0 ||
strcmp(class_name, "Shell_SecondaryTrayWnd") == 0) {
active_window = nullptr;
}
winkey_popup->show(active_window);
}
void OverlayWindow::on_held_press(DWORD vkCode) {
winkey_popup->animate(vkCode);
}
void OverlayWindow::was_hidden() {
target_state->was_hiden();
}
void OverlayWindow::destroy() {
delete this;
instance = nullptr;
}
void OverlayWindow::init_settings() {
try {
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_name());
if (settings.is_int_value(pressTime.name)) {
pressTime.value = settings.get_int_value(pressTime.name);
}
if (settings.is_int_value(overlayOpacity.name)) {
overlayOpacity.value = settings.get_int_value(overlayOpacity.name);
}
}
catch (std::exception ex) {
// Error while loading from the settings file. Just let default values stay as they are.
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <interface/powertoy_module_interface.h>
#include <interface/lowlevel_keyboard_event_data.h>
#include "overlay_window.h"
// We support only one instance of the overlay
extern class OverlayWindow* instance;
class TargetState;
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;
virtual void set_config(const wchar_t* config) override;
virtual void enable() override;
virtual void disable() override;
virtual bool is_enabled() override;
virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override;
void on_held();
void on_held_press(DWORD vkCode);
void was_hidden();
virtual void destroy() override;
private:
TargetState* target_state;
D2DOverlayWindow *winkey_popup;
HWND desktop, shell;
HWND active_window;
bool _enabled = false;
void init_settings();
struct PressTime {
PCWSTR name = L"press_time";
int value = 900; // ms
int resourceId = IDS_SETTING_DESCRIPTION_PRESS_TIME;
} pressTime;
struct OverlayOpacity {
PCWSTR name = L"overlay_opacity";
int value = 90; // percent
int resourceId = IDS_SETTING_DESCRIPTION_OVERLAY_OPACITY;
} overlayOpacity;
};

Binary file not shown.

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>overlaywindow</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;OVERLAYWINDOW_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\common\inc;..\..\common\Telemetry;..\..\;..\;..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;OVERLAYWINDOW_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\common\inc;..\..\common\Telemetry;..\..\;..\;..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="overlay_window.h" />
<ClInclude Include="keyboard_state.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="shortcut_guide.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="target_state.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="overlay_window.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="keyboard_state.cpp" />
<ClCompile Include="shortcut_guide.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="target_state.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="shortcut_guide.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="pch.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="keyboard_state.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shortcut_guide.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="target_state.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="overlay_window.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="keyboard_state.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shortcut_guide.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="target_state.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="overlay_window.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{2c7c97f7-0d87-4230-a4b2-baf2cfc35d58}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{aa4b6713-589d-42ef-804d-3a045833f83f}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="shortcut_guide.rc" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,147 @@
#include "pch.h"
#include "target_state.h"
#include "common/start_visible.h"
#include "keyboard_state.h"
TargetState::TargetState(int ms_delay) : delay(std::chrono::milliseconds(ms_delay)), thread(&TargetState::thread_proc, this)
{ }
bool TargetState::signal_event(unsigned vk_code, bool key_down) {
std::unique_lock lock(mutex);
if (!events.empty() && events.back().key_down == key_down && events.back().vk_code == vk_code) {
return false;
}
bool supress = false;
if (!key_down && (vk_code == VK_LWIN || vk_code == VK_RWIN) &&
state == Shown &&
std::chrono::system_clock::now() - singnal_timestamp > std::chrono::seconds(1) &&
!key_was_pressed) {
supress = true;
}
events.push_back({ key_down, vk_code });
lock.unlock();
cv.notify_one();
if (supress) {
INPUT input[3] = { {},{},{} };
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = VK_CONTROL;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = VK_CONTROL;
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
input[2].type = INPUT_KEYBOARD;
input[2].ki.wVk = VK_LWIN;
input[2].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(3, input, sizeof(INPUT));
}
return supress;
}
void TargetState::was_hiden() {
std::unique_lock<std::mutex> lock(mutex);
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() {
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)) {
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;
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();
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, delay);
if (state == Exiting)
return;
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 (!events.empty() || !only_winkey_key_held() || is_start_visible()) {
state = Hidden;
return;
}
if (std::chrono::system_clock::now() - winkey_timestamp < delay)
return;
singnal_timestamp = std::chrono::system_clock::now();
key_was_pressed = false;
state = Shown;
lock.unlock();
instance->on_held();
}
void TargetState::set_delay(int ms_delay) {
delay = std::chrono::milliseconds(ms_delay);
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include "shortcut_guide.h"
struct KeyEvent {
bool key_down;
unsigned vk_code;
};
class TargetState {
public:
TargetState(int ms_delay);
bool signal_event(unsigned vk_code, bool key_down);
void was_hiden();
void exit();
void set_delay(int ms_delay);
private:
KeyEvent next();
void handle_hidden();
void handle_timeout();
void handle_shown();
void thread_proc();
std::mutex mutex;
std::condition_variable cv;
std::chrono::system_clock::time_point winkey_timestamp, singnal_timestamp;
std::chrono::milliseconds delay;
std::deque<KeyEvent> events;
enum { Hidden, Timeout, Shown, Exiting } state = Hidden;
bool key_was_pressed = false;
std::thread thread;
};

View File

@@ -0,0 +1,47 @@
#include "pch.h"
#include "trace.h"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider() {
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider() {
TraceLoggingUnregister(g_hProvider);
}
void Trace::EventShow() {
TraceLoggingWrite(
g_hProvider,
"ShortcutGuide::Event::ShowGuide",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::EventHide(const __int64 duration_ms, std::vector<int> &key_pressed) {
std::string vk_codes;
std::vector<int>::iterator it;
for (it = key_pressed.begin(); it != key_pressed.end(); ) {
vk_codes += std::to_string(*it);
if (++it != key_pressed.end()) {
vk_codes += " ";
}
}
TraceLoggingWrite(
g_hProvider,
"ShortcutGuide::Event::HideGuide",
TraceLoggingInt64(duration_ms, "Duration in ms"),
TraceLoggingInt64(key_pressed.size(), "# of key pressed"),
TraceLoggingString(vk_codes.c_str(), "list of key pressed"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -0,0 +1,9 @@
#pragma once
class Trace {
public:
static void RegisterProvider();
static void UnregisterProvider();
static void EventShow();
static void EventHide(const __int64 duration_ms, std::vector<int> &key_pressed);
};