Files
PowerToys/src/modules/MouseUtils/MousePointerCrosshairs/InclusiveCrosshairs.cpp
Mike Hall faf7c7f1a1 add horizontal and vertical options for MousePointerCrosshairs (#41789)
## Summary of the Pull Request
This PR addresses two logged issues for MousePointerCrosshairs, these
are:
https://github.com/microsoft/PowerToys/issues/24944 
https://github.com/microsoft/PowerToys/issues/31817

## PR Checklist

- [x] Closes: #24944, #31817
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments
The PR adds a new combo box to MousePointerCrosshairs XAML options, this
gives the option for 'both', 'vertical only' or 'horizontal only'. The
default option is 'both' which mirrors the existing behavior.

## Validation Steps Performed
Validation has been completed on two separate PCs, a Surface laptop 7
Pro and a Dell Workstation.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-29 10:32:07 +08:00

701 lines
27 KiB
C++

// InclusiveCrosshairs.cpp : Defines the entry point for the application.
//
#include "pch.h"
#include "InclusiveCrosshairs.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 InclusiveCrosshairs
{
bool MyRegisterClass(HINSTANCE hInstance);
static InclusiveCrosshairs* instance;
void Terminate();
void SwitchActivationMode();
void ApplySettings(InclusiveCrosshairsSettings& settings, bool applyToRuntimeObjects);
public:
// Allow external callers to request a position update (thread-safe enqueue)
static void RequestUpdatePosition()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr)
{
instance->UpdateCrosshairsPosition();
}
});
}
}
static void EnsureOn()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr && !instance->m_drawing)
{
instance->StartDrawing();
}
});
}
}
static void EnsureOff()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr && instance->m_drawing)
{
instance->StopDrawing();
}
});
}
}
static void SetExternalControl(bool enabled)
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([enabled]() {
if (instance != nullptr)
{
instance->m_externalControl = enabled;
if (enabled && instance->m_mouseHook)
{
UnhookWindowsHookEx(instance->m_mouseHook);
instance->m_mouseHook = NULL;
}
else if (!enabled && instance->m_drawing && !instance->m_mouseHook)
{
instance->m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, instance->m_hinstance, 0);
}
}
});
}
}
static void SetCrosshairsOrientation(CrosshairsOrientation orientation)
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([orientation]() {
if (instance != nullptr)
{
instance->m_crosshairs_orientation = orientation;
instance->UpdateCrosshairsPosition();
}
});
}
}
private:
enum class MouseButton
{
Left,
Right
};
void DestroyInclusiveCrosshairs();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
void StartDrawing();
void StopDrawing();
bool CreateInclusiveCrosshairs();
void UpdateCrosshairsPosition();
HHOOK m_mouseHook = NULL;
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
static constexpr auto m_className = L"MousePointerCrosshairs";
static constexpr auto m_windowTitle = L"PowerToys Mouse Pointer Crosshairs";
static constexpr DWORD AUTO_HIDE_TIMER_ID = 101;
HWND m_hwndOwner = NULL;
HWND m_hwnd = NULL;
HINSTANCE m_hinstance = NULL;
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_crosshairs_border_layer{ nullptr };
winrt::LayerVisual m_crosshairs_layer{ nullptr };
winrt::SpriteVisual m_left_crosshairs_border{ nullptr };
winrt::SpriteVisual m_left_crosshairs{ nullptr };
winrt::SpriteVisual m_right_crosshairs_border{ nullptr };
winrt::SpriteVisual m_right_crosshairs{ nullptr };
winrt::SpriteVisual m_top_crosshairs_border{ nullptr };
winrt::SpriteVisual m_top_crosshairs{ nullptr };
winrt::SpriteVisual m_bottom_crosshairs_border{ nullptr };
winrt::SpriteVisual m_bottom_crosshairs{ nullptr };
bool m_drawing = false;
bool m_destroyed = false;
bool m_hiddenCursor = false;
bool m_externalControl = false;
void SetAutoHideTimer() noexcept;
// Configurable Settings
winrt::Windows::UI::Color m_crosshairs_border_color = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_COLOR;
winrt::Windows::UI::Color m_crosshairs_color = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_COLOR;
int m_crosshairs_radius = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_RADIUS;
int m_crosshairs_thickness = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_THICKNESS;
int m_crosshairs_border_size = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_BORDER_SIZE;
bool m_crosshairs_is_fixed_length_enabled = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED;
int m_crosshairs_fixed_length = INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_FIXED_LENGTH;
CrosshairsOrientation m_crosshairs_orientation = static_cast<CrosshairsOrientation>(INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_ORIENTATION);
float m_crosshairs_opacity = max(0.f, min(1.f, (float)INCLUSIVE_MOUSE_DEFAULT_CROSSHAIRS_OPACITY / 100.0f));
bool m_crosshairs_auto_hide = INCLUSIVE_MOUSE_DEFAULT_AUTO_HIDE;
};
InclusiveCrosshairs* InclusiveCrosshairs::instance = nullptr;
bool InclusiveCrosshairs::CreateInclusiveCrosshairs()
{
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
// \ [crosshairs border layer] LayerVisual
// \ [crosshairs border sprites]
// [crosshairs layer] LayerVisual
// \ [crosshairs sprites]
m_root = m_compositor.CreateContainerVisual();
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_target.Root(m_root);
m_root.Opacity(m_crosshairs_opacity);
m_crosshairs_border_layer = m_compositor.CreateLayerVisual();
m_crosshairs_border_layer.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_root.Children().InsertAtTop(m_crosshairs_border_layer);
m_crosshairs_border_layer.Opacity(1.0f);
m_crosshairs_layer = m_compositor.CreateLayerVisual();
m_crosshairs_layer.RelativeSizeAdjustment({ 1.0f, 1.0f });
// Create the crosshairs sprites.
m_left_crosshairs_border = m_compositor.CreateSpriteVisual();
m_left_crosshairs_border.AnchorPoint({ 1.0f, 0.5f });
m_left_crosshairs_border.Brush(m_compositor.CreateColorBrush(m_crosshairs_border_color));
m_crosshairs_border_layer.Children().InsertAtTop(m_left_crosshairs_border);
m_left_crosshairs = m_compositor.CreateSpriteVisual();
m_left_crosshairs.AnchorPoint({ 1.0f, 0.5f });
m_left_crosshairs.Brush(m_compositor.CreateColorBrush(m_crosshairs_color));
m_crosshairs_layer.Children().InsertAtTop(m_left_crosshairs);
m_right_crosshairs_border = m_compositor.CreateSpriteVisual();
m_right_crosshairs_border.AnchorPoint({ 0.0f, 0.5f });
m_right_crosshairs_border.Brush(m_compositor.CreateColorBrush(m_crosshairs_border_color));
m_crosshairs_border_layer.Children().InsertAtTop(m_right_crosshairs_border);
m_right_crosshairs = m_compositor.CreateSpriteVisual();
m_right_crosshairs.AnchorPoint({ 0.0f, 0.5f });
m_right_crosshairs.Brush(m_compositor.CreateColorBrush(m_crosshairs_color));
m_crosshairs_layer.Children().InsertAtTop(m_right_crosshairs);
m_top_crosshairs_border = m_compositor.CreateSpriteVisual();
m_top_crosshairs_border.AnchorPoint({ 0.5f, 1.0f });
m_top_crosshairs_border.Brush(m_compositor.CreateColorBrush(m_crosshairs_border_color));
m_crosshairs_border_layer.Children().InsertAtTop(m_top_crosshairs_border);
m_top_crosshairs = m_compositor.CreateSpriteVisual();
m_top_crosshairs.AnchorPoint({ 0.5f, 1.0f });
m_top_crosshairs.Brush(m_compositor.CreateColorBrush(m_crosshairs_color));
m_crosshairs_layer.Children().InsertAtTop(m_top_crosshairs);
m_bottom_crosshairs_border = m_compositor.CreateSpriteVisual();
m_bottom_crosshairs_border.AnchorPoint({ 0.5f, 0.0f });
m_bottom_crosshairs_border.Brush(m_compositor.CreateColorBrush(m_crosshairs_border_color));
m_crosshairs_border_layer.Children().InsertAtTop(m_bottom_crosshairs_border);
m_bottom_crosshairs = m_compositor.CreateSpriteVisual();
m_bottom_crosshairs.AnchorPoint({ 0.5f, 0.0f });
m_bottom_crosshairs.Brush(m_compositor.CreateColorBrush(m_crosshairs_color));
m_crosshairs_layer.Children().InsertAtTop(m_bottom_crosshairs);
m_crosshairs_border_layer.Children().InsertAtTop(m_crosshairs_layer);
m_crosshairs_layer.Opacity(1.0f);
UpdateCrosshairsPosition();
return true;
}
catch (...)
{
return false;
}
}
void InclusiveCrosshairs::UpdateCrosshairsPosition()
{
POINT ptCursor;
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, 0);
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);
// Crosshair position should receive a minor adjustment for odd values to prevent anti-aliasing due to half pixels, while still looking like it's centered around the mouse pointer.
float halfPixelAdjustment = m_crosshairs_thickness % 2 == 1 ? 0.5f : 0.0f;
float borderSizePadding = m_crosshairs_border_size * 2.f;
// Left and Right crosshairs (horizontal line)
if (m_crosshairs_orientation == CrosshairsOrientation::Both || m_crosshairs_orientation == CrosshairsOrientation::HorizontalOnly)
{
float leftCrosshairsFullScreenLength = ptCursor.x - ptMonitorUpperLeft.x - m_crosshairs_radius + halfPixelAdjustment * 2.f;
float leftCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : leftCrosshairsFullScreenLength;
float leftCrosshairsBorderLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length + borderSizePadding : leftCrosshairsFullScreenLength + m_crosshairs_border_size;
m_left_crosshairs_border.Offset({ ptCursor.x - m_crosshairs_radius + m_crosshairs_border_size + halfPixelAdjustment * 2.f, ptCursor.y + halfPixelAdjustment, .0f });
m_left_crosshairs_border.Size({ leftCrosshairsBorderLength, m_crosshairs_thickness + borderSizePadding });
m_left_crosshairs.Offset({ ptCursor.x - m_crosshairs_radius + halfPixelAdjustment * 2.f, ptCursor.y + halfPixelAdjustment, .0f });
m_left_crosshairs.Size({ leftCrosshairsLength, static_cast<float>(m_crosshairs_thickness) });
float rightCrosshairsFullScreenLength = static_cast<float>(ptMonitorBottomRight.x) - ptCursor.x - m_crosshairs_radius;
float rightCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : rightCrosshairsFullScreenLength;
float rightCrosshairsBorderLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length + borderSizePadding : rightCrosshairsFullScreenLength + m_crosshairs_border_size;
m_right_crosshairs_border.Offset({ static_cast<float>(ptCursor.x) + m_crosshairs_radius - m_crosshairs_border_size, ptCursor.y + halfPixelAdjustment, .0f });
m_right_crosshairs_border.Size({ rightCrosshairsBorderLength, m_crosshairs_thickness + borderSizePadding });
m_right_crosshairs.Offset({ static_cast<float>(ptCursor.x) + m_crosshairs_radius, ptCursor.y + halfPixelAdjustment, .0f });
m_right_crosshairs.Size({ rightCrosshairsLength, static_cast<float>(m_crosshairs_thickness) });
}
else
{
// Hide horizontal crosshairs by setting size to 0
m_left_crosshairs_border.Size({ 0.0f, 0.0f });
m_left_crosshairs.Size({ 0.0f, 0.0f });
m_right_crosshairs_border.Size({ 0.0f, 0.0f });
m_right_crosshairs.Size({ 0.0f, 0.0f });
}
// Top and Bottom crosshairs (vertical line)
if (m_crosshairs_orientation == CrosshairsOrientation::Both || m_crosshairs_orientation == CrosshairsOrientation::VerticalOnly)
{
float topCrosshairsFullScreenLength = ptCursor.y - ptMonitorUpperLeft.y - m_crosshairs_radius + halfPixelAdjustment * 2.f;
float topCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : topCrosshairsFullScreenLength;
float topCrosshairsBorderLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length + borderSizePadding : topCrosshairsFullScreenLength + m_crosshairs_border_size;
m_top_crosshairs_border.Offset({ ptCursor.x + halfPixelAdjustment, ptCursor.y - m_crosshairs_radius + m_crosshairs_border_size + halfPixelAdjustment * 2.f, .0f });
m_top_crosshairs_border.Size({ m_crosshairs_thickness + borderSizePadding, topCrosshairsBorderLength });
m_top_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, ptCursor.y - m_crosshairs_radius + halfPixelAdjustment * 2.f, .0f });
m_top_crosshairs.Size({ static_cast<float>(m_crosshairs_thickness), topCrosshairsLength });
float bottomCrosshairsFullScreenLength = static_cast<float>(ptMonitorBottomRight.y) - ptCursor.y - m_crosshairs_radius;
float bottomCrosshairsLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length : bottomCrosshairsFullScreenLength;
float bottomCrosshairsBorderLength = m_crosshairs_is_fixed_length_enabled ? m_crosshairs_fixed_length + borderSizePadding : bottomCrosshairsFullScreenLength + m_crosshairs_border_size;
m_bottom_crosshairs_border.Offset({ ptCursor.x + halfPixelAdjustment, static_cast<float>(ptCursor.y) + m_crosshairs_radius - m_crosshairs_border_size, .0f });
m_bottom_crosshairs_border.Size({ m_crosshairs_thickness + borderSizePadding, bottomCrosshairsBorderLength });
m_bottom_crosshairs.Offset({ ptCursor.x + halfPixelAdjustment, static_cast<float>(ptCursor.y) + m_crosshairs_radius, .0f });
m_bottom_crosshairs.Size({ static_cast<float>(m_crosshairs_thickness), bottomCrosshairsLength });
}
else
{
// Hide vertical crosshairs by setting size to 0
m_top_crosshairs_border.Size({ 0.0f, 0.0f });
m_top_crosshairs.Size({ 0.0f, 0.0f });
m_bottom_crosshairs_border.Size({ 0.0f, 0.0f });
m_bottom_crosshairs.Size({ 0.0f, 0.0f });
}
}
LRESULT CALLBACK InclusiveCrosshairs::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept
{
if (nCode >= 0)
{
MSLLHOOKSTRUCT* hookData = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
if (instance && !instance->m_externalControl)
{
if (wParam == WM_MOUSEMOVE)
{
instance->UpdateCrosshairsPosition();
}
}
}
return CallNextHookEx(0, nCode, wParam, lParam);
}
void InclusiveCrosshairs::StartDrawing()
{
Logger::info("Start drawing crosshairs.");
Trace::StartDrawingCrosshairs();
UpdateCrosshairsPosition();
m_hiddenCursor = false;
if (m_crosshairs_auto_hide)
{
CURSORINFO cursorInfo{};
cursorInfo.cbSize = sizeof(cursorInfo);
if (GetCursorInfo(&cursorInfo))
{
m_hiddenCursor = !(cursorInfo.flags & CURSOR_SHOWING);
}
SetAutoHideTimer();
}
if (!m_hiddenCursor)
{
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
}
m_drawing = true;
m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, m_hinstance, 0);
}
void InclusiveCrosshairs::StopDrawing()
{
Logger::info("Stop drawing crosshairs.");
m_drawing = false;
ShowWindow(m_hwnd, SW_HIDE);
UnhookWindowsHookEx(m_mouseHook);
m_mouseHook = NULL;
KillTimer(m_hwnd, AUTO_HIDE_TIMER_ID);
}
void InclusiveCrosshairs::SwitchActivationMode()
{
PostMessage(m_hwnd, WM_SWITCH_ACTIVATION_MODE, 0, 0);
}
void InclusiveCrosshairs::ApplySettings(InclusiveCrosshairsSettings& settings, bool applyToRunTimeObjects)
{
m_crosshairs_radius = settings.crosshairsRadius;
m_crosshairs_thickness = settings.crosshairsThickness;
m_crosshairs_color = settings.crosshairsColor;
m_crosshairs_opacity = max(0.f, min(1.f, (float)settings.crosshairsOpacity / 100.0f));
m_crosshairs_border_color = settings.crosshairsBorderColor;
m_crosshairs_border_size = settings.crosshairsBorderSize;
bool autoHideChanged = m_crosshairs_auto_hide != settings.crosshairsAutoHide;
m_crosshairs_auto_hide = settings.crosshairsAutoHide;
m_crosshairs_is_fixed_length_enabled = settings.crosshairsIsFixedLengthEnabled;
m_crosshairs_fixed_length = settings.crosshairsFixedLength;
m_crosshairs_orientation = settings.crosshairsOrientation;
if (applyToRunTimeObjects)
{
if (autoHideChanged)
{
if (m_crosshairs_auto_hide)
{
SetAutoHideTimer();
}
else
{
KillTimer(m_hwnd, AUTO_HIDE_TIMER_ID);
// Edge case of settings being changed with hidden crosshairs: timer time-out is 1 seconds
if (m_drawing && m_hiddenCursor)
{
instance->m_hiddenCursor = false;
ShowWindow(instance->m_hwnd, SW_SHOWNOACTIVATE);
}
}
}
// Runtime objects already created. Should update in the owner thread.
if (m_dispatcherQueueController == nullptr)
{
Logger::warn("Tried accessing the dispatch queue controller before it was initialized.");
// No dispatcher Queue Controller? Means initialization still hasn't run, so settings will be applied then.
return;
}
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
InclusiveCrosshairsSettings localSettings = settings;
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
if (!m_destroyed)
{
// Apply new settings to runtime composition objects.
m_left_crosshairs.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_color);
m_right_crosshairs.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_color);
m_top_crosshairs.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_color);
m_bottom_crosshairs.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_color);
m_left_crosshairs_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_border_color);
m_right_crosshairs_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_border_color);
m_top_crosshairs_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_border_color);
m_bottom_crosshairs_border.Brush().as<winrt::CompositionColorBrush>().Color(m_crosshairs_border_color);
m_root.Opacity(m_crosshairs_opacity);
UpdateCrosshairsPosition();
}
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to update the crosshairs settings.");
}
}
}
void InclusiveCrosshairs::DestroyInclusiveCrosshairs()
{
StopDrawing();
PostQuitMessage(0);
}
LRESULT CALLBACK InclusiveCrosshairs::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->CreateInclusiveCrosshairs() ? 0 : -1;
case WM_NCHITTEST:
return HTTRANSPARENT;
case WM_SWITCH_ACTIVATION_MODE:
if (instance->m_drawing)
{
instance->StopDrawing();
}
else
{
instance->StartDrawing();
}
break;
case WM_DESTROY:
instance->DestroyInclusiveCrosshairs();
break;
case WM_TIMER:
if (wParam == AUTO_HIDE_TIMER_ID && instance->m_drawing)
{
CURSORINFO cursorInfo{};
cursorInfo.cbSize = sizeof(cursorInfo);
if (GetCursorInfo(&cursorInfo))
{
if (cursorInfo.flags & CURSOR_SHOWING)
{
if (instance->m_hiddenCursor)
{
instance->m_hiddenCursor = false;
ShowWindow(instance->m_hwnd, SW_SHOWNOACTIVATE);
}
}
else
{
if (!instance->m_hiddenCursor)
{
instance->m_hiddenCursor = true;
ShowWindow(instance->m_hwnd, SW_HIDE);
}
}
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
bool InclusiveCrosshairs::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 = static_cast<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 InclusiveCrosshairs::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.");
}
}
void InclusiveCrosshairs::SetAutoHideTimer() noexcept
{
if (SetTimer(m_hwnd, AUTO_HIDE_TIMER_ID, 1000, NULL) == 0)
{
int error = GetLastError();
Logger::trace("Failed to create auto hide timer. Last error: {}", error);
}
}
#pragma region InclusiveCrosshairs_API
void InclusiveCrosshairsApplySettings(InclusiveCrosshairsSettings& settings)
{
if (InclusiveCrosshairs::instance != nullptr)
{
Logger::info("Applying settings.");
InclusiveCrosshairs::instance->ApplySettings(settings, true);
}
}
void InclusiveCrosshairsSwitch()
{
if (InclusiveCrosshairs::instance != nullptr)
{
Logger::info("Switching activation mode.");
InclusiveCrosshairs::instance->SwitchActivationMode();
}
}
void InclusiveCrosshairsDisable()
{
if (InclusiveCrosshairs::instance != nullptr)
{
Logger::info("Terminating the crosshairs instance.");
InclusiveCrosshairs::instance->Terminate();
}
}
bool InclusiveCrosshairsIsEnabled()
{
return (InclusiveCrosshairs::instance != nullptr);
}
void InclusiveCrosshairsRequestUpdatePosition()
{
InclusiveCrosshairs::RequestUpdatePosition();
}
void InclusiveCrosshairsEnsureOn()
{
InclusiveCrosshairs::EnsureOn();
}
void InclusiveCrosshairsEnsureOff()
{
InclusiveCrosshairs::EnsureOff();
}
void InclusiveCrosshairsSetExternalControl(bool enabled)
{
InclusiveCrosshairs::SetExternalControl(enabled);
}
void InclusiveCrosshairsSetOrientation(CrosshairsOrientation orientation)
{
InclusiveCrosshairs::SetCrosshairsOrientation(orientation);
}
int InclusiveCrosshairsMain(HINSTANCE hInstance, InclusiveCrosshairsSettings& settings)
{
Logger::info("Starting a crosshairs instance.");
if (InclusiveCrosshairs::instance != nullptr)
{
Logger::error("A crosshairs instance was still working when trying to start a new one.");
return 0;
}
// Perform application initialization:
InclusiveCrosshairs crosshairs;
InclusiveCrosshairs::instance = &crosshairs;
crosshairs.ApplySettings(settings, false);
if (!crosshairs.MyRegisterClass(hInstance))
{
Logger::error("Couldn't initialize a crosshairs instance.");
InclusiveCrosshairs::instance = nullptr;
return FALSE;
}
Logger::info("Initialized the crosshairs instance.");
if (settings.autoActivate)
{
crosshairs.SwitchActivationMode();
}
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Logger::info("Crosshairs message loop ended.");
InclusiveCrosshairs::instance = nullptr;
return (int)msg.wParam;
}
#pragma endregion InclusiveCrosshairs_API