Mouse Utils - Mouse Pointer Crosshair (#15633)

* Apply PowerToys template

* Add CppWinRt package

* Add Settings reference

* Use proper output dir

* Proper WindowsTargetPlatformVersion

* Add filters to vcxproj

* Proper resource file generation

* Remove boilerplate code

* Initial implementation of the mouse crosshair

* Add enable module to settings page

* Change hotkey in settings

* Add color, opacity, thickness settings

* Add telemetry

* Add Oobe entry

* Add installer instructions

* Add dll to pipelines

* Add crosshair borders

* Fix settings case

* Tweak defaults to make it look like the specs

* fix spellchecker

* Fix resources and binary info

* Correct composition tree comment typo

* Reduce argument copy

* Start disabled by default

* Add maximum to crosshair thickness and border size

* Set minimum border size of 0

* Fix comment

* Add maximum to radius

* Add comment for non-localizable strings

* Rename "Inclusive Mouse"-"Mouse Pointer Crosshair"
This commit is contained in:
Jaime Bernardo 2022-01-24 13:29:16 +00:00 committed by GitHub
parent 453bb613af
commit 2e8dfa73d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1622 additions and 4 deletions

View File

@ -95,6 +95,7 @@
"modules\\MouseUtils\\PowerToys.FindMyMouse.dll",
"modules\\MouseUtils\\PowerToys.MouseHighlighter.dll",
"modules\\MouseUtils\\PowerToys.MousePointerCrosshair.dll",
"modules\\PowerRename\\PowerToys.PowerRenameExt.dll",
"modules\\PowerRename\\PowerToys.PowerRenameUILib.dll",

View File

@ -388,6 +388,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlwaysOnTopModuleInterface"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.WebSearch", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.WebSearch\Community.PowerToys.Run.Plugin.WebSearch.csproj", "{9F94B303-5E21-4364-9362-64426F8DB932}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MousePointerCrosshair", "src\modules\MouseUtils\MousePointerCrosshair\MousePointerCrosshair.vcxproj", "{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -1045,6 +1047,12 @@ Global
{9F94B303-5E21-4364-9362-64426F8DB932}.Release|x64.ActiveCfg = Release|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Release|x64.Build.0 = Release|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Release|x86.ActiveCfg = Release|x64
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|x64.ActiveCfg = Debug|x64
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|x64.Build.0 = Debug|x64
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|x86.ActiveCfg = Debug|x64
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x64.ActiveCfg = Release|x64
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x64.Build.0 = Release|x64
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x86.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1172,6 +1180,7 @@ Global
{1DC3BE92-CE89-43FB-8110-9C043A2FE7A2} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE}
{48A0A19E-A0BE-4256-ACF8-CC3B80291AF9} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE}
{9F94B303-5E21-4364-9362-64426F8DB932} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E} = {322566EF-20DC-43A6-B9F8-616AF942579A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@ -707,6 +707,9 @@
<Component Id="Module_MouseHighlighter" Guid="3BAEA39F-A73D-48D2-9616-BBED5B8C86D3" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.MouseUtilsProjectName)\PowerToys.MouseHighlighter.dll" KeyPath="yes" />
</Component>
<Component Id="Module_MousePointerCrosshair" Guid="157F664C-E993-49AC-B9E1-EBF73080D072" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.MouseUtilsProjectName)\PowerToys.MousePointerCrosshair.dll" KeyPath="yes" />
</Component>
</DirectoryRef>
<!-- Shortcut guide -->
@ -1045,6 +1048,7 @@
<ComponentRef Id="Module_Awake_runtime_netcoreapp21"/>
<ComponentRef Id="Module_FindMyMouse"/>
<ComponentRef Id="Module_MouseHighlighter"/>
<ComponentRef Id="Module_MousePointerCrosshair" />
<ComponentRef Id="Module_AlwaysOnTop"/>
<ComponentRef Id="SettingsV2" />
<ComponentRef Id="SettingsV2Assets" />

View File

@ -27,6 +27,7 @@ struct LogSettings
inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt";
inline const static std::string findMyMouseLoggerName = "find-my-mouse";
inline const static std::string mouseHighlighterLoggerName = "mouse-highlighter";
inline const static std::string mousePointerCrosshairLoggerName = "mouse-pointer-crosshair";
inline const static std::string powerRenameLoggerName = "powerrename";
inline const static std::string alwaysOnTopLoggerName = "always-on-top";
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt";

View File

@ -0,0 +1,452 @@
// InclusiveCrosshair.cpp : Defines the entry point for the application.
//
#include "pch.h"
#include "InclusiveCrosshair.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
struct InclusiveCrosshair
{
bool MyRegisterClass(HINSTANCE hInstance);
static InclusiveCrosshair* instance;
void Terminate();
void SwitchActivationMode();
void ApplySettings(InclusiveCrosshairSettings& settings, bool applyToRuntimeObjects);
private:
enum class MouseButton
{
Left,
Right
};
void DestroyInclusiveCrosshair();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
void StartDrawing();
void StopDrawing();
bool CreateInclusiveCrosshair();
void UpdateCrosshairPosition();
HHOOK m_mouseHook = NULL;
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
static constexpr auto m_className = L"MousePointerCrosshair";
static constexpr auto m_windowTitle = L"PowerToys Mouse Pointer Crosshair";
HWND m_hwndOwner = NULL;
HWND m_hwnd = NULL;
HINSTANCE m_hinstance = NULL;
static constexpr DWORD WM_SWITCH_ACTIVATION_MODE = WM_APP;
winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr };
winrt::Compositor m_compositor{ nullptr };
winrt::Desktop::DesktopWindowTarget m_target{ nullptr };
winrt::ContainerVisual m_root{ nullptr };
winrt::LayerVisual m_crosshair_border_layer{ nullptr };
winrt::LayerVisual m_crosshair_layer{ nullptr };
winrt::SpriteVisual m_left_crosshair_border{ nullptr };
winrt::SpriteVisual m_left_crosshair{ nullptr };
winrt::SpriteVisual m_right_crosshair_border{ nullptr };
winrt::SpriteVisual m_right_crosshair{ nullptr };
winrt::SpriteVisual m_top_crosshair_border{ nullptr };
winrt::SpriteVisual m_top_crosshair{ nullptr };
winrt::SpriteVisual m_bottom_crosshair_border{ nullptr };
winrt::SpriteVisual m_bottom_crosshair{ nullptr };
bool m_visible = false;
bool m_destroyed = false;
// Configurable Settings
winrt::Windows::UI::Color m_crosshair_border_color = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_BORDER_COLOR;
winrt::Windows::UI::Color m_crosshair_color = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_COLOR;
float m_crosshair_radius = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_RADIUS;
float m_crosshair_thickness = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_THICKNESS;
float m_crosshair_border_size = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_BORDER_SIZE;
float m_crosshair_opacity = max(0.f, min(1.f, (float)INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_OPACITY / 100.0f));
};
InclusiveCrosshair* InclusiveCrosshair::instance = nullptr;
bool InclusiveCrosshair::CreateInclusiveCrosshair()
{
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<ABI::ICompositorDesktopInterop>()->CreateDesktopWindowTarget(m_hwnd, false, &target));
*winrt::put_abi(m_target) = target;
// Our composition tree:
//
// [root] ContainerVisual
// \ [crosshair border layer] LayerVisual
// \ [crosshair border sprites]
// [crosshair layer] LayerVisual
// \ [crosshair sprites]
m_root = m_compositor.CreateContainerVisual();
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_target.Root(m_root);
m_root.Opacity(m_crosshair_opacity);
m_crosshair_border_layer = m_compositor.CreateLayerVisual();
m_crosshair_border_layer.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_root.Children().InsertAtTop(m_crosshair_border_layer);
m_crosshair_border_layer.Opacity(1.0f);
m_crosshair_layer = m_compositor.CreateLayerVisual();
m_crosshair_layer.RelativeSizeAdjustment({ 1.0f, 1.0f });
// Create the crosshair sprites.
m_left_crosshair_border = m_compositor.CreateSpriteVisual();
m_left_crosshair_border.AnchorPoint({ 1.0f, 0.5f });
m_left_crosshair_border.Brush(m_compositor.CreateColorBrush(m_crosshair_border_color));
m_crosshair_border_layer.Children().InsertAtTop(m_left_crosshair_border);
m_left_crosshair = m_compositor.CreateSpriteVisual();
m_left_crosshair.AnchorPoint({ 1.0f, 0.5f });
m_left_crosshair.Brush(m_compositor.CreateColorBrush(m_crosshair_color));
m_crosshair_layer.Children().InsertAtTop(m_left_crosshair);
m_right_crosshair_border = m_compositor.CreateSpriteVisual();
m_right_crosshair_border.AnchorPoint({ 0.0f, 0.5f });
m_right_crosshair_border.Brush(m_compositor.CreateColorBrush(m_crosshair_border_color));
m_crosshair_border_layer.Children().InsertAtTop(m_right_crosshair_border);
m_right_crosshair = m_compositor.CreateSpriteVisual();
m_right_crosshair.AnchorPoint({ 0.0f, 0.5f });
m_right_crosshair.Brush(m_compositor.CreateColorBrush(m_crosshair_color));
m_crosshair_layer.Children().InsertAtTop(m_right_crosshair);
m_top_crosshair_border = m_compositor.CreateSpriteVisual();
m_top_crosshair_border.AnchorPoint({ 0.5f, 1.0f });
m_top_crosshair_border.Brush(m_compositor.CreateColorBrush(m_crosshair_border_color));
m_crosshair_border_layer.Children().InsertAtTop(m_top_crosshair_border);
m_top_crosshair = m_compositor.CreateSpriteVisual();
m_top_crosshair.AnchorPoint({ 0.5f, 1.0f });
m_top_crosshair.Brush(m_compositor.CreateColorBrush(m_crosshair_color));
m_crosshair_layer.Children().InsertAtTop(m_top_crosshair);
m_bottom_crosshair_border = m_compositor.CreateSpriteVisual();
m_bottom_crosshair_border.AnchorPoint({ 0.5f, 0.0f });
m_bottom_crosshair_border.Brush(m_compositor.CreateColorBrush(m_crosshair_border_color));
m_crosshair_border_layer.Children().InsertAtTop(m_bottom_crosshair_border);
m_bottom_crosshair = m_compositor.CreateSpriteVisual();
m_bottom_crosshair.AnchorPoint({ 0.5f, 0.0f });
m_bottom_crosshair.Brush(m_compositor.CreateColorBrush(m_crosshair_color));
m_crosshair_layer.Children().InsertAtTop(m_bottom_crosshair);
m_crosshair_border_layer.Children().InsertAtTop(m_crosshair_layer);
m_crosshair_layer.Opacity(1.0f);
UpdateCrosshairPosition();
return true;
}
catch (...)
{
return false;
}
}
void InclusiveCrosshair::UpdateCrosshairPosition()
{
POINT ptCursor;
GetCursorPos(&ptCursor);
HMONITOR cursorMonitor = MonitorFromPoint(ptCursor, MONITOR_DEFAULTTONEAREST);
if (cursorMonitor == NULL)
{
return;
}
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfo(cursorMonitor, &monitorInfo))
{
return;
}
POINT ptMonitorUpperLeft;
ptMonitorUpperLeft.x = monitorInfo.rcMonitor.left;
ptMonitorUpperLeft.y = monitorInfo.rcMonitor.top;
POINT ptMonitorBottomRight;
ptMonitorBottomRight.x = monitorInfo.rcMonitor.right;
ptMonitorBottomRight.y = monitorInfo.rcMonitor.bottom;
// Convert everything to client coordinates.
ScreenToClient(m_hwnd, &ptCursor);
ScreenToClient(m_hwnd, &ptMonitorUpperLeft);
ScreenToClient(m_hwnd, &ptMonitorBottomRight);
// Position crosshair components around the mouse pointer.
m_left_crosshair_border.Offset({ (float)ptCursor.x - m_crosshair_radius + m_crosshair_border_size, (float)ptCursor.y, .0f });
m_left_crosshair_border.Size({ (float)ptCursor.x - (float)ptMonitorUpperLeft.x - m_crosshair_radius + m_crosshair_border_size, m_crosshair_thickness + m_crosshair_border_size * 2 });
m_left_crosshair.Offset({ (float)ptCursor.x - m_crosshair_radius, (float)ptCursor.y, .0f });
m_left_crosshair.Size({ (float)ptCursor.x - (float)ptMonitorUpperLeft.x - m_crosshair_radius, m_crosshair_thickness });
m_right_crosshair_border.Offset({ (float)ptCursor.x + m_crosshair_radius - m_crosshair_border_size, (float)ptCursor.y, .0f });
m_right_crosshair_border.Size({ (float)ptMonitorBottomRight.x - (float)ptCursor.x - m_crosshair_radius + m_crosshair_border_size, m_crosshair_thickness + m_crosshair_border_size * 2 });
m_right_crosshair.Offset({ (float)ptCursor.x + m_crosshair_radius, (float)ptCursor.y, .0f });
m_right_crosshair.Size({ (float)ptMonitorBottomRight.x - (float)ptCursor.x - m_crosshair_radius, m_crosshair_thickness });
m_top_crosshair_border.Offset({ (float)ptCursor.x, (float)ptCursor.y - m_crosshair_radius + m_crosshair_border_size, .0f });
m_top_crosshair_border.Size({ m_crosshair_thickness + m_crosshair_border_size * 2, (float)ptCursor.y - (float)ptMonitorUpperLeft.y - m_crosshair_radius + m_crosshair_border_size });
m_top_crosshair.Offset({ (float)ptCursor.x, (float)ptCursor.y - m_crosshair_radius, .0f });
m_top_crosshair.Size({ m_crosshair_thickness, (float)ptCursor.y - (float)ptMonitorUpperLeft.y - m_crosshair_radius });
m_bottom_crosshair_border.Offset({ (float)ptCursor.x, (float)ptCursor.y + m_crosshair_radius - m_crosshair_border_size, .0f });
m_bottom_crosshair_border.Size({ m_crosshair_thickness + m_crosshair_border_size * 2, (float)ptMonitorBottomRight.y - (float)ptCursor.y - m_crosshair_radius + m_crosshair_border_size });
m_bottom_crosshair.Offset({ (float)ptCursor.x, (float)ptCursor.y + m_crosshair_radius, .0f });
m_bottom_crosshair.Size({ m_crosshair_thickness, (float)ptMonitorBottomRight.y - (float)ptCursor.y - m_crosshair_radius });
}
LRESULT CALLBACK InclusiveCrosshair::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept
{
if (nCode >= 0)
{
MSLLHOOKSTRUCT* hookData = (MSLLHOOKSTRUCT*)lParam;
if (wParam == WM_MOUSEMOVE) {
instance->UpdateCrosshairPosition();
}
}
return CallNextHookEx(0, nCode, wParam, lParam);
}
void InclusiveCrosshair::StartDrawing()
{
Logger::info("Start drawing crosshairs.");
Trace::StartDrawingCrosshair();
m_visible = true;
SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0);
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, m_hinstance, 0);
UpdateCrosshairPosition();
}
void InclusiveCrosshair::StopDrawing()
{
Logger::info("Stop drawing crosshairs.");
m_visible = false;
ShowWindow(m_hwnd, SW_HIDE);
UnhookWindowsHookEx(m_mouseHook);
m_mouseHook = NULL;
}
void InclusiveCrosshair::SwitchActivationMode()
{
PostMessage(m_hwnd, WM_SWITCH_ACTIVATION_MODE, 0, 0);
}
void InclusiveCrosshair::ApplySettings(InclusiveCrosshairSettings& settings, bool applyToRunTimeObjects)
{
m_crosshair_radius = (float)settings.crosshairRadius;
m_crosshair_thickness = (float)settings.crosshairThickness;
m_crosshair_color = settings.crosshairColor;
m_crosshair_opacity = max(0.f, min(1.f, (float)settings.crosshairOpacity / 100.0f));
m_crosshair_border_color = settings.crosshairBorderColor;
m_crosshair_border_size = (float)settings.crosshairBorderSize;
if (applyToRunTimeObjects)
{
// Runtime objects already created. Should update in the owner thread.
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
InclusiveCrosshairSettings localSettings = settings;
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
if (!m_destroyed)
{
// Apply new settings to runtime composition objects.
m_left_crosshair.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_color);
m_right_crosshair.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_color);
m_top_crosshair.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_color);
m_bottom_crosshair.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_color);
m_left_crosshair_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_border_color);
m_right_crosshair_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_border_color);
m_top_crosshair_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_border_color);
m_bottom_crosshair_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshair_border_color);
m_root.Opacity(m_crosshair_opacity);
UpdateCrosshairPosition();
}
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to update the crosshair settings.");
}
}
}
void InclusiveCrosshair::DestroyInclusiveCrosshair()
{
StopDrawing();
PostQuitMessage(0);
}
LRESULT CALLBACK InclusiveCrosshair::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept
{
switch (message)
{
case WM_NCCREATE:
instance->m_hwnd = hWnd;
return DefWindowProc(hWnd, message, wParam, lParam);
case WM_CREATE:
return instance->CreateInclusiveCrosshair() ? 0 : -1;
case WM_NCHITTEST:
return HTTRANSPARENT;
case WM_SWITCH_ACTIVATION_MODE:
if (instance->m_visible)
{
instance->StopDrawing();
}
else
{
instance->StartDrawing();
}
break;
case WM_DESTROY:
instance->DestroyInclusiveCrosshair();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
bool InclusiveCrosshair::MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASS wc{};
m_hinstance = hInstance;
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (!GetClassInfoW(hInstance, m_className, &wc))
{
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszClassName = m_className;
if (!RegisterClassW(&wc))
{
return false;
}
}
m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hInstance, nullptr);
DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW;
return CreateWindowExW(exStyle, m_className, m_windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hInstance, nullptr) != nullptr;
}
void InclusiveCrosshair::Terminate()
{
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
m_destroyed = true;
DestroyWindow(m_hwndOwner);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to destroy the window.");
}
}
#pragma region InclusiveCrosshair_API
void InclusiveCrosshairApplySettings(InclusiveCrosshairSettings& settings)
{
if (InclusiveCrosshair::instance != nullptr)
{
Logger::info("Applying settings.");
InclusiveCrosshair::instance->ApplySettings(settings, true);
}
}
void InclusiveCrosshairSwitch()
{
if (InclusiveCrosshair::instance != nullptr)
{
Logger::info("Switching activation mode.");
InclusiveCrosshair::instance->SwitchActivationMode();
}
}
void InclusiveCrosshairDisable()
{
if (InclusiveCrosshair::instance != nullptr)
{
Logger::info("Terminating the crosshair instance.");
InclusiveCrosshair::instance->Terminate();
}
}
bool InclusiveCrosshairIsEnabled()
{
return (InclusiveCrosshair::instance != nullptr);
}
int InclusiveCrosshairMain(HINSTANCE hInstance, InclusiveCrosshairSettings& settings)
{
Logger::info("Starting a crosshair instance.");
if (InclusiveCrosshair::instance != nullptr)
{
Logger::error("A crosshair instance was still working when trying to start a new one.");
return 0;
}
// Perform application initialization:
InclusiveCrosshair crosshair;
InclusiveCrosshair::instance = &crosshair;
crosshair.ApplySettings(settings, false);
if (!crosshair.MyRegisterClass(hInstance))
{
Logger::error("Couldn't initialize a crosshair instance.");
InclusiveCrosshair::instance = nullptr;
return FALSE;
}
Logger::info("Initialized the crosshair instance.");
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Logger::info("Crosshair message loop ended.");
InclusiveCrosshair::instance = nullptr;
return (int)msg.wParam;
}
#pragma endregion InclusiveCrosshair_API

View File

@ -0,0 +1,25 @@
#pragma once
#include "pch.h"
constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_OPACITY = 75;
const winrt::Windows::UI::Color INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 0, 0);
const winrt::Windows::UI::Color INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_BORDER_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255);
constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_RADIUS = 20;
constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_THICKNESS = 5;
constexpr int INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_BORDER_SIZE = 1;
struct InclusiveCrosshairSettings
{
winrt::Windows::UI::Color crosshairColor = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_COLOR;
winrt::Windows::UI::Color crosshairBorderColor = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_BORDER_COLOR;
int crosshairRadius = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_RADIUS;
int crosshairThickness = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_THICKNESS;
int crosshairOpacity = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_OPACITY;
int crosshairBorderSize = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIR_BORDER_SIZE;
};
int InclusiveCrosshairMain(HINSTANCE hinst, InclusiveCrosshairSettings& settings);
void InclusiveCrosshairDisable();
bool InclusiveCrosshairIsEnabled();
void InclusiveCrosshairSwitch();
void InclusiveCrosshairApplySettings(InclusiveCrosshairSettings& settings);

View File

@ -0,0 +1,40 @@
#include <windows.h>
#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

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" />
<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>{eae14c0e-7a6b-45da-9080-a7d8c077ba6e}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>MousePointerCrosshair</RootNamespace>
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
<ProjectName>MousePointerCrosshair</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\deps\spdlog.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)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\</OutDir>
<TargetName>PowerToys.MousePointerCrosshair</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\</OutDir>
<TargetName>PowerToys.MousePointerCrosshair</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<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>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(CIBuild)'!='true'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="InclusiveCrosshair.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="InclusiveCrosshair.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="MousePointerCrosshair.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SetttingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="InclusiveCrosshair.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Resource Files</Filter>
</ClInclude>
<ClInclude Include="InclusiveCrosshair.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Filter Include="Generated Files">
<UniqueIdentifier>{890924c4-f592-4e84-b140-4b07a607a224}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{a9a2fd9b-de66-43dc-99b2-56f0d1ddecda}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{b5d0d62e-0275-439b-a910-e7c5befad045}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{5e350f4d-b07a-4bb2-8e63-b2c527358234}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="MousePointerCrosshair.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,312 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include "trace.h"
#include "InclusiveCrosshair.h"
#include "common/utils/color.h"
// Non-Localizable strings
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
const wchar_t JSON_KEY_CROSSHAIR_COLOR[] = L"crosshair_color";
const wchar_t JSON_KEY_CROSSHAIR_OPACITY[] = L"crosshair_opacity";
const wchar_t JSON_KEY_CROSSHAIR_RADIUS[] = L"crosshair_radius";
const wchar_t JSON_KEY_CROSSHAIR_THICKNESS[] = L"crosshair_thickness";
const wchar_t JSON_KEY_CROSSHAIR_BORDER_COLOR[] = L"crosshair_border_color";
const wchar_t JSON_KEY_CROSSHAIR_BORDER_SIZE[] = L"crosshair_border_size";
}
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"MousePointerCrosshair";
// Add a description that will we shown in the module settings page.
const static wchar_t* MODULE_DESC = L"<no description>";
// Implement the PowerToy Module Interface and all the required methods.
class MousePointerCrosshair : public PowertoyModuleIface
{
private:
// The PowerToy state.
bool m_enabled = false;
// Hotkey to invoke the module
HotkeyEx m_hotkey;
// Mouse Pointer Crosshair specific settings
InclusiveCrosshairSettings m_inclusiveCrosshairSettings;
public:
// Constructor
MousePointerCrosshair()
{
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mousePointerCrosshairLoggerName);
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<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinstance, get_name());
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());
parse_settings(values);
InclusiveCrosshairApplySettings(m_inclusiveCrosshairSettings);
}
catch (std::exception&)
{
Logger::error("Invalid json when trying to parse Mouse Pointer Crosshair settings json.");
}
}
// Enable the powertoy
virtual void enable()
{
m_enabled = true;
Trace::EnableMousePointerCrosshair(true);
std::thread([=]() { InclusiveCrosshairMain(m_hModule, m_inclusiveCrosshairSettings); }).detach();
}
// Disable the powertoy
virtual void disable()
{
m_enabled = false;
Trace::EnableMousePointerCrosshair(false);
InclusiveCrosshairDisable();
}
// Returns if the powertoys is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Returns whether the PowerToys should be enabled by default
virtual bool is_enabled_by_default() const override
{
return false;
}
virtual std::optional<HotkeyEx> GetHotkeyEx() override
{
return m_hotkey;
}
virtual void OnHotkeyEx() override
{
InclusiveCrosshairSwitch();
}
// Load the settings file.
void init_settings()
{
try
{
// Load and parse the settings file for this PowerToy.
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(MousePointerCrosshair::get_key());
parse_settings(settings);
}
catch (std::exception&)
{
Logger::error("Invalid json when trying to load the Mouse Pointer Crosshair settings json from file.");
}
}
void parse_settings(PowerToysSettings::PowerToyValues& settings)
{
// TODO: refactor to use common/utils/json.h instead
auto settingsObject = settings.get_raw_json();
InclusiveCrosshairSettings inclusiveCrosshairSettings;
if (settingsObject.GetView().Size())
{
try
{
// Parse HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_hotkey = HotkeyEx();
if (hotkey.win_pressed())
{
m_hotkey.modifiersMask |= MOD_WIN;
}
if (hotkey.ctrl_pressed())
{
m_hotkey.modifiersMask |= MOD_CONTROL;
}
if (hotkey.shift_pressed())
{
m_hotkey.modifiersMask |= MOD_SHIFT;
}
if (hotkey.alt_pressed())
{
m_hotkey.modifiersMask |= MOD_ALT;
}
m_hotkey.vkCode = hotkey.get_code();
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Pointer Crosshair activation shortcut");
}
try
{
// Parse Opacity
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIR_OPACITY);
inclusiveCrosshairSettings.crosshairOpacity = (uint8_t)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
}
catch (...)
{
Logger::warn("Failed to initialize Opacity from settings. Will use default value");
}
try
{
// Parse crosshair color
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIR_COLOR);
auto crosshairColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t r, g, b;
if (!checkValidRGB(crosshairColor, &r, &g, &b))
{
Logger::error("Crosshair color RGB value is invalid. Will use default value");
}
else
{
inclusiveCrosshairSettings.crosshairColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
}
}
catch (...)
{
Logger::warn("Failed to initialize crosshair color from settings. Will use default value");
}
try
{
// Parse Radius
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIR_RADIUS);
inclusiveCrosshairSettings.crosshairRadius = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
}
catch (...)
{
Logger::warn("Failed to initialize Radius from settings. Will use default value");
}
try
{
// Parse Thickness
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIR_THICKNESS);
inclusiveCrosshairSettings.crosshairThickness = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
}
catch (...)
{
Logger::warn("Failed to initialize Thickness from settings. Will use default value");
}
try
{
// Parse crosshair border color
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIR_BORDER_COLOR);
auto crosshairBorderColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
uint8_t r, g, b;
if (!checkValidRGB(crosshairBorderColor, &r, &g, &b))
{
Logger::error("Crosshair border color RGB value is invalid. Will use default value");
}
else
{
inclusiveCrosshairSettings.crosshairBorderColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
}
}
catch (...)
{
Logger::warn("Failed to initialize crosshair border color from settings. Will use default value");
}
try
{
// Parse border size
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIR_BORDER_SIZE);
inclusiveCrosshairSettings.crosshairBorderSize = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
}
catch (...)
{
Logger::warn("Failed to initialize border color from settings. Will use default value");
}
}
else
{
Logger::info("Mouse Pointer Crosshair settings are empty");
}
if (!m_hotkey.modifiersMask)
{
Logger::info("Mouse Pointer Crosshair is going to use default shortcut");
m_hotkey.modifiersMask = MOD_CONTROL | MOD_ALT;
m_hotkey.vkCode = 0x50; // P key
}
m_inclusiveCrosshairSettings = inclusiveCrosshairSettings;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new MousePointerCrosshair();
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>

View File

@ -0,0 +1 @@
#include "pch.h"

View File

@ -0,0 +1,16 @@
#pragma once
#define COMPOSITION
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windows.ui.composition.interop.h>
#include <DispatcherQueue.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <ProjectTelemetry.h>
#include <thread>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>

View File

@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by MousePointerCrosshair.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys MousePointerCrosshair"
#define INTERNAL_NAME "MousePointerCrosshair"
#define ORIGINAL_FILENAME "PowerToys.MousePointerCrosshair.dll"
// Non-localizable
//////////////////////////////

View File

@ -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 MousePointerCrosshair enabled or disabled
void Trace::EnableMousePointerCrosshair(const bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"MousePointerCrosshair_EnableMousePointerCrosshair",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
// Log that the user activated the module by having the crosshair be drawn
void Trace::StartDrawingCrosshair() noexcept
{
TraceLoggingWrite(
g_hProvider,
"MousePointerCrosshair_StartDrawingCrosshair",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@ -0,0 +1,14 @@
#pragma once
class Trace
{
public:
static void RegisterProvider() noexcept;
static void UnregisterProvider() noexcept;
// Log if the user has MousePointerCrosshair enabled or disabled
static void EnableMousePointerCrosshair(const bool enabled) noexcept;
// Log that the user activated the module by having the crosshair be drawn
static void StartDrawingCrosshair() noexcept;
};

View File

@ -151,7 +151,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"modules/MouseUtils/PowerToys.FindMyMouse.dll" ,
L"modules/MouseUtils/PowerToys.MouseHighlighter.dll",
L"modules/AlwaysOnTop/PowerToys.AlwaysOnTopModuleInterface.dll",
L"modules/MouseUtils/PowerToys.MousePointerCrosshair.dll",
};
const auto VCM_PATH = L"modules/VideoConference/PowerToys.VideoConferenceModule.dll";
if (const auto mf = LoadLibraryA("mf.dll"))

View File

@ -223,6 +223,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool mousePointerCrosshair = true;
[JsonPropertyName("MousePointerCrosshair")]
public bool MousePointerCrosshair
{
get => mousePointerCrosshair;
set
{
if (mousePointerCrosshair != value)
{
LogTelemetryEvent(value);
mousePointerCrosshair = value;
}
}
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);

View File

@ -0,0 +1,43 @@
// 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.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MousePointerCrosshairProperties
{
[JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; }
[JsonPropertyName("crosshair_color")]
public StringProperty CrosshairColor { get; set; }
[JsonPropertyName("crosshair_opacity")]
public IntProperty CrosshairOpacity { get; set; }
[JsonPropertyName("crosshair_radius")]
public IntProperty CrosshairRadius { get; set; }
[JsonPropertyName("crosshair_thickness")]
public IntProperty CrosshairThickness { get; set; }
[JsonPropertyName("crosshair_border_color")]
public StringProperty CrosshairBorderColor { get; set; }
[JsonPropertyName("crosshair_border_size")]
public IntProperty CrosshairBorderSize { get; set; }
public MousePointerCrosshairProperties()
{
ActivationShortcut = new HotkeySettings(false, true, true, false, 0x50); // Ctrl + Alt + P
CrosshairColor = new StringProperty("#FF0000");
CrosshairOpacity = new IntProperty(75);
CrosshairRadius = new IntProperty(20);
CrosshairThickness = new IntProperty(5);
CrosshairBorderColor = new StringProperty("#FFFFFF");
CrosshairBorderSize = new IntProperty(1);
}
}
}

View File

@ -0,0 +1,35 @@
// 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.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MousePointerCrosshairSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "MousePointerCrosshair";
[JsonPropertyName("properties")]
public MousePointerCrosshairProperties Properties { get; set; }
public MousePointerCrosshairSettings()
{
Name = ModuleName;
Properties = new MousePointerCrosshairProperties();
Version = "1.0";
}
public string GetModuleName()
{
return Name;
}
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
{
return false;
}
}
}

View File

@ -0,0 +1,29 @@
// 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.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MousePointerCrosshairSettingsIPCMessage
{
[JsonPropertyName("powertoys")]
public SndMousePointerCrosshairSettings Powertoys { get; set; }
public MousePointerCrosshairSettingsIPCMessage()
{
}
public MousePointerCrosshairSettingsIPCMessage(SndMousePointerCrosshairSettings settings)
{
this.Powertoys = settings;
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@ -0,0 +1,29 @@
// 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.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class SndMousePointerCrosshairSettings
{
[JsonPropertyName("MousePointerCrosshair")]
public MousePointerCrosshairSettings MousePointerCrosshair { get; set; }
public SndMousePointerCrosshairSettings()
{
}
public SndMousePointerCrosshairSettings(MousePointerCrosshairSettings settings)
{
MousePointerCrosshair = settings;
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@ -19,7 +19,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private MouseHighlighterSettings MouseHighlighterSettingsConfig { get; set; }
public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<FindMyMouseSettings> findMyMouseSettingsRepository, ISettingsRepository<MouseHighlighterSettings> mouseHighlighterSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
private MousePointerCrosshairSettings MousePointerCrosshairSettingsConfig { get; set; }
public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<FindMyMouseSettings> findMyMouseSettingsRepository, ISettingsRepository<MouseHighlighterSettings> mouseHighlighterSettingsRepository, ISettingsRepository<MousePointerCrosshairSettings> mousePointerCrosshairSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
{
SettingsUtils = settingsUtils;
@ -35,6 +37,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
_isMouseHighlighterEnabled = GeneralSettingsConfig.Enabled.MouseHighlighter;
_isMousePointerCrosshairEnabled = GeneralSettingsConfig.Enabled.MousePointerCrosshair;
// To obtain the find my mouse settings, if the file exists.
// If not, to create a file with the default settings and to return the default configurations.
if (findMyMouseSettingsRepository == null)
@ -73,6 +77,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
_highlightFadeDelayMs = MouseHighlighterSettingsConfig.Properties.HighlightFadeDelayMs.Value;
_highlightFadeDurationMs = MouseHighlighterSettingsConfig.Properties.HighlightFadeDurationMs.Value;
if (mousePointerCrosshairSettingsRepository == null)
{
throw new ArgumentNullException(nameof(mousePointerCrosshairSettingsRepository));
}
MousePointerCrosshairSettingsConfig = mousePointerCrosshairSettingsRepository.SettingsConfig;
string crosshairColor = MousePointerCrosshairSettingsConfig.Properties.CrosshairColor.Value;
_mousePointerCrosshairColor = !string.IsNullOrEmpty(crosshairColor) ? crosshairColor : "#FF0000";
string crosshairBorderColor = MousePointerCrosshairSettingsConfig.Properties.CrosshairBorderColor.Value;
_mousePointerCrosshairBorderColor = !string.IsNullOrEmpty(crosshairBorderColor) ? crosshairBorderColor : "#FFFFFF";
_mousePointerCrosshairOpacity = MousePointerCrosshairSettingsConfig.Properties.CrosshairOpacity.Value;
_mousePointerCrosshairRadius = MousePointerCrosshairSettingsConfig.Properties.CrosshairRadius.Value;
_mousePointerCrosshairThickness = MousePointerCrosshairSettingsConfig.Properties.CrosshairThickness.Value;
_mousePointerCrosshairBorderSize = MousePointerCrosshairSettingsConfig.Properties.CrosshairBorderSize.Value;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
@ -398,6 +420,169 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
SettingsUtils.SaveSettings(MouseHighlighterSettingsConfig.ToJsonString(), MouseHighlighterSettings.ModuleName);
}
public bool IsMousePointerCrosshairEnabled
{
get => _isMousePointerCrosshairEnabled;
set
{
if (_isMousePointerCrosshairEnabled != value)
{
_isMousePointerCrosshairEnabled = value;
GeneralSettingsConfig.Enabled.MousePointerCrosshair = value;
OnPropertyChanged(nameof(_isMousePointerCrosshairEnabled));
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public HotkeySettings MousePointerCrosshairActivationShortcut
{
get
{
return MousePointerCrosshairSettingsConfig.Properties.ActivationShortcut;
}
set
{
if (MousePointerCrosshairSettingsConfig.Properties.ActivationShortcut != value)
{
MousePointerCrosshairSettingsConfig.Properties.ActivationShortcut = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public string MousePointerCrosshairColor
{
get
{
return _mousePointerCrosshairColor;
}
set
{
// The fallback value is based on ToRGBHex's behavior, which returns
// #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
// This extra handling is added here to deal with FxCop warnings.
value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
if (!value.Equals(_mousePointerCrosshairColor, StringComparison.OrdinalIgnoreCase))
{
_mousePointerCrosshairColor = value;
MousePointerCrosshairSettingsConfig.Properties.CrosshairColor.Value = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public int MousePointerCrosshairOpacity
{
get
{
return _mousePointerCrosshairOpacity;
}
set
{
if (value != _mousePointerCrosshairOpacity)
{
_mousePointerCrosshairOpacity = value;
MousePointerCrosshairSettingsConfig.Properties.CrosshairOpacity.Value = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public int MousePointerCrosshairRadius
{
get
{
return _mousePointerCrosshairRadius;
}
set
{
if (value != _mousePointerCrosshairRadius)
{
_mousePointerCrosshairRadius = value;
MousePointerCrosshairSettingsConfig.Properties.CrosshairRadius.Value = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public int MousePointerCrosshairThickness
{
get
{
return _mousePointerCrosshairThickness;
}
set
{
if (value != _mousePointerCrosshairThickness)
{
_mousePointerCrosshairThickness = value;
MousePointerCrosshairSettingsConfig.Properties.CrosshairThickness.Value = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public string MousePointerCrosshairBorderColor
{
get
{
return _mousePointerCrosshairBorderColor;
}
set
{
// The fallback value is based on ToRGBHex's behavior, which returns
// #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
// This extra handling is added here to deal with FxCop warnings.
value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
if (!value.Equals(_mousePointerCrosshairBorderColor, StringComparison.OrdinalIgnoreCase))
{
_mousePointerCrosshairBorderColor = value;
MousePointerCrosshairSettingsConfig.Properties.CrosshairBorderColor.Value = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public int MousePointerCrosshairBorderSize
{
get
{
return _mousePointerCrosshairBorderSize;
}
set
{
if (value != _mousePointerCrosshairBorderSize)
{
_mousePointerCrosshairBorderSize = value;
MousePointerCrosshairSettingsConfig.Properties.CrosshairBorderSize.Value = value;
NotifyMousePointerCrosshairPropertyChanged();
}
}
}
public void NotifyMousePointerCrosshairPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
SndMousePointerCrosshairSettings outsettings = new SndMousePointerCrosshairSettings(MousePointerCrosshairSettingsConfig);
SndModuleSettings<SndMousePointerCrosshairSettings> ipcMessage = new SndModuleSettings<SndMousePointerCrosshairSettings>(outsettings);
SendConfigMSG(ipcMessage.ToJsonString());
SettingsUtils.SaveSettings(MousePointerCrosshairSettingsConfig.ToJsonString(), MousePointerCrosshairSettings.ModuleName);
}
private Func<string, int> SendConfigMSG { get; }
private bool _isFindMyMouseEnabled;
@ -416,5 +601,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private int _highlighterRadius;
private int _highlightFadeDelayMs;
private int _highlightFadeDurationMs;
private bool _isMousePointerCrosshairEnabled;
private string _mousePointerCrosshairColor;
private int _mousePointerCrosshairOpacity;
private int _mousePointerCrosshairRadius;
private int _mousePointerCrosshairThickness;
private string _mousePointerCrosshairBorderColor;
private int _mousePointerCrosshairBorderSize;
}
}

View File

@ -22,6 +22,10 @@
Style="{ThemeResource OobeSubtitleStyle}" />
<toolkitcontrols:MarkdownTextBlock Background="Transparent" x:Uid="Oobe_MouseUtils_MouseHighlighter_Description" />
<TextBlock x:Uid="Oobe_MouseUtils_MousePointerCrosshair"
Style="{ThemeResource OobeSubtitleStyle}" />
<toolkitcontrols:MarkdownTextBlock Background="Transparent" x:Uid="Oobe_MouseUtils_MousePointerCrosshair_Description" />
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,24,0,0">
<Button x:Uid="OOBE_Settings"
Click="SettingsLaunchButton_Click"/>

View File

@ -1651,6 +1651,14 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
<value>Use a keyboard shortcut highlight left and right mouse clicks.</value>
<comment>Mouse as in the hardware peripheral.</comment>
</data>
<data name="Oobe_MouseUtils_MousePointerCrosshair.Text" xml:space="preserve">
<value>Mouse Pointer Crosshair</value>
<comment>Mouse as in the hardware peripheral.</comment>
</data>
<data name="Oobe_MouseUtils_MousePointerCrosshair_Description.Text" xml:space="preserve">
<value>Draw a crosshair centered around the mouse pointer.</value>
<comment>Mouse as in the hardware peripheral.</comment>
</data>
<data name="Launch_Run.Content" xml:space="preserve">
<value>Launch PowerToys Run</value>
</data>
@ -1821,6 +1829,45 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
<value>Duration of the disappear animation (ms)</value>
<comment>ms = milliseconds</comment>
</data>
<data name="MouseUtils_MousePointerCrosshair.Header" xml:space="preserve">
<value>Mouse Pointer Crosshair</value>
<comment>Refers to the utility name</comment>
</data>
<data name="MouseUtils_MousePointerCrosshair.Description" xml:space="preserve">
<value>Mouse Pointer Crosshair draws a crosshair centered on the mouse pointer.</value>
<comment>"Mouse Pointer Crosshair" is the name of the utility. Mouse is the hardware mouse.</comment>
</data>
<data name="MouseUtils_Enable_MousePointerCrosshair.Header" xml:space="preserve">
<value>Enable Mouse Pointer Crosshair</value>
<comment>"Mouse Pointer Crosshair" is the name of the utility.</comment>
</data>
<data name="MouseUtils_MousePointerCrosshair_ActivationShortcut.Header" xml:space="preserve">
<value>Activation shortcut</value>
</data>
<data name="MouseUtils_MousePointerCrosshair_ActivationShortcut.Description" xml:space="preserve">
<value>Customize the shortcut to show/hide the crosshair</value>
</data>
<data name="MouseUtils_MousePointerCrosshair_CrosshairColor.Header" xml:space="preserve">
<value>Crosshair color</value>
</data>
<data name="MouseUtils_MousePointerCrosshair_CrosshairOpacity.Header" xml:space="preserve">
<value>Crosshair opacity (%)</value>
</data>
<data name="MouseUtils_MousePointerCrosshair_CrosshairRadius.Header" xml:space="preserve">
<value>Crosshair center radius (px)</value>
<comment>px = pixels</comment>
</data>
<data name="MouseUtils_MousePointerCrosshair_CrosshairThickness.Header" xml:space="preserve">
<value>Crosshair thickness (px)</value>
<comment>px = pixels</comment>
</data>
<data name="MouseUtils_MousePointerCrosshair_CrosshairBorderColor.Header" xml:space="preserve">
<value>Crosshair border color</value>
</data>
<data name="MouseUtils_MousePointerCrosshair_CrosshairBorderSize.Header" xml:space="preserve">
<value>Crosshair border size (px)</value>
<comment>px = pixels</comment>
</data>
<data name="FancyZones_Radio_Custom_Colors.Content" xml:space="preserve">
<value>Custom colors</value>
</data>

View File

@ -175,7 +175,89 @@
</controls:SettingExpander.Content>
</controls:SettingExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshair">
<controls:Setting x:Uid="MouseUtils_Enable_MousePointerCrosshair" Icon="&#xE710;">
<!-- TODO: Uncomment once an icon is added.
<controls:Setting.Icon>
<BitmapIcon UriSource="ms-appx:///Assets/FluentIcons/FluentIconsMousePointerCrosshair.png" ShowAsMonochrome="False" />
</controls:Setting.Icon>
-->
<controls:Setting.ActionContent>
<ToggleSwitch IsOn="{x:Bind ViewModel.IsMousePointerCrosshairEnabled, Mode=TwoWay}" x:Uid="ToggleSwitch"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_ActivationShortcut" Icon="&#xEDA7;" IsEnabled="{x:Bind ViewModel.IsMousePointerCrosshairEnabled, Mode=OneWay}">
<controls:Setting.ActionContent>
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.MousePointerCrosshairActivationShortcut, Mode=TwoWay}"
MinWidth="{StaticResource SettingActionControlMinWidth}"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:SettingExpander IsEnabled="{x:Bind ViewModel.IsMousePointerCrosshairEnabled, Mode=OneWay}" IsExpanded="False" >
<controls:SettingExpander.Header>
<controls:Setting x:Uid="ShortcutGuide_Appearance_Behavior" Icon="&#xE790;" />
</controls:SettingExpander.Header>
<controls:SettingExpander.Content>
<StackPanel>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_CrosshairColor" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsMousePointerCrosshairEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
<controls:Setting.ActionContent>
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.MousePointerCrosshairColor, Mode=TwoWay}" />
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_CrosshairOpacity" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsMousePointerCrosshairEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
<controls:Setting.ActionContent>
<Slider Minimum="1"
Maximum="100"
MinWidth="{StaticResource SettingActionControlMinWidth}"
Value="{x:Bind Mode=TwoWay, Path=ViewModel.MousePointerCrosshairOpacity}"
HorizontalAlignment="Right"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_CrosshairRadius" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsMousePointerCrosshairEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
<controls:Setting.ActionContent>
<muxc:NumberBox Minimum="1"
Maximum="500"
Value="{x:Bind Mode=TwoWay, Path=ViewModel.MousePointerCrosshairRadius}"
MinWidth="{StaticResource SettingActionControlMinWidth}"
SpinButtonPlacementMode="Compact"
HorizontalAlignment="Left"
SmallChange="1"
LargeChange="10"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_CrosshairThickness" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsMousePointerCrosshairEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
<controls:Setting.ActionContent>
<muxc:NumberBox Minimum="1"
Maximum="50"
Value="{x:Bind Mode=TwoWay, Path=ViewModel.MousePointerCrosshairThickness}"
MinWidth="{StaticResource SettingActionControlMinWidth}"
SpinButtonPlacementMode="Compact"
HorizontalAlignment="Left"
SmallChange="1"
LargeChange="10"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_CrosshairBorderColor" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsMousePointerCrosshairEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
<controls:Setting.ActionContent>
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.MousePointerCrosshairBorderColor, Mode=TwoWay}" />
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MouseUtils_MousePointerCrosshair_CrosshairBorderSize" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsMousePointerCrosshairEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
<controls:Setting.ActionContent>
<muxc:NumberBox Minimum="0"
Maximum="50"
Value="{x:Bind Mode=TwoWay, Path=ViewModel.MousePointerCrosshairBorderSize}"
MinWidth="{StaticResource SettingActionControlMinWidth}"
SpinButtonPlacementMode="Compact"
HorizontalAlignment="Left"
SmallChange="1"
LargeChange="2"/>
</controls:Setting.ActionContent>
</controls:Setting>
</StackPanel>
</controls:SettingExpander.Content>
</controls:SettingExpander>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>
<controls:SettingsPageControl.PrimaryLinks>

View File

@ -33,7 +33,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
var settingsUtils = new SettingsUtils();
ViewModel = new MouseUtilsViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<FindMyMouseSettings>.GetInstance(settingsUtils), SettingsRepository<MouseHighlighterSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
ViewModel = new MouseUtilsViewModel(
settingsUtils,
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
SettingsRepository<FindMyMouseSettings>.GetInstance(settingsUtils),
SettingsRepository<MouseHighlighterSettings>.GetInstance(settingsUtils),
SettingsRepository<MousePointerCrosshairSettings>.GetInstance(settingsUtils),
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
}