Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Changing the system cursor


  • Please log in to reply
53 replies to this topic
Serenity
  • Members
  • 1271 posts
  • Last active:
  • Joined: 07 Nov 2004
I thought I'd write a guide to hopefully make this more accessible to others.

Update 2008/09/10 - Wrapper function released. (See end of post).

The first thing is decide what kind of cursor to use - a system cursor, or a cursor loaded from a file? It is also possible to load a cursor from within a script, see Crazy Scripting : Include an Icon in your script.

Using a system cursor

These are our cursor IDs:

IDC_ARROW := 32512
IDC_IBEAM := 32513
IDC_WAIT := 32514
IDC_CROSS := 32515
IDC_UPARROW := 32516
IDC_SIZE := 32640
IDC_ICON := 32641
IDC_SIZENWSE := 32642
IDC_SIZENESW := 32643
IDC_SIZEWE := 32644
IDC_SIZENS := 32645
IDC_SIZEALL := 32646
IDC_NO := 32648
IDC_HAND := 32649
IDC_APPSTARTING := 32650
IDC_HELP := 32651

To load a system cursor use LoadCursor, which returns a handle to use in SetSystemCursor. Here I've used IDC_SIZEALL:

CursorHandle := DllCall( "LoadCursor", Uint,0, Int,IDC_SIZEALL )

Loading a cursor from a file

If you want to load a cursor from a file, use LoadCursorFromFile instead. You can also load an icon file here:

Cursor = %A_ScriptDir%\mycursor.ani
CursorHandle := DllCall( "LoadCursorFromFile", Str,Cursor )

Size of cursor

We can change the size of the cursor with CopyImage, although this method stretches it so the end look may not be satisfactory. This step is optional:

; uType:
IMAGE_BITMAP := 0x0
IMAGE_CURSOR := 0x2
IMAGE_ICON := 0x1

; Size:
cx := 32, cy := cx 

; fuFlags:
LR_COPYFROMRESOURCE := 0x4000

CursorHandle := DllCall( "CopyImage", uint,CursorHandle, uint,IMAGE_CURSOR, int,cx, int,cy, uint,0 )

We can use LoadImage instead of LoadCursorFromFile to load non-standard size icons and cursors. I used this method to load a 48x48 icon when my system is set to 32x32. I also tried this with multi icon and it seems to always load the largest icon.

Note: LoadCursorFromFile will load other size icons and cursors but it seems to force the system settings onto it as it loads. The result after a resize via CopyImage isn't as nice even with the LR_COPYFROMRESOURCE option.

Image = %A_ScriptDir%\mymulti.ico

; uType:
IMAGE_BITMAP := 0x0
IMAGE_CURSOR := 0x2
IMAGE_ICON := 0x1

; Size:
cx := 48, cy := cx

; fuLoad:
LR_COLOR := 0x2
LR_CREATEDIBSECTION := 0x2000
LR_DEFAULTSIZE := 0x40
LR_LOADFROMFILE := 0x10
LR_LOADMAP3DCOLORS := 0x1000

CursorHandle := DllCall( "LoadImageA", UInt,0, Str,Image, UInt,IMAGE_ICON, Int,cx, Int,cy, UInt,0x10 )

Loading from a DLL or EXE

It may also be possible to load a cursor or icon from a DLL or EXE file. Here we need GetModuleHandle to get a handle for LoadImage, and MAKEINTRESOURCE to load a resource by ordinal. If anyone knows how to use MAKEINTRESOURCE macro within DllCall do post.

; uType:
IMAGE_BITMAP := 0x0
IMAGE_CURSOR := 0x2
IMAGE_ICON := 0x1

; Size:
cx := 48, cy := cx

; fuLoad:
LR_COLOR := 0x2
LR_CREATEDIBSECTION := 0x2000
LR_DEFAULTSIZE := 0x40
LR_LOADFROMFILE := 0x10
LR_LOADMAP3DCOLORS := 0x1000

Module = %A_WinDir%\SYSTEM32\SHELL32.DLL
ModuleHandle := DllCall("GetModuleHandleA", Str,Module)
CursorHandle := DllCall( "LoadImageA", Uint,ModuleHandle, Str,Cursor, Uint,IMAGE_ICON, Int,cx, Int,cy, UInt,0x10 )

Replacing the cursors

Now that we've loaded a cursor, the next step is to loop through a list of system cursors to replace all of them:

Cursors = 32512,32513,32514,32515,32516,32640,32641,32642,32643,32644,32645,32646,32648,32649,32650,32651
Loop, Parse, Cursors, `,
{
	DllCall( "SetSystemCursor", Uint,CursorHandle, Int,A_Loopfield )
}

Restoring system cursors

To restore system cursors use SystemParametersInfo/SPI_SETCURSORS:

SPI_SETCURSORS := 0x57
DllCall( "SystemParametersInfo", UInt,SPI_SETCURSORS, UInt,0, UInt,0, UInt,0 ) ; Reload the system cursors

Example script: The following changes the cursor while the MButton is down, and restores it on release.

#SingleInstance Force

~MButton::SetSystemCursor() 
~MButton Up::RestoreCursors()

SetSystemCursor()
{
	IDC_SIZEALL := 32646
	CursorHandle := DllCall( "LoadCursor", Uint,0, Int,IDC_SIZEALL )
	Cursors = 32512,32513,32514,32515,32516,32640,32641,32642,32643,32644,32645,32646,32648,32649,32650,32651
	Loop, Parse, Cursors, `,
	{
		DllCall( "SetSystemCursor", Uint,CursorHandle, Int,A_Loopfield )
	}
}

RestoreCursors() 
{
	SPI_SETCURSORS := 0x57
	DllCall( "SystemParametersInfo", UInt,SPI_SETCURSORS, UInt,0, UInt,0, UInt,0 )
}

Wrapper function

This covers nearly everything in a single function. I've left out RestoreCursors as I think it should be seperate.

SetSystemCursor( File path or cursor name, Width, Height )

Parameter 1 is file path or cursor name, e.g. IDC_SIZEALL. If this is omitted it will hide the cursor.
Parameters 2 and 3 are the desired width and height of cursor. Omit these to use the default size, e.g. loading a 48x48 cursor will display as 48x48.

Changelog
2008/09/13 - Set cursor now shows everywhere bar apps that set their own cursor
2008/09/10 - Wrapper function released
SetSystemCursor( Cursor = "", cx = 0, cy = 0 )
{
	BlankCursor := 0, SystemCursor := 0, FileCursor := 0 ; init
	
	SystemCursors = 32512IDC_ARROW,32513IDC_IBEAM,32514IDC_WAIT,32515IDC_CROSS
	,32516IDC_UPARROW,32640IDC_SIZE,32641IDC_ICON,32642IDC_SIZENWSE
	,32643IDC_SIZENESW,32644IDC_SIZEWE,32645IDC_SIZENS,32646IDC_SIZEALL
	,32648IDC_NO,32649IDC_HAND,32650IDC_APPSTARTING,32651IDC_HELP
	
	If Cursor = ; empty, so create blank cursor 
	{
		VarSetCapacity( AndMask, 32*4, 0xFF ), VarSetCapacity( XorMask, 32*4, 0 )
		BlankCursor = 1 ; flag for later
	}
	Else If SubStr( Cursor,1,4 ) = "IDC_" ; load system cursor
	{
		Loop, Parse, SystemCursors, `,
		{
			CursorName := SubStr( A_Loopfield, 6, 15 ) ; get the cursor name, no trailing space with substr
			CursorID := SubStr( A_Loopfield, 1, 5 ) ; get the cursor id
			SystemCursor = 1
			If ( CursorName = Cursor )
			{
				CursorHandle := DllCall( "LoadCursor", Uint,0, Int,CursorID )	
				Break					
			}
		}	
		If CursorHandle = ; invalid cursor name given
		{
			Msgbox,, SetCursor, Error: Invalid cursor name
			CursorHandle = Error
		}
	}	
	Else If FileExist( Cursor )
	{
		SplitPath, Cursor,,, Ext ; auto-detect type
		If Ext = ico 
			uType := 0x1	
		Else If Ext in cur,ani
			uType := 0x2		
		Else ; invalid file ext
		{
			Msgbox,, SetCursor, Error: Invalid file type
			CursorHandle = Error
		}		
		FileCursor = 1
	}
	Else
	{	
		Msgbox,, SetCursor, Error: Invalid file path or cursor name
		CursorHandle = Error ; raise for later
	}
	If CursorHandle != Error 
	{
		Loop, Parse, SystemCursors, `,
		{
			If BlankCursor = 1 
			{
				Type = BlankCursor
				%Type%%A_Index% := DllCall( "CreateCursor"
				, Uint,0, Int,0, Int,0, Int,32, Int,32, Uint,&AndMask, Uint,&XorMask )
				CursorHandle := DllCall( "CopyImage", Uint,%Type%%A_Index%, Uint,0x2, Int,0, Int,0, Int,0 )
				DllCall( "SetSystemCursor", Uint,CursorHandle, Int,SubStr( A_Loopfield, 1, 5 ) )
			}			
			Else If SystemCursor = 1
			{
				Type = SystemCursor
				CursorHandle := DllCall( "LoadCursor", Uint,0, Int,CursorID )	
				%Type%%A_Index% := DllCall( "CopyImage"
				, Uint,CursorHandle, Uint,0x2, Int,cx, Int,cy, Uint,0 )		
				CursorHandle := DllCall( "CopyImage", Uint,%Type%%A_Index%, Uint,0x2, Int,0, Int,0, Int,0 )
				DllCall( "SetSystemCursor", Uint,CursorHandle, Int,SubStr( A_Loopfield, 1, 5 ) )
			}
			Else If FileCursor = 1
			{
				Type = FileCursor
				%Type%%A_Index% := DllCall( "LoadImageA"
				, UInt,0, Str,Cursor, UInt,uType, Int,cx, Int,cy, UInt,0x10 ) 
				DllCall( "SetSystemCursor", Uint,%Type%%A_Index%, Int,SubStr( A_Loopfield, 1, 5 ) )			
			}          
		}
	}	
}

RestoreCursors()
{
	SPI_SETCURSORS := 0x57
	DllCall( "SystemParametersInfo", UInt,SPI_SETCURSORS, UInt,0, UInt,0, UInt,0 )
}

"Anything worth doing is worth doing slowly." - Mae West
Posted Image

jaco0646
  • Moderators
  • 3165 posts
  • Last active: Apr 01 2014 01:46 AM
  • Joined: 07 Oct 2006
That's a neat trick! Switching cursors is a long-awaited feature. I've never seen it coded that concisely. Thank you!

Serenity
  • Members
  • 1271 posts
  • Last active:
  • Joined: 07 Nov 2004
Thanks Jaco. :)

I wrote a wrapper function which covers nearly everything. See my first post for details.
"Anything worth doing is worth doing slowly." - Mae West
Posted Image

freakkk
  • Members
  • 182 posts
  • Last active: Dec 16 2014 06:23 PM
  • Joined: 29 Jul 2005
Nice! :D

You may want to point out how this one differs from..

[fun] SetCursor 1.1 - Set cursor shape for control or window

..since it shares the same stdlib name, but this has been often requested.

Thx!

Serenity
  • Members
  • 1271 posts
  • Last active:
  • Joined: 07 Nov 2004
Thanks, I didn't know about that. I'll rename this one to SetSystemCursor since it changes it system-wide.
"Anything worth doing is worth doing slowly." - Mae West
Posted Image

derRaphael
  • Members
  • 872 posts
  • Last active: Mar 19 2013 04:42 PM
  • Joined: 23 Nov 2007
hi, there

i have been testing ur script, and it seems like there is a bug when being used on windows 2k

#SingleInstance Force

space::SetSystemCursor("IDC_NO")
space Up::RestoreCursors()
esc::exitapp

this was the code i used and it seems that there is a bug. when i hold space (expecting my cursor to display the slashed circle) its hows the circle for a splitsecond and then starts cycling mad thru all available cursor shapes.

i guess it is not the desired result.

greets
dR

All scripts, unless otherwise noted, are hereby released under CC-BY

Slanter
  • Members
  • 739 posts
  • Last active: Jul 08 2011 05:26 AM
  • Joined: 28 May 2008
There are also some interesting results when you change the cursor and then mouse over something that also tries to change your cursor (like edit fields and the edges of re-sizable windows). I'm running win2k as well.
Unless otherwise stated, all code is untested

(\__/) This is Bunny.
(='.'=) Cut, copy, and paste bunny onto your sig.
(")_(") Help Bunny gain World Domination.

Centra
  • Guests
  • Last active:
  • Joined: --
Can someone pls show me how to make all system Cursors, be just a simple arrow? That is, no matter what, I just have a arrow... no hourglass, etc :)

Serenity
  • Members
  • 1271 posts
  • Last active:
  • Joined: 07 Nov 2004

when i hold space (expecting my cursor to display the slashed circle) its hows the circle for a splitsecond and then starts cycling mad thru all available cursor shapes.


Hi, thanks for posting. The same thing happens for me on 2k. I tried calling the function with a 100ms timer and got the same thing, so this is what happens when it is called repeatedly. It seems holding down a key has the same effect; this doesn't happen with a mouse button or if called normally.

I tried adding the following and results are the same. If I add a sleep of 1000-2000ms after calling the function it slows it down a little, but the calls seem to be buffered so it still cycles, albeit at a slower rate.

#MaxThreadsPerHotkey 1
#MaxThreads 1

There are also some interesting results when you change the cursor and then mouse over something that also tries to change your cursor (like edit fields and the edges of re-sizable windows).


What happens, and how are you using the function? From my testing if the app has changed the cursor (Photoshop for instance) it takes priority and it is as if the function was never called, but only for these controls, then it changes back.

Can someone pls show me how to make all system Cursors, be just a simple arrow? That is, no matter what, I just have a arrow... no hourglass, etc Smile


Try calling the wrapper function with the cursor name IDC_ARROW.

If you really mean no matter what, you could use Resource Hacker to change them in system DLLs such as COMCTL32.DLL but this won't work for apps that load custom cursors, you'll have to change these as well.

You could also try changing them via Control Panel/Mouse/Pointers or replacing the files in %WinDir%\Cursors.
"Anything worth doing is worth doing slowly." - Mae West
Posted Image

Centra
  • Guests
  • Last active:
  • Joined: --
I am impressed with that wrapper function! I am also intimidated by its power, but yes, I would like to perform this for my apps and not worry about other apps that may overide.

but when I try,

SetSystemCursor("IDC_ARROW", 700, 700)

I get a nice big arrow... but still get the Hourglass/arrow.

Cursors = 32512,32513,32514,32515,32516,32640,32641,32642,32643,32644,32645,32646,32648,32649,32650,32651
Loop, Parse, Cursors, `,
{
   DllCall( "SetSystemCursor", Uint,CursorHandle, Int,A_Loopfield )
} 


I keyed in on this above code... but don't throughly understand it. Do you mean it is possible to loop through all possible cursors, and replace with a straight arrow? :) Best regards, Centra

Serenity
  • Members
  • 1271 posts
  • Last active:
  • Joined: 07 Nov 2004
Thanks Centra. Under what condition does the hourglass cursor show? Is it normal size?

I keyed in on this above code... but don't throughly understand it. Do you mean it is possible to loop through all possible cursors, and replace with a straight arrow?


Yes. The loop just replaces what would be sixteen different DllCalls to replace each cursor. To understand it better try this:

Cursors = 32512,32513,32514,32515,32516,32640,32641,32642,32643,32644,32645,32646,32648,32649,32650,32651
Loop, Parse, Cursors, `,
{
	; DllCall( "SetSystemCursor", Uint,CursorHandle, Int,A_Loopfield )
	Msgbox % A_Loopfield
}

"Anything worth doing is worth doing slowly." - Mae West
Posted Image

Centra
  • Guests
  • Last active:
  • Joined: --
Aie... but can you pls oblige if possible, ... what can the proper script/procedure to achieve my earlier goal?? I am just too confused by all the cryptic ID codes here, but I think I grasp the concept of what is going on from what you show me, but I just don't see all the paths and possibilities :| :oops:

Krogdor
  • Members
  • 1391 posts
  • Last active: Jun 08 2011 05:31 AM
  • Joined: 18 Apr 2008

when i hold space (expecting my cursor to display the slashed circle) its hows the circle for a splitsecond and then starts cycling mad thru all available cursor shapes.


Hi, thanks for posting. The same thing happens for me on 2k. I tried calling the function with a 100ms timer and got the same thing, so this is what happens when it is called repeatedly. It seems holding down a key has the same effect; this doesn't happen with a mouse button or if called normally.

I tried adding the following and results are the same. If I add a sleep of 1000-2000ms after calling the function it slows it down a little, but the calls seem to be buffered so it still cycles, albeit at a slower rate.

#MaxThreadsPerHotkey 1
#MaxThreads 1


Perhaps try
space::
SetSystemCursor("IDC_NO")
KeyWait, Space
RestoreCursors()


Slanter
  • Members
  • 739 posts
  • Last active: Jul 08 2011 05:26 AM
  • Joined: 28 May 2008

There are also some interesting results when you change the cursor and then mouse over something that also tries to change your cursor (like edit fields and the edges of re-sizable windows).


What happens, and how are you using the function? From my testing if the app has changed the cursor (Photoshop for instance) it takes priority and it is as if the function was never called, but only for these controls, then it changes back.


In edit fields for example where the "I" type cursor is showing up, if I hit the hotkey it turns into the normal cursor. If I'm at the top of a resizable window it normally shows the double vertical arrow, when I hit the hotkey it switches to horizontal. There are several more that I've seen, but it looks to me like it's just cycling to the next cursor or something.

#SingleInstance Force

space::
   SetSystemCursor("IDC_NO")
   KeyWait, Space
   RestoreCursors()
Return
esc::exitapp

Unless otherwise stated, all code is untested

(\__/) This is Bunny.
(='.'=) Cut, copy, and paste bunny onto your sig.
(")_(") Help Bunny gain World Domination.

Serenity
  • Members
  • 1271 posts
  • Last active:
  • Joined: 07 Nov 2004
Thanks Slanter, I can reproduce this. The solution is to always use CopyImage after LoadCursor. I've updated the function to do so. This also solves the bug reported by DerRaphael. Unfortunately the function will always be overridden by controls such as the resize border of windows and edit fields, but at least they now display correctly.
"Anything worth doing is worth doing slowly." - Mae West
Posted Image