From 7e4e8f59bb9468f7f8bb7183d861b3b47e67a1e5 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Tue, 18 Jul 2023 14:33:32 +0200 Subject: [PATCH] [Mouse Crosshairs] Hide crosshairs when cursor hides (#27381) * hide crosshairs when cursor hides * fix formatting --- .../InclusiveCrosshairs.cpp | 103 ++++++++++++++++-- .../InclusiveCrosshairs.h | 2 + .../MousePointerCrosshairs/dllmain.cpp | 11 ++ .../MousePointerCrosshairsProperties.cs | 4 + .../Settings.UI/Strings/en-us/Resources.resw | 15 ++- .../ViewModels/MouseUtilsViewModel.cs | 20 ++++ .../Settings.UI/Views/MouseUtilsPage.xaml | 6 + 7 files changed, 143 insertions(+), 18 deletions(-) diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.cpp b/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.cpp index 6cedfe6d52..a6fca80090 100644 --- a/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.cpp +++ b/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.cpp @@ -45,6 +45,7 @@ private: static constexpr auto m_className = L"MousePointerCrosshairs"; static constexpr auto m_windowTitle = L"PowerToys Mouse Pointer Crosshairs"; + static constexpr DWORD AUTO_HIDE_TIMER_ID = 101; HWND m_hwndOwner = NULL; HWND m_hwnd = NULL; HINSTANCE m_hinstance = NULL; @@ -65,8 +66,10 @@ private: winrt::SpriteVisual m_bottom_crosshairs_border{ nullptr }; winrt::SpriteVisual m_bottom_crosshairs{ nullptr }; - bool m_visible = false; + bool m_drawing = false; bool m_destroyed = false; + bool m_hiddenCursor = false; + void SetAutoHideTimer() noexcept; // Configurable Settings winrt::Windows::UI::Color m_crosshairs_border_color = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_COLOR; @@ -75,6 +78,7 @@ private: int m_crosshairs_thickness = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_THICKNESS; int m_crosshairs_border_size = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_SIZE; float m_crosshairs_opacity = max(0.f, min(1.f, (float)INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_OPACITY / 100.0f)); + bool m_crosshairs_auto_hide = INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE; }; InclusiveCrosshairs* InclusiveCrosshairs::instance = nullptr; @@ -217,7 +221,7 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition() m_left_crosshairs.Offset({ ptCursor.x - m_crosshairs_radius + halfPixelAdjustment * 2.f, ptCursor.y + halfPixelAdjustment, .0f }); m_left_crosshairs.Size({ ptCursor.x - ptMonitorUpperLeft.x - m_crosshairs_radius + halfPixelAdjustment * 2, static_cast(m_crosshairs_thickness) }); - m_right_crosshairs_border.Offset({static_cast(ptCursor.x) + m_crosshairs_radius - m_crosshairs_border_size, ptCursor.y + halfPixelAdjustment, .0f }); + m_right_crosshairs_border.Offset({ static_cast(ptCursor.x) + m_crosshairs_radius - m_crosshairs_border_size, ptCursor.y + halfPixelAdjustment, .0f }); m_right_crosshairs_border.Size({ static_cast(ptMonitorBottomRight.x) - ptCursor.x - m_crosshairs_radius + m_crosshairs_border_size, m_crosshairs_thickness + m_crosshairs_border_size * 2.f }); m_right_crosshairs.Offset({ static_cast(ptCursor.x) + m_crosshairs_radius, ptCursor.y + halfPixelAdjustment, .0f }); m_right_crosshairs.Size({ static_cast(ptMonitorBottomRight.x) - ptCursor.x - m_crosshairs_radius, static_cast(m_crosshairs_thickness) }); @@ -227,11 +231,10 @@ void InclusiveCrosshairs::UpdateCrosshairsPosition() m_top_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, ptCursor.y - m_crosshairs_radius + halfPixelAdjustment * 2, .0f }); m_top_crosshairs.Size({ static_cast(m_crosshairs_thickness), ptCursor.y - ptMonitorUpperLeft.y - m_crosshairs_radius + halfPixelAdjustment * 2 }); - m_bottom_crosshairs_border.Offset({ ptCursor.x + halfPixelAdjustment, static_cast(ptCursor.y) + m_crosshairs_radius - m_crosshairs_border_size, .0f }); - m_bottom_crosshairs_border.Size({ m_crosshairs_thickness + m_crosshairs_border_size * 2.f, static_cast(ptMonitorBottomRight.y) - ptCursor.y - m_crosshairs_radius + m_crosshairs_border_size }); - m_bottom_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, static_cast(ptCursor.y) + m_crosshairs_radius, .0f }); - m_bottom_crosshairs.Size({ static_cast(m_crosshairs_thickness), static_cast(ptMonitorBottomRight.y) - ptCursor.y - m_crosshairs_radius }); - + m_bottom_crosshairs_border.Offset({ ptCursor.x + halfPixelAdjustment, static_cast(ptCursor.y) + m_crosshairs_radius - m_crosshairs_border_size, .0f }); + m_bottom_crosshairs_border.Size({ m_crosshairs_thickness + m_crosshairs_border_size * 2.f, static_cast(ptMonitorBottomRight.y) - ptCursor.y - m_crosshairs_radius + m_crosshairs_border_size }); + m_bottom_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, static_cast(ptCursor.y) + m_crosshairs_radius, .0f }); + m_bottom_crosshairs.Size({ static_cast(m_crosshairs_thickness), static_cast(ptMonitorBottomRight.y) - ptCursor.y - m_crosshairs_radius }); } LRESULT CALLBACK InclusiveCrosshairs::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept @@ -239,7 +242,8 @@ LRESULT CALLBACK InclusiveCrosshairs::MouseHookProc(int nCode, WPARAM wParam, LP if (nCode >= 0) { MSLLHOOKSTRUCT* hookData = reinterpret_cast(lParam); - if (wParam == WM_MOUSEMOVE) { + if (wParam == WM_MOUSEMOVE) + { instance->UpdateCrosshairsPosition(); } } @@ -251,18 +255,37 @@ void InclusiveCrosshairs::StartDrawing() Logger::info("Start drawing crosshairs."); Trace::StartDrawingCrosshairs(); UpdateCrosshairsPosition(); - ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); - m_visible = true; + + m_hiddenCursor = false; + if (m_crosshairs_auto_hide) + { + CURSORINFO cursorInfo{}; + cursorInfo.cbSize = sizeof(cursorInfo); + if (GetCursorInfo(&cursorInfo)) + { + m_hiddenCursor = !(cursorInfo.flags & CURSOR_SHOWING); + } + + SetAutoHideTimer(); + } + + if (!m_hiddenCursor) + { + ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); + } + + m_drawing = true; m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, m_hinstance, 0); } void InclusiveCrosshairs::StopDrawing() { Logger::info("Stop drawing crosshairs."); - m_visible = false; + m_drawing = false; ShowWindow(m_hwnd, SW_HIDE); UnhookWindowsHookEx(m_mouseHook); m_mouseHook = NULL; + KillTimer(m_hwnd, AUTO_HIDE_TIMER_ID); } void InclusiveCrosshairs::SwitchActivationMode() @@ -278,9 +301,30 @@ void InclusiveCrosshairs::ApplySettings(InclusiveCrosshairsSettings& settings, b m_crosshairs_opacity = max(0.f, min(1.f, (float)settings.crosshairsOpacity / 100.0f)); m_crosshairs_border_color = settings.crosshairsBorderColor; m_crosshairs_border_size = settings.crosshairsBorderSize; + bool autoHideChanged = m_crosshairs_auto_hide != settings.crosshairsAutoHide; + m_crosshairs_auto_hide = settings.crosshairsAutoHide; if (applyToRunTimeObjects) { + if (autoHideChanged) + { + if (m_crosshairs_auto_hide) + { + SetAutoHideTimer(); + } + else + { + KillTimer(m_hwnd, AUTO_HIDE_TIMER_ID); + + // Edge case of settings being changed with hidden crosshairs: timer time-out is 1 seconds + if (m_drawing && m_hiddenCursor) + { + instance->m_hiddenCursor = false; + ShowWindow(instance->m_hwnd, SW_SHOWNOACTIVATE); + } + } + } + // Runtime objects already created. Should update in the owner thread. if (m_dispatcherQueueController == nullptr) { @@ -331,7 +375,7 @@ LRESULT CALLBACK InclusiveCrosshairs::WndProc(HWND hWnd, UINT message, WPARAM wP case WM_NCHITTEST: return HTTRANSPARENT; case WM_SWITCH_ACTIVATION_MODE: - if (instance->m_visible) + if (instance->m_drawing) { instance->StopDrawing(); } @@ -343,6 +387,32 @@ LRESULT CALLBACK InclusiveCrosshairs::WndProc(HWND hWnd, UINT message, WPARAM wP case WM_DESTROY: instance->DestroyInclusiveCrosshairs(); break; + case WM_TIMER: + if (wParam == AUTO_HIDE_TIMER_ID && instance->m_drawing) + { + CURSORINFO cursorInfo{}; + cursorInfo.cbSize = sizeof(cursorInfo); + if (GetCursorInfo(&cursorInfo)) + { + if (cursorInfo.flags & CURSOR_SHOWING) + { + if (instance->m_hiddenCursor) + { + instance->m_hiddenCursor = false; + ShowWindow(instance->m_hwnd, SW_SHOWNOACTIVATE); + } + } + else + { + if (!instance->m_hiddenCursor) + { + instance->m_hiddenCursor = true; + ShowWindow(instance->m_hwnd, SW_HIDE); + } + } + } + } + break; default: return DefWindowProc(hWnd, message, wParam, lParam); } @@ -390,6 +460,15 @@ void InclusiveCrosshairs::Terminate() } } +void InclusiveCrosshairs::SetAutoHideTimer() noexcept +{ + if (SetTimer(m_hwnd, AUTO_HIDE_TIMER_ID, 1000, NULL) == 0) + { + int error = GetLastError(); + Logger::trace("Failed to create auto hide timer. Last error: {}", error); + } +} + #pragma region InclusiveCrosshairs_API void InclusiveCrosshairsApplySettings(InclusiveCrosshairsSettings& settings) diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.h b/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.h index cf4aae14a1..28844e9380 100644 --- a/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.h +++ b/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.h @@ -7,6 +7,7 @@ const winrt::Windows::UI::Color INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_COLOR constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_RADIUS = 20; constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_THICKNESS = 5; constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_SIZE = 1; +constexpr bool INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE = false; struct InclusiveCrosshairsSettings { @@ -16,6 +17,7 @@ struct InclusiveCrosshairsSettings int crosshairsThickness = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_THICKNESS; int crosshairsOpacity = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_OPACITY; int crosshairsBorderSize = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_SIZE; + bool crosshairsAutoHide = INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE; }; int InclusiveCrosshairsMain(HINSTANCE hinst, InclusiveCrosshairsSettings& settings); diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp b/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp index 257d050e5e..cf611b55cc 100644 --- a/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp +++ b/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp @@ -17,6 +17,7 @@ namespace const wchar_t JSON_KEY_CROSSHAIRS_THICKNESS[] = L"crosshairs_thickness"; const wchar_t JSON_KEY_CROSSHAIRS_BORDER_COLOR[] = L"crosshairs_border_color"; const wchar_t JSON_KEY_CROSSHAIRS_BORDER_SIZE[] = L"crosshairs_border_size"; + const wchar_t JSON_KEY_CROSSHAIRS_AUTO_HIDE[] = L"crosshairs_auto_hide"; } extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -330,6 +331,16 @@ public: { Logger::warn("Failed to initialize border color from settings. Will use default value"); } + try + { + // Parse auto hide + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_AUTO_HIDE); + inclusiveCrosshairsSettings.crosshairsAutoHide = static_cast(jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE)); + } + catch (...) + { + Logger::warn("Failed to initialize auto hide from settings. Will use default value"); + } } else { diff --git a/src/settings-ui/Settings.UI.Library/MousePointerCrosshairsProperties.cs b/src/settings-ui/Settings.UI.Library/MousePointerCrosshairsProperties.cs index 0c1dde5722..1354088163 100644 --- a/src/settings-ui/Settings.UI.Library/MousePointerCrosshairsProperties.cs +++ b/src/settings-ui/Settings.UI.Library/MousePointerCrosshairsProperties.cs @@ -31,6 +31,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("crosshairs_border_size")] public IntProperty CrosshairsBorderSize { get; set; } + [JsonPropertyName("crosshairs_auto_hide")] + public BoolProperty CrosshairsAutoHide { get; set; } + public MousePointerCrosshairsProperties() { ActivationShortcut = DefaultActivationShortcut; @@ -40,6 +43,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library CrosshairsThickness = new IntProperty(5); CrosshairsBorderColor = new StringProperty("#FFFFFF"); CrosshairsBorderSize = new IntProperty(1); + CrosshairsAutoHide = new BoolProperty(false); } } } diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index a8bc7f744c..22ec1f4a49 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -2150,7 +2150,7 @@ From there, simply click on one of the supported files in the File Explorer and Welcome to PowerToys - + Welcome to PowerToys @@ -2469,10 +2469,10 @@ From there, simply click on one of the supported files in the File Explorer and Cancel - Press a combination of keys to change this shortcut + Press a combination of keys to change this shortcut - Reset + Reset Save @@ -3318,7 +3318,7 @@ Activate by holding the key for the character you want to add an accent to, then Default and extended context menu - + Extended context menu only @@ -3551,9 +3551,9 @@ Activate by holding the key for the character you want to add an accent to, then Maximum width (px) px = pixels - + PowerToys Settings - + A lightning fast file preview feature for Windows. @@ -3582,4 +3582,7 @@ Activate by holding the key for the character you want to add an accent to, then UTF-8 with BOM + + Automatically hide crosshairs when the mouse pointer is hidden + diff --git a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs index 278026d43d..db14dff27f 100644 --- a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs @@ -106,6 +106,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _mousePointerCrosshairsRadius = MousePointerCrosshairsSettingsConfig.Properties.CrosshairsRadius.Value; _mousePointerCrosshairsThickness = MousePointerCrosshairsSettingsConfig.Properties.CrosshairsThickness.Value; _mousePointerCrosshairsBorderSize = MousePointerCrosshairsSettingsConfig.Properties.CrosshairsBorderSize.Value; + _mousePointerCrosshairsAutoHide = MousePointerCrosshairsSettingsConfig.Properties.CrosshairsAutoHide.Value; // set the callback functions value to handle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; @@ -792,6 +793,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool MousePointerCrosshairsAutoHide + { + get + { + return _mousePointerCrosshairsAutoHide; + } + + set + { + if (value != _mousePointerCrosshairsAutoHide) + { + _mousePointerCrosshairsAutoHide = value; + MousePointerCrosshairsSettingsConfig.Properties.CrosshairsAutoHide.Value = value; + NotifyMousePointerCrosshairsPropertyChanged(); + } + } + } + public void NotifyMousePointerCrosshairsPropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(propertyName); @@ -850,5 +869,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private int _mousePointerCrosshairsThickness; private string _mousePointerCrosshairsBorderColor; private int _mousePointerCrosshairsBorderSize; + private bool _mousePointerCrosshairsAutoHide; } } diff --git a/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml b/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml index 054b31b59a..9fc657f415 100644 --- a/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml +++ b/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml @@ -382,6 +382,12 @@ SpinButtonPlacementMode="Compact" Value="{x:Bind Mode=TwoWay, Path=ViewModel.MousePointerCrosshairsBorderSize}" /> + + + +