diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index d678404ba5..562ce51cf4 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -5,6 +5,7 @@ abcdef abcdefgh ABCDEFGHIJKLMNOPQRSTUVWXYZ abgr +abi ABlocked Abug accctrl @@ -382,6 +383,7 @@ CXVIRTUALSCREEN cxx cxxopts CYSMICON +CYVIRTUALSCREEN cziplib Dac dacl @@ -493,6 +495,8 @@ DPICHANGED DPolicy DPopup DPSAPI +DQTAT +DQTYPE DRAWFRAME drawingcolor dreamsofameaningfullife @@ -788,6 +792,7 @@ hotspot HPAINTBUFFER hpj hpp +HRAWINPUT hread HREDRAW href @@ -807,6 +812,7 @@ Htmdid html htt http +HTTRANSPARENT hwb HWINEVENTHOOK hwnd @@ -840,6 +846,7 @@ ICollection IColor ICommand IComparer +ICompositor ICONERROR ICONINFORMATION IContext @@ -854,6 +861,7 @@ IDesktop IDictionary IDirectory IDispatch +IDispatcher IDisposable idl IDLIST @@ -941,6 +949,7 @@ INPUTHARDWARE INPUTKEYBOARD INPUTLANGCHANGED INPUTMOUSE +INPUTSINK INPUTTYPE INSTALLDESKTOPSHORTCUT INSTALLDIR @@ -1702,6 +1711,9 @@ Radiobuttons RAII RAlt randyrants +RAWINPUT +RAWINPUTDEVICE +RAWINPUTHEADER RAWPATH rbegin Rbp @@ -1786,6 +1798,7 @@ rgs rhs ricardosantos Richtext +RIDEV RIGHTSCROLLBAR riid riverar @@ -2165,7 +2178,9 @@ tsx TYMED typedef TYPEKEY +TYPEKEYBOARD TYPELIB +TYPEMOUSE typename typeof typeparam @@ -2468,6 +2483,7 @@ XSmall XStr XToolset xunit +XVIRTUALSCREEN Yaml YDiff YIncrement @@ -2478,6 +2494,7 @@ YStr YUY yuyoyuppe YUYV +YVIRTUALSCREEN YVU YVYU ZEROINIT diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 1e46e0810f..8f4b0da2f4 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -170,6 +170,7 @@ build: - 'modules\launcher\Wox.dll' - 'modules\launcher\Wox.Infrastructure.dll' - 'modules\launcher\Wox.Plugin.dll' + - 'modules\MouseUtils\FindMyMouse.dll' - 'modules\PowerRename\PowerRenameExt.dll' - 'modules\ShortcutGuide\ShortcutGuide\PowerToys.ShortcutGuide.exe' - 'modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.dll' diff --git a/COMMUNITY.md b/COMMUNITY.md index a1ed96bddc..6c005bbede 100644 --- a/COMMUNITY.md +++ b/COMMUNITY.md @@ -72,6 +72,10 @@ PowerToys Awake is a tool to keep your computer awake. Color Picker is from Martin. +### [@oldnewthing](https://github.com/oldnewthing) - Raymond Chen + +Find My Mouse is based on Raymond Chen's SuperSonar. + ### Microsoft InVEST team This amazing team helped PowerToys develop PowerToys Run and Keyboard manager as well as update our Settings to v2. @alekhyareddy28, @arjunbalgovind, @jyuwono @laviusmotileng-ms, @ryanbodrug-microsoft, @saahmedm, @somil55, @traies, @udit3333 diff --git a/PowerToys.sln b/PowerToys.sln index 4d0113baa7..17e285653d 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -372,6 +372,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj", "{4ED320BC-BA04-4D42-8D15-CBE62151F08B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseUtils", "MouseUtils", "{322566EF-20DC-43A6-B9F8-616AF942579A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FindMyMouse", "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj", "{E94FD11C-0591-456F-899F-EFC0CA548336}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -981,6 +985,12 @@ Global {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.Build.0 = Release|x64 {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.ActiveCfg = Release|Any CPU {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.Build.0 = Release|Any CPU + {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.ActiveCfg = Debug|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.Build.0 = Debug|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x86.ActiveCfg = Debug|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.ActiveCfg = Release|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.Build.0 = Release|x64 + {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1098,6 +1108,8 @@ Global {F40C3397-1834-4530-B2D9-8F8B8456BCDF} = {2F305555-C296-497E-AC20-5FA1B237996A} {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {4ED320BC-BA04-4D42-8D15-CBE62151F08B} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {322566EF-20DC-43A6-B9F8-616AF942579A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {E94FD11C-0591-456F-899F-EFC0CA548336} = {322566EF-20DC-43A6-B9F8-616AF942579A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/README.md b/README.md index 8fbaf7633d..1f4708bc0f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline | [Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | -| [Video Conference Mute (Experimental)](https://aka.ms/PowerToysOverview_VideoConference) | | | +| [Video Conference Mute (Experimental)](https://aka.ms/PowerToysOverview_VideoConference) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | | ## Installing and running Microsoft PowerToys diff --git a/doc/devdocs/akaLinks.md b/doc/devdocs/akaLinks.md index e81275efef..064035fd16 100644 --- a/doc/devdocs/akaLinks.md +++ b/doc/devdocs/akaLinks.md @@ -25,6 +25,7 @@ | PowerToysOverview_FileExplorerAddOns | https://docs.microsoft.com/windows/powertoys/file-explorer | | PowerToysOverview_ImageResizer | https://docs.microsoft.com/windows/powertoys/image-resizer | | PowerToysOverview_KeyboardManager | https://docs.microsoft.com/windows/powertoys/keyboard-manager | +| PowerToysOverview_MouseUtilities | https://docs.microsoft.com/windows/powertoys/mouse-utilities | | PowerToysOverview_PowerRename | https://docs.microsoft.com/windows/powertoys/powerrename | | PowerToysOverview_PowerToysRun | https://docs.microsoft.com/windows/powertoys/run | | PowerToysOverview_ShortcutGuide | https://docs.microsoft.com/windows/powertoys/shortcut-guide | diff --git a/doc/images/icons/MouseUtils.png b/doc/images/icons/MouseUtils.png new file mode 100644 index 0000000000..08a730476e Binary files /dev/null and b/doc/images/icons/MouseUtils.png differ diff --git a/doc/images/overview/MouseUtils_large.png b/doc/images/overview/MouseUtils_large.png new file mode 100644 index 0000000000..1e60dec571 Binary files /dev/null and b/doc/images/overview/MouseUtils_large.png differ diff --git a/doc/images/overview/MouseUtils_small.png b/doc/images/overview/MouseUtils_small.png new file mode 100644 index 0000000000..cb1c3907b1 Binary files /dev/null and b/doc/images/overview/MouseUtils_small.png differ diff --git a/doc/images/overview/Original/MouseUtils.png b/doc/images/overview/Original/MouseUtils.png new file mode 100644 index 0000000000..8e374e3ceb Binary files /dev/null and b/doc/images/overview/Original/MouseUtils.png differ diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 43518096c7..32df99d259 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -10,6 +10,7 @@ + @@ -270,6 +271,10 @@ + + + + @@ -702,6 +707,13 @@ + + + + + + + @@ -903,21 +915,21 @@ - + - + - + @@ -1012,6 +1024,7 @@ + diff --git a/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs b/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs index c5054ba7d6..e8a6cfdce0 100644 --- a/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs +++ b/src/common/Microsoft.PowerToys.Common.UI/SettingsDeepLink.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Common.UI Run, ImageResizer, KBM, + MouseUtils, PowerRename, FileExplorer, ShortcutGuide, @@ -43,6 +44,8 @@ namespace Microsoft.PowerToys.Common.UI return "ImageResizer"; case SettingsWindow.KBM: return "KBM"; + case SettingsWindow.MouseUtils: + return "MouseUtils"; case SettingsWindow.PowerRename: return "PowerRename"; case SettingsWindow.FileExplorer: diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index d4ea19a40f..35b398792d 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -23,6 +23,7 @@ struct LogSettings inline const static std::wstring shortcutGuideLogPath = L"ShortcutGuideLogs\\shortcut-guide-log.txt"; inline const static std::string keyboardManagerLoggerName = "keyboard-manager"; inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt"; + inline const static std::string findMyMouseLoggerName = "find-my-mouse"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/modules/MouseUtils/FindMyMouse/Directory.Build.targets b/src/modules/MouseUtils/FindMyMouse/Directory.Build.targets new file mode 100644 index 0000000000..53fe8fc425 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.base.rc b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.base.rc new file mode 100644 index 0000000000..0bcdeca2ef --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.base.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp new file mode 100644 index 0000000000..4af7906e43 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -0,0 +1,814 @@ +// FindMyMouse.cpp : Based on Raymond Chen's SuperSonar.cpp +// +#include "pch.h" +#include "FindMyMouse.h" +#include "trace.h" + +#ifdef COMPOSITION +namespace winrt +{ + using namespace winrt::Windows::System; + using namespace winrt::Windows::UI::Composition; +} + +namespace ABI +{ + using namespace ABI::Windows::System; + using namespace ABI::Windows::UI::Composition::Desktop; +} +#endif + +#pragma region Super_Sonar_Base_Code + +template +struct SuperSonar +{ + bool Initialize(HINSTANCE hinst); + void Terminate(); + +protected: + // You are expected to override these, as appropriate. + + DWORD GetExtendedStyle() + { + return 0; + } + + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + return BaseWndProc(message, wParam, lParam); + } + + void BeforeMoveSonar() {} + void AfterMoveSonar() {} + void SetSonarVisibility(bool visible) = delete; + +protected: + // Base class members you can access. + D* Shim() { return static_cast(this); } + LRESULT BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept; + + HWND m_hwnd; + POINT m_sonarPos = ptNowhere; + + static constexpr int SonarRadius = 100; + static constexpr int SonarZoomFactor = 9; + static constexpr DWORD FadeDuration = 500; + static constexpr int FinalAlphaNumerator = 1; + static constexpr int FinalAlphaDenominator = 2; + winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr }; + +private: + static bool IsEqual(POINT const& p1, POINT const& p2) + { + return p1.x == p2.x && p1.y == p2.y; + } + + static constexpr POINT ptNowhere = { -1, -1 }; + + static constexpr DWORD TIMER_ID_TRACK = 100; + static constexpr DWORD IdlePeriod = 1000; + + // Activate sonar: Hit LeftControl twice. + enum class SonarState + { + Idle, + ControlDown1, + ControlUp1, + ControlDown2, + ControlUp2, + }; + + HWND m_hwndOwner; + SonarState m_sonarState = SonarState::Idle; + POINT m_lastKeyPos{}; + DWORD m_lastKeyTime{}; + + static constexpr DWORD NoSonar = 0; + static constexpr DWORD SonarWaitingForMouseMove = 1; + DWORD m_sonarStart = NoSonar; + bool m_isSnoopingMouse = false; + +private: + static constexpr auto className = L"FindMyMouse"; + + // Use the runner name for the Window title. Otherwise, since Find My Mouse has an actual visual, its Window name will be the one shown in Task Manager after being shown. + static constexpr auto windowTitle = L"PowerToys Runner"; + + static LRESULT CALLBACK s_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + + BOOL OnSonarCreate(); + void OnSonarDestroy(); + void OnSonarInput(WPARAM flags, HRAWINPUT hInput); + void OnSonarKeyboardInput(RAWINPUT const& input); + void OnSonarMouseInput(RAWINPUT const& input); + void OnMouseTimer(); + + void StartSonar(); + void StopSonar(); + + void UpdateMouseSnooping(); +}; + +template +bool SuperSonar::Initialize(HINSTANCE hinst) +{ + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + WNDCLASS wc{}; + + if (!GetClassInfoW(hinst, className, &wc)) + { + wc.lpfnWndProc = s_WndProc; + wc.hInstance = hinst; + wc.hIcon = LoadIcon(hinst, IDI_APPLICATION); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); + wc.lpszClassName = className; + + if (!RegisterClassW(&wc)) + { + return false; + } + } + + m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr); + + DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | Shim()->GetExtendedStyle(); + return CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this) != nullptr; +} + +template +void SuperSonar::Terminate() +{ + auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); + bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + DestroyWindow(m_hwndOwner); + }); + if (!enqueueSucceeded) + { + Logger::error("Couldn't enqueue message to destroy the sonar Window."); + } +} + +template +LRESULT SuperSonar::s_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + SuperSonar* self; + if (message == WM_NCCREATE) + { + auto info = (LPCREATESTRUCT)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)info->lpCreateParams); + self = (SuperSonar*)info->lpCreateParams; + self->m_hwnd = hwnd; + } + else + { + self = (SuperSonar*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + } + if (self) + { + return self->Shim()->WndProc(message, wParam, lParam); + } + else + { + return DefWindowProc(hwnd, message, wParam, lParam); + } +} + +template +LRESULT SuperSonar::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept +{ + switch (message) + { + case WM_CREATE: + return OnSonarCreate() ? 0 : -1; + + case WM_DESTROY: + OnSonarDestroy(); + break; + + case WM_INPUT: + OnSonarInput(wParam, (HRAWINPUT)lParam); + break; + + case WM_TIMER: + switch (wParam) + { + case TIMER_ID_TRACK: + OnMouseTimer(); + break; + } + break; + + case WM_NCHITTEST: + return HTTRANSPARENT; + } + + return DefWindowProc(m_hwnd, message, wParam, lParam); +} + +template +BOOL SuperSonar::OnSonarCreate() +{ + RAWINPUTDEVICE keyboard{}; + keyboard.usUsagePage = HID_USAGE_PAGE_GENERIC; + keyboard.usUsage = HID_USAGE_GENERIC_KEYBOARD; + keyboard.dwFlags = RIDEV_INPUTSINK; + keyboard.hwndTarget = m_hwnd; + return RegisterRawInputDevices(&keyboard, 1, sizeof(keyboard)); +} + +template +void SuperSonar::OnSonarDestroy() +{ + PostQuitMessage(0); +} + +template +void SuperSonar::OnSonarInput(WPARAM flags, HRAWINPUT hInput) +{ + RAWINPUT input; + UINT size = sizeof(input); + auto result = GetRawInputData(hInput, RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER)); + if ((int)result < sizeof(RAWINPUTHEADER)) + { + return; + } + + switch (input.header.dwType) + { + case RIM_TYPEKEYBOARD: + OnSonarKeyboardInput(input); + break; + case RIM_TYPEMOUSE: + OnSonarMouseInput(input); + break; + } +} + +template +void SuperSonar::OnSonarKeyboardInput(RAWINPUT const& input) +{ + if (input.data.keyboard.VKey != VK_CONTROL) + { + StopSonar(); + return; + } + + bool pressed = (input.data.keyboard.Flags & RI_KEY_BREAK) == 0; + bool rightCtrl = (input.data.keyboard.Flags & RI_KEY_E0) != 0; + + // Deal with rightCtrl first. + if (rightCtrl) + { + /* + * SuperSonar originally exited when pressing right control after pressing left control twice. + * We take care of exiting FindMyMouse through module disabling in PowerToys settings instead. + if (m_sonarState == SonarState::ControlUp2) + { + Terminate(); + } + */ + StopSonar(); + return; + } + + switch (m_sonarState) + { + case SonarState::Idle: + if (pressed) + { + m_sonarState = SonarState::ControlDown1; + m_lastKeyTime = GetTickCount(); + m_lastKeyPos = {}; + GetCursorPos(&m_lastKeyPos); + UpdateMouseSnooping(); + } + break; + + case SonarState::ControlDown1: + if (!pressed) + { + m_sonarState = SonarState::ControlUp1; + } + break; + + case SonarState::ControlUp1: + case SonarState::ControlUp2: + if (pressed) + { + m_sonarState = SonarState::ControlDown2; + auto now = GetTickCount(); + POINT ptCursor{}; + if (GetCursorPos(&ptCursor) && + now - m_lastKeyTime <= GetDoubleClickTime() && + IsEqual(m_lastKeyPos, ptCursor)) + { + StartSonar(); + } + m_lastKeyTime = now; + m_lastKeyPos = ptCursor; + } + break; + + case SonarState::ControlDown2: + if (!pressed) + { + m_sonarState = SonarState::ControlUp2; + } + break; + } +} + +template +void SuperSonar::OnSonarMouseInput(RAWINPUT const& input) +{ + if (input.data.mouse.usButtonFlags) + { + StopSonar(); + } + else if (m_sonarStart != NoSonar) + { + OnMouseTimer(); + } +} + +template +void SuperSonar::StartSonar() +{ + Logger::info("Focusing the sonar on the mouse cursor."); + Trace::MousePointerFocused(); + // Cover the entire virtual screen. + SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0); + m_sonarPos = ptNowhere; + OnMouseTimer(); + UpdateMouseSnooping(); + Shim()->SetSonarVisibility(true); +} + +template +void SuperSonar::StopSonar() +{ + if (m_sonarStart != NoSonar) + { + m_sonarStart = NoSonar; + Shim()->SetSonarVisibility(false); + KillTimer(m_hwnd, TIMER_ID_TRACK); + } + m_sonarState = SonarState::Idle; + UpdateMouseSnooping(); +} + +template +void SuperSonar::OnMouseTimer() +{ + auto now = GetTickCount(); + + // If mouse has moved, then reset the sonar timer. + POINT ptCursor{}; + if (!GetCursorPos(&ptCursor)) + { + // We are no longer the active desktop - done. + StopSonar(); + return; + } + ScreenToClient(m_hwnd, &ptCursor); + + if (IsEqual(m_sonarPos, ptCursor)) + { + // Mouse is stationary. + if (m_sonarStart != SonarWaitingForMouseMove && now - m_sonarStart >= IdlePeriod) + { + StopSonar(); + return; + } + } + else + { + // Mouse has moved. + if (IsEqual(m_sonarPos, ptNowhere)) + { + // Initial call, mark sonar as active but waiting for first mouse-move. + now = SonarWaitingForMouseMove; + } + SetTimer(m_hwnd, TIMER_ID_TRACK, IdlePeriod, nullptr); + Shim()->BeforeMoveSonar(); + m_sonarPos = ptCursor; + m_sonarStart = now; + Shim()->AfterMoveSonar(); + } +} + +template +void SuperSonar::UpdateMouseSnooping() +{ + bool wantSnoopingMouse = m_sonarStart != NoSonar || m_sonarState != SonarState::Idle; + if (m_isSnoopingMouse != wantSnoopingMouse) + { + m_isSnoopingMouse = wantSnoopingMouse; + RAWINPUTDEVICE mouse{}; + mouse.usUsagePage = HID_USAGE_PAGE_GENERIC; + mouse.usUsage = HID_USAGE_GENERIC_MOUSE; + if (wantSnoopingMouse) + { + mouse.dwFlags = RIDEV_INPUTSINK; + mouse.hwndTarget = m_hwnd; + } + else + { + mouse.dwFlags = RIDEV_REMOVE; + mouse.hwndTarget = nullptr; + } + RegisterRawInputDevices(&mouse, 1, sizeof(mouse)); + } +} + +struct CompositionSpotlight : SuperSonar +{ + static constexpr UINT WM_OPACITY_ANIMATION_COMPLETED = WM_APP; + static constexpr float SonarRadiusFloat = static_cast(SonarRadius); + + DWORD GetExtendedStyle() + { + return WS_EX_NOREDIRECTIONBITMAP; + } + + void AfterMoveSonar() + { + m_spotlight.Offset({ (float)m_sonarPos.x, (float)m_sonarPos.y, 0.0f }); + } + + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + switch (message) + { + case WM_CREATE: + return OnCompositionCreate() && BaseWndProc(message, wParam, lParam); + + case WM_OPACITY_ANIMATION_COMPLETED: + OnOpacityAnimationCompleted(); + break; + } + return BaseWndProc(message, wParam, lParam); + } + + void SetSonarVisibility(bool visible) + { + m_batch = m_compositor.GetCommitBatch(winrt::CompositionBatchTypes::Animation); + m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) { + PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0); + }); + m_root.Opacity(visible ? static_cast(FinalAlphaNumerator) / FinalAlphaDenominator : 0.0f); + if (visible) + { + ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); + } + } + +private: + bool OnCompositionCreate() + try + { + // We need a dispatcher queue. + DispatcherQueueOptions options = { + sizeof(options), + DQTYPE_THREAD_CURRENT, + DQTAT_COM_ASTA, + }; + ABI::IDispatcherQueueController* controller; + winrt::check_hresult(CreateDispatcherQueueController(options, &controller)); + *winrt::put_abi(m_dispatcherQueueController) = controller; + + // Create the compositor for our window. + m_compositor = winrt::Compositor(); + ABI::IDesktopWindowTarget* target; + winrt::check_hresult(m_compositor.as()->CreateDesktopWindowTarget(m_hwnd, false, &target)); + *winrt::put_abi(m_target) = target; + + // Our composition tree: + // + // [root] ContainerVisual + // \ LayerVisual + // \[gray backdrop] + // [spotlight] + m_root = m_compositor.CreateContainerVisual(); + m_root.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + m_root.Opacity(0.0f); + m_target.Root(m_root); + + auto layer = m_compositor.CreateLayerVisual(); + layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + m_root.Children().InsertAtTop(layer); + + auto backdrop = m_compositor.CreateSpriteVisual(); + backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + backdrop.Brush(m_compositor.CreateColorBrush({ 255, 0, 0, 0 })); + layer.Children().InsertAtTop(backdrop); + + m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation + auto circleShape = m_compositor.CreateSpriteShape(m_circleGeometry); + circleShape.FillBrush(m_compositor.CreateColorBrush({ 255, 255, 255, 255 })); + circleShape.Offset({ SonarRadiusFloat * SonarZoomFactor, SonarRadiusFloat * SonarZoomFactor }); + m_spotlight = m_compositor.CreateShapeVisual(); + m_spotlight.Size({ SonarRadiusFloat * 2 * SonarZoomFactor, SonarRadiusFloat * 2 * SonarZoomFactor }); + m_spotlight.AnchorPoint({ 0.5f, 0.5f }); + m_spotlight.Shapes().Append(circleShape); + + layer.Children().InsertAtTop(m_spotlight); + + // Implicitly animate the alpha. + auto animation = m_compositor.CreateScalarKeyFrameAnimation(); + animation.Target(L"Opacity"); + animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue"); + animation.Duration(std::chrono::milliseconds{ FadeDuration }); + auto collection = m_compositor.CreateImplicitAnimationCollection(); + collection.Insert(L"Opacity", animation); + m_root.ImplicitAnimations(collection); + + // Radius of spotlight shrinks as opacity increases. + // At opacity zero, it is SonarRadius * SonarZoomFactor. + // At maximum opacity, it is SonarRadius. + auto radiusExpression = m_compositor.CreateExpressionAnimation(); + radiusExpression.SetReferenceParameter(L"Root", m_root); + wchar_t expressionText[256]; + winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", SonarRadius * SonarZoomFactor, SonarRadius * SonarZoomFactor, SonarRadius, SonarRadius, FinalAlphaDenominator, FinalAlphaNumerator)); + radiusExpression.Expression(expressionText); + m_circleGeometry.StartAnimation(L"Radius", radiusExpression); + + return true; + } + catch (...) + { + return false; + } + + void OnOpacityAnimationCompleted() + { + if (m_root.Opacity() < 0.01f) + { + ShowWindow(m_hwnd, SW_HIDE); + } + } + +private: + winrt::Compositor m_compositor{ nullptr }; + winrt::Desktop::DesktopWindowTarget m_target{ nullptr }; + winrt::ContainerVisual m_root{ nullptr }; + winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr }; + winrt::ShapeVisual m_spotlight{ nullptr }; + winrt::CompositionCommitBatch m_batch{ nullptr }; +}; + +template +struct GdiSonar : SuperSonar +{ + LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept + { + switch (message) + { + case WM_CREATE: + SetLayeredWindowAttributes(this->m_hwnd, 0, 0, LWA_ALPHA); + break; + + case WM_TIMER: + switch (wParam) + { + case TIMER_ID_FADE: + OnFadeTimer(); + break; + } + break; + + case WM_PAINT: + this->Shim()->OnPaint(); + break; + } + return this->BaseWndProc(message, wParam, lParam); + } + + void BeforeMoveSonar() { this->Shim()->InvalidateSonar(); } + void AfterMoveSonar() { this->Shim()->InvalidateSonar(); } + + void SetSonarVisibility(bool visible) + { + m_alphaTarget = visible ? MaxAlpha : 0; + m_fadeStart = GetTickCount() - FadeFramePeriod; + SetTimer(this->m_hwnd, TIMER_ID_FADE, FadeFramePeriod, nullptr); + OnFadeTimer(); + } + + void OnFadeTimer() + { + auto now = GetTickCount(); + auto step = (int)((now - m_fadeStart) * MaxAlpha / this->FadeDuration); + + this->Shim()->InvalidateSonar(); + if (m_alpha < m_alphaTarget) + { + m_alpha += step; + if (m_alpha > m_alphaTarget) + m_alpha = m_alphaTarget; + } + else if (m_alpha > m_alphaTarget) + { + m_alpha -= step; + if (m_alpha < m_alphaTarget) + m_alpha = m_alphaTarget; + } + SetLayeredWindowAttributes(this->m_hwnd, 0, (BYTE)m_alpha, LWA_ALPHA); + this->Shim()->InvalidateSonar(); + if (m_alpha == m_alphaTarget) + { + KillTimer(this->m_hwnd, TIMER_ID_FADE); + if (m_alpha == 0) + { + ShowWindow(this->m_hwnd, SW_HIDE); + } + } + else + { + ShowWindow(this->m_hwnd, SW_SHOWNOACTIVATE); + } + } + +protected: + int CurrentSonarRadius() + { + int range = MaxAlpha - m_alpha; + int radius = this->SonarRadius + this->SonarRadius * range * (this->SonarZoomFactor - 1) / MaxAlpha; + return radius; + } + +private: + static constexpr DWORD FadeFramePeriod = 10; + static constexpr int MaxAlpha = SuperSonar::FinalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; + static constexpr DWORD TIMER_ID_FADE = 101; + +private: + int m_alpha = 0; + int m_alphaTarget = 0; + DWORD m_fadeStart = 0; +}; + +struct GdiSpotlight : GdiSonar +{ + void InvalidateSonar() + { + RECT rc; + auto radius = CurrentSonarRadius(); + rc.left = this->m_sonarPos.x - radius; + rc.top = this->m_sonarPos.y - radius; + rc.right = this->m_sonarPos.x + radius; + rc.bottom = this->m_sonarPos.y + radius; + InvalidateRect(this->m_hwnd, &rc, FALSE); + } + + void OnPaint() + { + PAINTSTRUCT ps; + BeginPaint(this->m_hwnd, &ps); + + auto radius = CurrentSonarRadius(); + auto spotlight = CreateRoundRectRgn( + this->m_sonarPos.x - radius, this->m_sonarPos.y - radius, this->m_sonarPos.x + radius, this->m_sonarPos.y + radius, radius * 2, radius * 2); + + FillRgn(ps.hdc, spotlight, (HBRUSH)GetStockObject(WHITE_BRUSH)); + Sleep(1000 / 60); + ExtSelectClipRgn(ps.hdc, spotlight, RGN_DIFF); + FillRect(ps.hdc, &ps.rcPaint, (HBRUSH)GetStockObject(BLACK_BRUSH)); + DeleteObject(spotlight); + + EndPaint(this->m_hwnd, &ps); + } +}; + +struct GdiCrosshairs : GdiSonar +{ + void InvalidateSonar() + { + RECT rc; + auto radius = CurrentSonarRadius(); + GetClientRect(m_hwnd, &rc); + rc.left = m_sonarPos.x - radius; + rc.right = m_sonarPos.x + radius; + InvalidateRect(m_hwnd, &rc, FALSE); + + GetClientRect(m_hwnd, &rc); + rc.top = m_sonarPos.y - radius; + rc.bottom = m_sonarPos.y + radius; + InvalidateRect(m_hwnd, &rc, FALSE); + } + + void OnPaint() + { + PAINTSTRUCT ps; + BeginPaint(this->m_hwnd, &ps); + + auto radius = CurrentSonarRadius(); + RECT rc; + + HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH); + + rc.left = m_sonarPos.x - radius; + rc.top = ps.rcPaint.top; + rc.right = m_sonarPos.x + radius; + rc.bottom = ps.rcPaint.bottom; + FillRect(ps.hdc, &rc, white); + + rc.left = ps.rcPaint.left; + rc.top = m_sonarPos.y - radius; + rc.right = ps.rcPaint.right; + rc.bottom = m_sonarPos.y + radius; + FillRect(ps.hdc, &rc, white); + + HBRUSH black = (HBRUSH)GetStockObject(BLACK_BRUSH); + + // Top left + rc.left = ps.rcPaint.left; + rc.top = ps.rcPaint.top; + rc.right = m_sonarPos.x - radius; + rc.bottom = m_sonarPos.y - radius; + FillRect(ps.hdc, &rc, black); + + // Top right + rc.left = m_sonarPos.x + radius; + rc.top = ps.rcPaint.top; + rc.right = ps.rcPaint.right; + rc.bottom = m_sonarPos.y - radius; + FillRect(ps.hdc, &rc, black); + + // Bottom left + rc.left = ps.rcPaint.left; + rc.top = m_sonarPos.y + radius; + rc.right = m_sonarPos.x - radius; + rc.bottom = ps.rcPaint.bottom; + FillRect(ps.hdc, &rc, black); + + // Bottom right + rc.left = m_sonarPos.x + radius; + rc.top = m_sonarPos.y + radius; + rc.right = ps.rcPaint.right; + rc.bottom = ps.rcPaint.bottom; + FillRect(ps.hdc, &rc, black); + + EndPaint(this->m_hwnd, &ps); + } +}; + +#pragma endregion Super_Sonar_Base_Code + + +#pragma region Super_Sonar_API + +CompositionSpotlight* m_sonar = nullptr; + +void FindMyMouseDisable() +{ + if (m_sonar != nullptr) + { + Logger::info("Terminating a sonar instance."); + m_sonar->Terminate(); + } +} + +bool FindMyMouseIsEnabled() +{ + return (m_sonar != nullptr); +} + +// Based on SuperSonar's original wWinMain. +int FindMyMouseMain(HINSTANCE hinst) +{ + Logger::info("Starting a sonar instance."); + if (m_sonar != nullptr) + { + Logger::error("A sonar instance was still working when trying to start a new one."); + return 0; + } + + CompositionSpotlight sonar; + if (!sonar.Initialize(hinst)) + { + Logger::error("Couldn't initialize a sonar instance."); + return 0; + } + m_sonar = &sonar; + Logger::info("Initialized the sonar instance."); + + MSG msg; + + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + Logger::info("Sonar message loop ended."); + m_sonar = nullptr; + + return (int)msg.wParam; +} + +#pragma endregion Super_Sonar_API diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h new file mode 100644 index 0000000000..feaa5d121b --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h @@ -0,0 +1,6 @@ +#pragma once +#include "pch.h" +int FindMyMouseMain(HINSTANCE hinst); +void FindMyMouseDisable(); +bool FindMyMouseIsEnabled(); + diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj new file mode 100644 index 0000000000..575955bd08 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj @@ -0,0 +1,146 @@ + + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {e94fd11c-0591-456f-899f-efc0ca548336} + Win32Proj + FindMyMouse + true + 10.0.18362.0 + FindMyMouse + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\ + + + + Level3 + Disabled + true + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + stdcpplatest + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + stdcpplatest + + + Windows + true + true + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + $(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories) + + + + + Use + pch.h + + + + + + + + + + + + + + Create + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj.filters b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj.filters new file mode 100644 index 0000000000..f2c5464f75 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {875a08c6-f610-4667-bd0f-80171ed96072} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Generated Files + + + + + + Resource Files + + + Resource Files + + + + + Generated Files + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp new file mode 100644 index 0000000000..26ebbb8994 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp @@ -0,0 +1,148 @@ +#include "pch.h" +#include +#include +#include "trace.h" +#include "FindMyMouse.h" +#include +#include + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +HMODULE m_hModule; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + m_hModule = hModule; + 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; +} + +// The PowerToy name that will be shown in the settings. +const static wchar_t* MODULE_NAME = L"FindMyMouse"; +// Add a description that will we shown in the module settings page. +const static wchar_t* MODULE_DESC = L"Focus the mouse pointer"; + +// Implement the PowerToy Module Interface and all the required methods. +class FindMyMouse : public PowertoyModuleIface +{ +private: + // The PowerToy state. + bool m_enabled = false; + + // Load initial settings from the persisted values. + void init_settings(); + +public: + // Constructor + FindMyMouse() + { + LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::findMyMouseLoggerName); + init_settings(); + }; + + // Destroy the powertoy and free memory + virtual void destroy() override + { + delete this; + } + + // Return the localized display name of the powertoy + virtual const wchar_t* get_name() override + { + return MODULE_NAME; + } + + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return MODULE_NAME; + } + + // Return JSON with the configuration options. + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + + // Create a Settings object. + PowerToysSettings::Settings settings(hinstance, get_name()); + settings.set_description(MODULE_DESC); + + return settings.serialize_to_buffer(buffer, buffer_size); + } + + // Signal from the Settings editor to call a custom action. + // This can be used to spawn more complex editors. + virtual void call_custom_action(const wchar_t* action) override + { + } + + // Called by the runner to pass the updated settings values as a serialized JSON. + virtual void set_config(const wchar_t* config) override + { + try + { + // Parse the input JSON string. + PowerToysSettings::PowerToyValues values = + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); + + values.save_to_settings_file(); + } + catch (std::exception&) + { + // Improper JSON. + } + } + + // Enable the powertoy + virtual void enable() + { + m_enabled = true; + Trace::EnableFindMyMouse(true); + std::thread([]() { FindMyMouseMain(m_hModule); }).detach(); + } + + // Disable the powertoy + virtual void disable() + { + m_enabled = false; + Trace::EnableFindMyMouse(false); + FindMyMouseDisable(); + } + + // Returns if the powertoys is enabled + virtual bool is_enabled() override + { + return m_enabled; + } +}; + +// Load the settings file. +void FindMyMouse::init_settings() +{ + try + { + // Load and parse the settings file for this PowerToy. + PowerToysSettings::PowerToyValues settings = + PowerToysSettings::PowerToyValues::load_from_settings_file(FindMyMouse::get_key()); + } + catch (std::exception&) + { + // Error while loading from the settings file. Let default values stay as they are. + } +} + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new FindMyMouse(); +} \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/packages.config b/src/modules/MouseUtils/FindMyMouse/packages.config new file mode 100644 index 0000000000..81f107b8bc --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/pch.cpp b/src/modules/MouseUtils/FindMyMouse/pch.cpp new file mode 100644 index 0000000000..1d9f38c57d --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/MouseUtils/FindMyMouse/pch.h b/src/modules/MouseUtils/FindMyMouse/pch.h new file mode 100644 index 0000000000..6dbc256004 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/pch.h @@ -0,0 +1,20 @@ +#pragma once + +#define COMPOSITION +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include + +#ifdef COMPOSITION +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include diff --git a/src/modules/MouseUtils/FindMyMouse/resource.base.h b/src/modules/MouseUtils/FindMyMouse/resource.base.h new file mode 100644 index 0000000000..83354c2884 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/resource.base.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by FindMyMouse.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys FindMyMouse" +#define INTERNAL_NAME "FindMyMouse" +#define ORIGINAL_FILENAME "FindMyMouse.dll" +#define IDS_KEYBOARDMANAGER_ICON 1001 + +// Non-localizable +////////////////////////////// diff --git a/src/modules/MouseUtils/FindMyMouse/trace.cpp b/src/modules/MouseUtils/FindMyMouse/trace.cpp new file mode 100644 index 0000000000..a5cfe02417 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/trace.cpp @@ -0,0 +1,40 @@ +#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() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +// Log if the user has FindMyMouse enabled or disabled +void Trace::EnableFindMyMouse(const bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FindMyMouse_EnableFindMyMouse", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +// Log that the user activated the module by focusing the mouse pointer +void Trace::MousePointerFocused() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FindMyMouse_MousePointerFocused", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/MouseUtils/FindMyMouse/trace.h b/src/modules/MouseUtils/FindMyMouse/trace.h new file mode 100644 index 0000000000..623ce60176 --- /dev/null +++ b/src/modules/MouseUtils/FindMyMouse/trace.h @@ -0,0 +1,14 @@ +#pragma once + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + + // Log if the user has FindMyMouse enabled or disabled + static void EnableFindMyMouse(const bool enabled) noexcept; + + // Log that the user activated the module by focusing the mouse pointer + static void MousePointerFocused() noexcept; +}; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 0a2941ec56..7743ae5818 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -147,7 +147,8 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"modules/PowerRename/PowerRenameExt.dll", L"modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.dll", L"modules/ColorPicker/ColorPicker.dll", - L"modules/Awake/AwakeModuleInterface.dll" + L"modules/Awake/AwakeModuleInterface.dll", + L"modules/MouseUtils/FindMyMouse.dll" }; const auto VCM_PATH = L"modules/VideoConference/VideoConferenceModule.dll"; diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 9d91bbb020..cb009dfef6 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -523,6 +523,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value) return "ImageResizer"; case ESettingsWindowNames::KBM: return "KBM"; + case ESettingsWindowNames::MouseUtils: + return "MouseUtils"; case ESettingsWindowNames::PowerRename: return "PowerRename"; case ESettingsWindowNames::FileExplorer: @@ -570,6 +572,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value) { return ESettingsWindowNames::KBM; } + else if (value == "MouseUtils") + { + return ESettingsWindowNames::MouseUtils; + } else if (value == "PowerRename") { return ESettingsWindowNames::PowerRename; diff --git a/src/runner/settings_window.h b/src/runner/settings_window.h index 44614b0a01..feeaa7abb3 100644 --- a/src/runner/settings_window.h +++ b/src/runner/settings_window.h @@ -11,6 +11,7 @@ enum class ESettingsWindowNames Run, ImageResizer, KBM, + MouseUtils, PowerRename, FileExplorer, ShortcutGuide, diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs index 227ebb9488..0232ff641b 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs @@ -175,6 +175,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool findMyMouse = true; + + [JsonPropertyName("FindMyMouse")] + public bool FindMyMouse + { + get => findMyMouse; + set + { + if (findMyMouse != value) + { + LogTelemetryEvent(value); + findMyMouse = value; + } + } + } + public string ToJsonString() { return JsonSerializer.Serialize(this); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs new file mode 100644 index 0000000000..6aeadef00d --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Library.Helpers; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels +{ + public class MouseUtilsViewModel : Observable + { + private GeneralSettings GeneralSettingsConfig { get; set; } + + public MouseUtilsViewModel(ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc) + { + // To obtain the general settings configurations of PowerToys Settings. + if (settingsRepository == null) + { + throw new ArgumentNullException(nameof(settingsRepository)); + } + + GeneralSettingsConfig = settingsRepository.SettingsConfig; + + _isFindMyMouseEnabled = GeneralSettingsConfig.Enabled.FindMyMouse; + + // set the callback functions value to hangle outgoing IPC message. + SendConfigMSG = ipcMSGCallBackFunc; + } + + public bool IsFindMyMouseEnabled + { + get => _isFindMyMouseEnabled; + set + { + if (_isFindMyMouseEnabled != value) + { + _isFindMyMouseEnabled = value; + + GeneralSettingsConfig.Enabled.FindMyMouse = value; + OnPropertyChanged(nameof(IsFindMyMouseEnabled)); + + OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); + SendConfigMSG(outgoing.ToString()); + + // TODO: Implement when this module has properties. + // NotifyPropertyChanged(); + } + } + } + + private Func SendConfigMSG { get; } + + private bool _isFindMyMouseEnabled; + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseUtils.png b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseUtils.png new file mode 100644 index 0000000000..26f935bea3 Binary files /dev/null and b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseUtils.png differ diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/MouseUtils.png b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/MouseUtils.png new file mode 100644 index 0000000000..96a58da259 Binary files /dev/null and b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/MouseUtils.png differ diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/OOBE/MouseUtils.gif b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/OOBE/MouseUtils.gif new file mode 100644 index 0000000000..8f4a97c0a2 Binary files /dev/null and b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/Modules/OOBE/MouseUtils.gif differ diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 32924752b5..a6c6bc44aa 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -155,6 +155,9 @@ OobeKBM.xaml + + OobeMouseUtils.xaml + OobeOverview.xaml @@ -192,6 +195,9 @@ KeyboardManagerPage.xaml + + MouseUtilsPage.xaml + PowerLauncherPage.xaml @@ -227,6 +233,7 @@ + @@ -239,12 +246,14 @@ + + @@ -365,6 +374,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -429,6 +442,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Enums/PowerToysModulesEnum.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Enums/PowerToysModulesEnum.cs index 70c65c44bf..6464b97e41 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Enums/PowerToysModulesEnum.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Enums/PowerToysModulesEnum.cs @@ -13,6 +13,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums FileExplorer, ImageResizer, KBM, + MouseUtils, PowerRename, Run, ShortcutGuide, diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml new file mode 100644 index 0000000000..6fe25099ce --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + +