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

dapplo / Dapplo.Windows / 21469958969

29 Jan 2026 07:48AM UTC coverage: 36.141% (+1.2%) from 34.905%
21469958969

push

github

Lakritzator
Fix for an issue which popped up in Greenshot, we should not dispose the shared CursorInfo.CursorHandle, see https://github.com/greenshot/greenshot/pull/889

670 of 2030 branches covered (33.0%)

Branch coverage included in aggregate %.

5 of 6 new or added lines in 2 files covered. (83.33%)

1883 of 5034 relevant lines covered (37.41%)

21.79 hits per line

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

72.34
/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
        {
41
            using (var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Cursors"))
2✔
42
            {
43
                if (key?.GetValue("CursorBaseSize") is int size)
2!
44
                {
45
                    return size;
2✔
46
                }
47
            }
×
48
        }
×
49
        catch
×
50
        {
51
            // Empty by design
52
        }
×
53
        return 32; // Default
×
54
    }
2✔
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
    /// <param name="returnBitmap">The cursor image represented by a Bitmap or BitmapSource</param>
60
    /// <param name="hotSpot">NativePoint with the hotspot information, this is the offset where the click on the screen happens</param>
61
    /// <returns>bool true if it worked</returns>
62
    public static bool TryGetCurrentCursor<TBitmapType>(out TBitmapType returnBitmap, out NativePoint hotSpot) where TBitmapType : class
63
    {
64
        var cursorInfo = CursorInfo.Create();
2✔
65
        returnBitmap = null;
2✔
66
        hotSpot = NativePoint.Empty;
2✔
67
        if (!NativeCursorMethods.GetCursorInfo(ref cursorInfo))
2!
68
        {
69
            return false;
×
70
        }
71

72
        if (!cursorInfo.IsShowing)
2!
73
        {
NEW
74
            return false;
×
75
        }
76

77
        var iconInfoEx = IconInfoEx.Create();
2✔
78

79
        if (NativeIconMethods.GetIconInfoEx(cursorInfo.CursorHandle, ref iconInfoEx))
2!
80
        {
81
            try
82
            {
83
                // Ignore what the handle says. Calculate what it SHOULD be.
84
                // "CursorBaseSize" is the raw size (32, 48, 64) set in Accessibility settings.
85
                int baseSize = GetCursorBaseSize();
2✔
86
                uint dpi = NativeDpiMethods.GetDpiForSystem();
2✔
87
                int targetSize = (int)(baseSize * (dpi / 96.0f));
2✔
88

89
                // We ask Windows: "Load this specific resource again, but make it exactly [targetSize] pixels."
90
                IntPtr hRealCursor = IntPtr.Zero;
2✔
91
                bool isSharedHandle = false;
2✔
92

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

106
                // If reload failed (dynamic cursor), fallback to the original 32px handle
107
                if (hRealCursor == IntPtr.Zero)
2✔
108
                {
109
                    hRealCursor = cursorInfo.CursorHandle;
2✔
110
                    isSharedHandle = true;
2✔
111
                }
112

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

142
                // The original hotspot is for the 32px version. Scale it up.
143
                if (targetSize > 0 && baseSize > 0) // Avoid divide by zero
2!
144
                {
145
                    float scaleFactor = (float)targetSize / 32.0f; // Assuming 32 is standard base
2✔
146

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

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

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

© 2026 Coveralls, Inc