18

I'd like for my program to be able to override the maximum imposed mouse size of 32x32 much like the program in the picture attached does, the cursor pictured is 72x72. This is a capture from ProcMon showing what's going on when the cursors change.

However, if I try to change the registry values for the cursor files myself and then push the changes using

SystemParametersInfo(SPI.SPI_SETCURSORS, 0, IntPtr.Zero, SPIF.SPIF_SENDCHANGE);

then the cursor will change, but it's still limited to the 32x32 maximum size. How was this program able to get around that restriction? Also, the cursors remain after the program ends, so it can't be something it's doing while running, but must override a setting somewhere.

enter image description here

Thanks for your help, I haven't been able to find ANYTHING like this on the net, so I don't even know if anyone will have the answer.

EDIT: I see some access to a file called C:\Windows\SysWOW64\Imageres.dll. They're only reads, but maybe these cursors are stored in here, or they modified this file somehow. But I figured it might get someone with more experience than myself off on the right track.

EDIT 2: I believe the sizes are dictated by the SM_CXCURSOR and SM_CYCURSOR variables. If I could find a way to set these, I might be in business. Going to write a quick program to get me those values on the PC with the program running and huge mouse cursor and see what it returns...

EDIT 3: No luck; the PC with the huge cursor returns 32x32 for it's SM_CXCURSOR and SM_CYCURSOR.

DTI-Matt
  • 2,065
  • 9
  • 35
  • 60
  • Maybe you could set your screen resizing to 150%? It's by the screen resolution setting – Cole Tobin Dec 03 '12 at 17:26
  • But that's not how this program is able to do it. Nothing else changes except for the mouse cursor, which is the effect I'm hoping to replicate. – DTI-Matt Dec 03 '12 at 17:33
  • 1
    You would need to post how your attempting to change the size of the mouse pointer. – Security Hound Dec 03 '12 at 17:34
  • 1
    Right now I'm not doing anything. This is a third-party application that is seemingly able to override the maximum imposed cursor size set by Microsoft, and I would like to replicate the same behaviour. – DTI-Matt Dec 03 '12 at 17:38
  • Transparent cursor and some desktop painting perhaps? – Lloyd Dec 03 '12 at 17:46
  • 1
    There is a program called ZoomText, which enlarges the screen, but you can set the zoom to 1x, and make the cursor pretty big. They provide a 60 day trial, so maybe you can use that to see what they do? – Ryan B Dec 03 '12 at 19:40
  • 219MB for a Zoom feature. Hahah. But I'll check it out, don't know if it'll put me further ahead than just "Yes, these folks can do it too" but it's worth a shot! Thanks! – DTI-Matt Dec 03 '12 at 19:46
  • @RyanB ZoomText doesn't work on Windows 7, it requires a Vista driver. So even if they are able to do it, it wouldn't be a viable solution for my needs. Thanks for the tip though! – DTI-Matt Dec 04 '12 at 13:59
  • @DTI-Matt What version are you using? I have loaded ZT about 5x on my test box, and fired it up before replying. Never had a driver error... – Ryan B Dec 04 '12 at 14:18
  • It was the latest from their website. 10.x.x. I have a feeling it's due to the specialized graphics cards and drivers in the machine that causes it to not run. So I'm not going to break my head over it, considering even if it was working it would serve only to confirm that what I'd like to do is somewhat possible. I still need to find a workable solution for our own software. However, I appreciate you taking the time to help me out! :) – DTI-Matt Dec 04 '12 at 14:40
  • That could be the reason. I would give them a call and ask. No sweat, glad to help – Ryan B Dec 06 '12 at 13:46

4 Answers4

8

Using SetSystemCursor it is possible to set a cursor to an image of much larger size than the standard cursor.

Here is a class I have for resizing the system cursors:

using System;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    class SystemCursors
    {
        [DllImport("user32.dll")]
        static extern bool SetSystemCursor(IntPtr hcur, uint id);

        enum CursorShift
        {
            Centered,
            LowerRight,
        }

        public static void SetSystemCursorsSize(int newSize)
        {
            ResizeCursor(System.Windows.Forms.Cursors.AppStarting, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.Arrow, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.Cross, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.Hand, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.Help, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.HSplit, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.IBeam, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.No, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.NoMove2D, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.NoMoveHoriz, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.NoMoveVert, newSize, CursorShift.LowerRight);
            ResizeCursor(System.Windows.Forms.Cursors.PanEast, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanNE, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanNorth, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanNW, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanSE, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanSouth, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanSW, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.PanWest, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.SizeAll, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.SizeNESW, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.SizeNS, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.SizeNWSE, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.SizeWE, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.UpArrow, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.VSplit, newSize, CursorShift.Centered);
            ResizeCursor(System.Windows.Forms.Cursors.WaitCursor, newSize, CursorShift.LowerRight);
        }

        private static void ResizeCursor(System.Windows.Forms.Cursor cursor, 
            int newSize, CursorShift cursorShift)
        {
            Bitmap cursorImage = GetSystemCursorBitmap(cursor);
            cursorImage = ResizeCursorBitmap(cursorImage, new Size(newSize, newSize), cursorShift);
            SetCursor(cursorImage, getResourceId(cursor));
        }

        public static Bitmap GetSystemCursorBitmap(System.Windows.Forms.Cursor cursor)
        {
            Bitmap bitmap = new Bitmap(
                cursor.Size.Width, cursor.Size.Height,
                System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            Graphics graphics = Graphics.FromImage(bitmap);

            cursor.Draw(graphics,
                new Rectangle(new Point(0, 0), cursor.Size));

            bitmap = Crop(bitmap);

            return bitmap;
        }

        private static Bitmap Crop(Bitmap bmp)
        {
            //code from http://stackoverflow.com/a/10392379/935052

            int w = bmp.Width;
            int h = bmp.Height;

            Func<int, bool> allWhiteRow = row =>
            {
                for (int i = 0; i < w; ++i)
                    if (bmp.GetPixel(i, row).A != 0)
                        return false;
                return true;
            };

            Func<int, bool> allWhiteColumn = col =>
            {
                for (int i = 0; i < h; ++i)
                    if (bmp.GetPixel(col, i).A != 0)
                        return false;
                return true;
            };

            int topmost = 0;
            for (int row = 0; row < h; ++row)
            {
                if (allWhiteRow(row))
                    topmost = row;
                else break;
            }

            int bottommost = 0;
            for (int row = h - 1; row >= 0; --row)
            {
                if (allWhiteRow(row))
                    bottommost = row;
                else break;
            }

            int leftmost = 0, rightmost = 0;
            for (int col = 0; col < w; ++col)
            {
                if (allWhiteColumn(col))
                    leftmost = col;
                else
                    break;
            }

            for (int col = w - 1; col >= 0; --col)
            {
                if (allWhiteColumn(col))
                    rightmost = col;
                else
                    break;
            }

            if (rightmost == 0) rightmost = w; // As reached left
            if (bottommost == 0) bottommost = h; // As reached top.

            int croppedWidth = rightmost - leftmost;
            int croppedHeight = bottommost - topmost;

            if (croppedWidth == 0) // No border on left or right
            {
                leftmost = 0;
                croppedWidth = w;
            }

            if (croppedHeight == 0) // No border on top or bottom
            {
                topmost = 0;
                croppedHeight = h;
            }

            try
            {
                var target = new Bitmap(croppedWidth, croppedHeight);
                using (Graphics g = Graphics.FromImage(target))
                {
                    g.DrawImage(bmp,
                      new RectangleF(0, 0, croppedWidth, croppedHeight),
                      new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
                      GraphicsUnit.Pixel);
                }
                return target;
            }
            catch (Exception ex)
            {
                throw new Exception(
                  string.Format("Values are topmost={0} btm={1} left={2} right={3} croppedWidth={4} croppedHeight={5}", topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight),
                  ex);
            }
        }

        private static Bitmap ResizeCursorBitmap(Bitmap bitmap, Size size, CursorShift cursorShift)
        {
            if (size.Width > 32)
            {
                //shifting must occur
                Bitmap intermediateBitmap = new Bitmap(64, 64);
                Graphics intermediateGraphics = Graphics.FromImage(intermediateBitmap);
                if (cursorShift == CursorShift.LowerRight)
                    //place the mouse cursor in the lower right hand quadrant of the bitmap
                    intermediateGraphics.DrawImage(bitmap,
                        intermediateBitmap.Width / 2, intermediateBitmap.Height / 2);
                else if (cursorShift == CursorShift.Centered)
                    intermediateGraphics.DrawImage(bitmap,
                        intermediateBitmap.Width / 2 - bitmap.Width / 2,
                        intermediateBitmap.Height / 2 - bitmap.Height / 2);

                //now we have a shifted bitmap; use it to draw the resized cursor
                //Bitmap finalBitmap = new Bitmap(intermediateBitmap, size);    //normal quality
                Bitmap finalBitmap = new Bitmap(size.Width, size.Height);
                Graphics finalGraphics = Graphics.FromImage(finalBitmap);
                finalGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                finalGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                finalGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                finalGraphics.DrawImage(intermediateBitmap, 0, 0, finalBitmap.Width, finalBitmap.Height);
                return finalBitmap;
            }
            else
            {
                Bitmap newBitmap = new Bitmap(bitmap, size);
                return newBitmap;
            }
        }

        private static uint getResourceId(System.Windows.Forms.Cursor cursor)
        {
            FieldInfo fi = typeof(System.Windows.Forms.Cursor).GetField(
                "resourceId", BindingFlags.NonPublic | BindingFlags.Instance);
            object obj = fi.GetValue(cursor);
            return Convert.ToUInt32((int)obj);
        }

        private static void SetCursor(Bitmap bitmap, uint whichCursor)
        {
            IntPtr ptr = bitmap.GetHicon();
            bool retval = SetSystemCursor(ptr, whichCursor);
        }

    }
}

It works by getting the current system cursor as provided in System.Windows.Forms.Cursors and making an image from it with Cursor.Draw. The image is then resized to the desired size. This requires shifting the cursor image, either to the lower right hand corner (like for arrow pointers) or centering the cursor image inside the larger image (like for Cross and IBeam).

You can use your own image for the cursor if desired, bypassing all the resizing code. Just supply the Bitmap to SetCursor.

Once the new cursor image is ready, the last piece of data needed is the ID of the cursor we are trying to replace. Each System.Windows.Forms.Cursor does contain this information, but in a private variable, so reflection is used to get the value. If you prefer to avoid reflection, you could build a table of these values instead. See MSDN SetSystemCursor for a list of values.

To use the class just call

SystemCursors.SetSystemCursorsSize(128);
endofzero
  • 1,830
  • 1
  • 22
  • 33
  • Hey so this is definitely the closest I've ever gotten to what I was looking for. I'm going to snoop around a bit, but the only issue with this is that there is no way to go back to the real default set of cursors, and if you were to go back and forth by calling `SetSystemCursorSize` from say, 32 to 128 to 32 over and over it'll become so distorted that it eventually ends up as a black square. Like I said, I'll look around but was just curious if you had any ideas! Thanks for the post! – DTI-Matt Nov 26 '13 at 18:45
  • If you know in advance what sizes you will need, you could save each cursor as an image file, and then load each size as needed. Something like `SetCursor(new Bitmap(Image.FromFile(arrow32.png)), ...);`. For images larger than 32, you will need to save the image shifted, like `ResizeCursorBitmap` does. Another possibility would be to keep a copy of the Bitmap of each cursor when the program starts up, and then resize and shift, or just restore the original. – endofzero Nov 26 '13 at 20:02
  • 3
    Unfortunately this does not work in Windows 10. Even when loading a larger external bitmap with `SetCursor` it gets resized to just 32x32. – kwill Jan 04 '18 at 19:34
7

If you are working with WPF, you can create your own Mouse cursor and assign it. But this is not a system wide mouse cursor change. Here's some code that would do the trick. In the code below I am creating a cursor 50x50 pixels. You can draw your own shape on to the RenderTargetBitmap

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Mouse.OverrideCursor = CreateCursor(50,50, Brushes.Gold, null);
    }

    Cursor CreateCursor(double rx, double ry, SolidColorBrush brush, Pen pen)
    {
        var vis = new DrawingVisual();
        using (var dc = vis.RenderOpen())
        {
            dc.DrawRectangle(brush, new Pen(Brushes.Black, 0.1), new Rect(0, 0, rx, ry));
            dc.Close();
        }
        var rtb = new RenderTargetBitmap(64, 64, 96, 96, PixelFormats.Pbgra32);
        rtb.Render(vis);

        using (var ms1 = new MemoryStream())
        {
            var penc = new PngBitmapEncoder();
            penc.Frames.Add(BitmapFrame.Create(rtb));
            penc.Save(ms1);

            var pngBytes = ms1.ToArray();
            var size = pngBytes.GetLength(0);

            //.cur format spec http://en.wikipedia.org/wiki/ICO_(file_format)
            using (var ms = new MemoryStream())
            {
                {//ICONDIR Structure
                    ms.Write(BitConverter.GetBytes((Int16)0), 0, 2);//Reserved must be zero; 2 bytes
                    ms.Write(BitConverter.GetBytes((Int16)2), 0, 2);//image type 1 = ico 2 = cur; 2 bytes
                    ms.Write(BitConverter.GetBytes((Int16)1), 0, 2);//number of images; 2 bytes
                }

                {//ICONDIRENTRY structure
                    ms.WriteByte(32); //image width in pixels
                    ms.WriteByte(32); //image height in pixels

                    ms.WriteByte(0); //Number of Colors in the color palette. Should be 0 if the image doesn't use a color palette
                    ms.WriteByte(0); //reserved must be 0

                    ms.Write(BitConverter.GetBytes((Int16)(rx / 2.0)), 0, 2);//2 bytes. In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left.
                    ms.Write(BitConverter.GetBytes((Int16)(ry / 2.0)), 0, 2);//2 bytes. In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top.

                    ms.Write(BitConverter.GetBytes(size), 0, 4);//Specifies the size of the image's data in bytes
                    ms.Write(BitConverter.GetBytes((Int32)22), 0, 4);//Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file
                }

                ms.Write(pngBytes, 0, size);//write the png data.
                ms.Seek(0, SeekOrigin.Begin);
                return new Cursor(ms);
            }
        }
    }

}
Indy9000
  • 8,651
  • 2
  • 32
  • 37
  • I appreciate the help and the code sample, but unfortunately it would be required system-wide. I'm not sure what kind of black magic these folks worked, but I'm determined to figure it out :P Thanks again! – DTI-Matt Dec 03 '12 at 18:24
2

Add these imports to your class:

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr LoadImage(IntPtr hinst, string lpszName, uint uType, int cxDesired, int cyDesired, uint fuLoad);

[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);

read msdn articles about LoadImage and SetSystemCursor, especially about parameters they use.

Then use functions in your code:

// "cursor.cur" - cursor image of any size you want
// 2 == IMAGE_CURSOR (from Winuser.h)
// cxDesired = 0 and cyDesired = 0, using original image size
// 0x8010 == LR_DEFAULTCOLOR | LR_SHARED | LR_LOADFROMFILE (from Winuser.h)
var ptr = LoadImage(IntPtr.Zero, "cursor.cur", 2, 0, 0, 0x8010);
if(ptr != IntPtr.Zero)
{
    SetSystemCursor(ptr, 32512); // 32512 == OCR_NORMAL (from Winuser.h)
}
intrueder
  • 445
  • 5
  • 11
1

I Solve this problem by Useing intrueder's way.Supplement, the API LoadImage cannot Load.png. Use System.drawing.BitMap.Like this:Bitmap bmp = new Bitmap(str, true); IntPtr p = bmp.GetHicon(); SetSystemCursor(p, OCR_NORMAL);

hyx
  • 11
  • 1