• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dapplo / Dapplo.Windows / 21365007899

26 Jan 2026 04:17PM UTC coverage: 34.905% (-0.4%) from 35.341%
21365007899

push

github

Lakritzator
Added support for getting the cursor with the correct size.

647 of 2024 branches covered (31.97%)

Branch coverage included in aggregate %.

1 of 86 new or added lines in 5 files covered. (1.16%)

2 existing lines in 2 files now uncovered.

1818 of 5038 relevant lines covered (36.09%)

21.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Dapplo.Windows.Icons/CursorHelper.cs
1
// Copyright (c) Dapplo and contributors. All rights reserved.
2
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3

4
#if !NETSTANDARD2_0
5
using Dapplo.Windows.Common.Enums;
6
using Dapplo.Windows.Common.Structs;
7
using Dapplo.Windows.Dpi;
8
using Dapplo.Windows.Icons.SafeHandles;
9
using Dapplo.Windows.Icons.Structs;
10
using Dapplo.Windows.Kernel32;
11
using Dapplo.Windows.User32.Structs;
12
using Microsoft.Win32;
13
using System;
14
using System.ComponentModel;
15
using System.Drawing;
16
using System.Runtime.InteropServices;
17
using System.Windows;
18
using System.Windows.Interop;
19
using System.Windows.Media;
20
using System.Windows.Media.Imaging;
21

22
namespace Dapplo.Windows.Icons;
23

24
/// <summary>
25
/// Helper methods for using cursor information
26
/// </summary>
27
public static class CursorHelper
28
{
29
    /// <summary>
30
    /// Gets the base size of the mouse cursor, in pixels, as configured by the user in the system settings.
31
    /// </summary>
32
    /// <remarks>This method reads the 'CursorBaseSize' value from the Windows Registry under 'Control
33
    /// Panel\Cursors'. If the value is not found or an error occurs while accessing the registry, a default size of 32
34
    /// pixels is returned.</remarks>
35
    /// <returns>The size of the mouse cursor in pixels. Returns 32 if the value cannot be retrieved from the system settings.</returns>
36
    public static int GetCursorBaseSize()
37
    {
38
        // Reads the "Make mouse pointer bigger" slider value from Registry
39
        try
40
        {
NEW
41
            using (var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Cursors"))
×
42
            {
NEW
43
                if (key?.GetValue("CursorBaseSize") is int size)
×
44
                {
NEW
45
                    return size;
×
46
                }
UNCOV
47
            }
×
NEW
48
        }
×
NEW
49
        catch
×
50
        {
51
            // Empty by design
NEW
52
        }
×
NEW
53
        return 32; // Default
×
NEW
54
    }
×
55

56
    /// <summary>
57
    /// This method will capture the current Cursor by using User32 Code and will try to get the actual size of the cursor as set in Accessibility settings.
58
    /// </summary>
59
    /// <returns>BitmapSource bitmapSource</returns>
60
    /// <returns>NativePoint hotSpot</returns>
61
    public static bool TryGetCurrentCursor<TBitmapType>(out TBitmapType returnBitmap, out NativePoint hotSpot) where TBitmapType : class
62
    {
NEW
63
        var cursorInfo = CursorInfo.Create();
×
NEW
64
        returnBitmap = null;
×
NEW
65
        hotSpot = NativePoint.Empty;
×
NEW
66
        if (!NativeCursorMethods.GetCursorInfo(ref cursorInfo))
×
67
        {
NEW
68
            return false;
×
69
        }
70

NEW
71
        var iconInfoEx = IconInfoEx.Create();
×
72

NEW
73
        if (NativeIconMethods.GetIconInfoEx(cursorInfo.CursorHandle, ref iconInfoEx))
×
74
        {
75
            try
76
            {
77
                // 2. CALCULATE TARGET SIZE
78
                // Ignore what the handle says. Calculate what it SHOULD be.
79
                // "CursorBaseSize" is the raw size (32, 48, 64) set in Accessibility settings.
NEW
80
                int baseSize = GetCursorBaseSize();
×
NEW
81
                uint dpi = NativeDpiMethods.GetDpiForSystem();
×
NEW
82
                int targetSize = (int)(baseSize * (dpi / 96.0f));
×
83

84
                // 3. RELOAD THE ASSET
85
                // We ask Windows: "Load this specific resource again, but make it exactly [targetSize] pixels."
NEW
86
                IntPtr hRealCursor = IntPtr.Zero;
×
NEW
87
                bool isSharedHandle = false;
×
88

NEW
89
                var moduleName = iconInfoEx.ModuleName;
×
NEW
90
                if (iconInfoEx.ResourceId != 0 && !string.IsNullOrEmpty(moduleName))
×
91
                {
92
                    // It's a System Resource (like the standard Arrow)
NEW
93
                    IntPtr hModule = Kernel32Api.GetModuleHandle(moduleName);
×
NEW
94
                    hRealCursor = NativeCursorMethods.LoadImage(hModule, (IntPtr)iconInfoEx.ResourceId, 2, targetSize, targetSize, 0);
×
95
                }
NEW
96
                else if (!string.IsNullOrEmpty(moduleName))
×
97
                {
98
                    // It's a Custom File (like a downloaded theme)
NEW
99
                    hRealCursor = NativeCursorMethods.LoadImage(IntPtr.Zero, moduleName, 2, targetSize, targetSize, 0x0010); // LR_LOADFROMFILE
×
100
                }
101

102
                // If reload failed (dynamic cursor), fallback to the original 32px handle
NEW
103
                if (hRealCursor == IntPtr.Zero)
×
104
                {
NEW
105
                    hRealCursor = cursorInfo.CursorHandle.DangerousGetHandle();
×
NEW
106
                    isSharedHandle = true;
×
107
                }
108

109
                // 4. DRAW IT
110
                // This renders the (potentially huge) cursor into a transparent bitmap
NEW
111
                if (targetSize > 0)
×
112
                {
NEW
113
                    if (typeof(TBitmapType) == typeof(BitmapSource) || typeof(TBitmapType) == typeof(ImageSource))
×
114
                    {
NEW
115
                        using (iconInfoEx.BitmaskBitmapHandle)
×
NEW
116
                        using (iconInfoEx.ColorBitmapHandle)
×
117
                        {
118
                            // Pass the BitmapSource to the called
NEW
119
                            returnBitmap = Imaging.CreateBitmapSourceFromHIcon(hRealCursor, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()) as TBitmapType;
×
NEW
120
                        }
×
121
                    }
NEW
122
                    else if (typeof(TBitmapType) == typeof(Bitmap))
×
123
                    {
NEW
124
                        var bmp = new Bitmap(targetSize, targetSize, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
×
NEW
125
                        using (Graphics g = Graphics.FromImage(bmp))
×
126
                        {
NEW
127
                            g.Clear(System.Drawing.Color.Transparent);
×
NEW
128
                            NativeIconMethods.DrawIconEx(g.GetHdc(), 0, 0, hRealCursor, targetSize, targetSize, 0, IntPtr.Zero, 0x0003); // DI_NORMAL
×
NEW
129
                            g.ReleaseHdc();
×
NEW
130
                        }
×
131
                        // Pass the bitmap to the called
NEW
132
                        returnBitmap = bmp as TBitmapType;
×
133
                    } else
134
                    {
NEW
135
                        throw new NotSupportedException(typeof(TBitmapType).Name);
×
136
                    }
137

138
                }
139

140
                // 5. SCALE HOTSPOT
141
                // The original hotspot is for the 32px version. Scale it up.
NEW
142
                if (targetSize > 0 && baseSize > 0) // Avoid divide by zero
×
143
                {
NEW
144
                    float scaleFactor = (float)targetSize / 32.0f; // Assuming 32 is standard base
×
145

146
                    // Refine: If we know the original was actually, say, 48, we should use that. 
147
                    // But 32 is the standard logical unit for Windows cursors.
NEW
148
                    hotSpot = new NativePoint((int)(iconInfoEx.Hotspot.X * scaleFactor), (int)(iconInfoEx.Hotspot.Y * scaleFactor));
×
149
                }
150
                else
151
                {
NEW
152
                    hotSpot = iconInfoEx.Hotspot;
×
153
                }
154

155
                // Cleanup cursor, but only if it's not a shared handle (in the other case. we clean up elsewhere)
NEW
156
                if (!isSharedHandle && hRealCursor != IntPtr.Zero) NativeCursorMethods.DestroyCursor(hRealCursor);
×
NEW
157
            }
×
158
            finally
159
            {
160
                // Cleanup icon
NEW
161
                iconInfoEx.BitmaskBitmapHandle.Dispose();
×
NEW
162
                iconInfoEx.ColorBitmapHandle.Dispose();
×
NEW
163
                cursorInfo.CursorHandle.Dispose();
×
NEW
164
            }
×
165
        } else {
NEW
166
            throw new Win32Exception(Marshal.GetLastWin32Error());
×
167
        }
168

NEW
169
        return true;
×
170
    }
171
}
172
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc