SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post your working scripts, libraries and tools for AHK v1.1 and older
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 12 May 2020, 18:39

SetSystemCursor() and RestoreCursor()

Original by Serenity - https://autohotkey.com/board/topic/32608-changing-the-system-cursor/
I touched up the code and fixed its logic.
  • SetSystemCursor() - Changes all cursors to invisible. Now over 10% faster.
  • SetSystemCursor("Cross") - Changes all cursors to the same resource. Now over 10% faster.
  • SetSystemCursor("Working.ani") - Loads an animated cursor from a file. Now over 200% faster.
Some small changes:
  • Fix memory leaks.
  • Animated Cursors on Serenity's original script never worked on AutoHotkey Unicode.
  • Cursor names are consistent with AutoHotkey's A_Cursor. The IDC_ prefix can be omitted.
  • Removed obsolete cursors IDC_ICON and IDC_SIZE. They were only valid on Windows 95 and earlier.
https://github.com/iseahound/SetSystemCursor

Code: Select all

; Source:   Serenity - https://autohotkey.com/board/topic/32608-changing-the-system-cursor/
; Modified: iseahound - https://www.autohotkey.com/boards/viewtopic.php?t=75867

SetSystemCursor(Cursor := "", cx := 0, cy := 0) {

   static SystemCursors := {APPSTARTING: 32650, ARROW: 32512, CROSS: 32515, HAND: 32649, HELP: 32651, IBEAM: 32513, NO: 32648
                        ,  SIZEALL: 32646, SIZENESW: 32643, SIZENS: 32645, SIZENWSE: 32642, SIZEWE: 32644, UPARROW: 32516, WAIT: 32514}

   if (Cursor = "") {
      VarSetCapacity(AndMask, 128, 0xFF), VarSetCapacity(XorMask, 128, 0)

      for CursorName, CursorID in SystemCursors {
         CursorHandle := DllCall("CreateCursor", "ptr", 0, "int", 0, "int", 0, "int", 32, "int", 32, "ptr", &AndMask, "ptr", &XorMask, "ptr")
         DllCall("SetSystemCursor", "ptr", CursorHandle, "int", CursorID) ; calls DestroyCursor
      }
      return
   }

   if (Cursor ~= "^(IDC_)?(?i:AppStarting|Arrow|Cross|Hand|Help|IBeam|No|SizeAll|SizeNESW|SizeNS|SizeNWSE|SizeWE|UpArrow|Wait)$") {
      Cursor := RegExReplace(Cursor, "^IDC_")

      if !(CursorShared := DllCall("LoadCursor", "ptr", 0, "ptr", SystemCursors[Cursor], "ptr"))
         throw Exception("Error: Invalid cursor name")

      for CursorName, CursorID in SystemCursors {
         CursorHandle := DllCall("CopyImage", "ptr", CursorShared, "uint", 2, "int", cx, "int", cy, "uint", 0, "ptr")
         DllCall("SetSystemCursor", "ptr", CursorHandle, "int", CursorID) ; calls DestroyCursor
      }
      return
   }

   if FileExist(Cursor) {
      SplitPath Cursor,,, Ext ; auto-detect type
      if !(uType := (Ext = "ani" || Ext = "cur") ? 2 : (Ext = "ico") ? 1 : 0)
         throw Exception("Error: Invalid file type")

      if (Ext = "ani") {
         for CursorName, CursorID in SystemCursors {
            CursorHandle := DllCall("LoadImage", "ptr", 0, "str", Cursor, "uint", uType, "int", cx, "int", cy, "uint", 0x10, "ptr")
            DllCall("SetSystemCursor", "ptr", CursorHandle, "int", CursorID) ; calls DestroyCursor
         }
      } else {
         if !(CursorShared := DllCall("LoadImage", "ptr", 0, "str", Cursor, "uint", uType, "int", cx, "int", cy, "uint", 0x8010, "ptr"))
            throw Exception("Error: Corrupted file")

         for CursorName, CursorID in SystemCursors {
            CursorHandle := DllCall("CopyImage", "ptr", CursorShared, "uint", 2, "int", 0, "int", 0, "uint", 0, "ptr")
            DllCall("SetSystemCursor", "ptr", CursorHandle, "int", CursorID) ; calls DestroyCursor
         }
      }
      return
   }

   throw Exception("Error: Invalid file path or cursor name")
}

RestoreCursor() {
   return DllCall("SystemParametersInfo", "uint", SPI_SETCURSORS := 0x57, "uint", 0, "ptr", 0, "uint", 0)
}
Last edited by iseahound on 02 Nov 2021, 12:58, edited 9 times in total.

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by Helgef » 13 May 2020, 08:43

Looks like you got a few types for dllcall wrong. For example, handles should be ptr, both for passing and returning.

Thanks for sharing, cheers.


Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by Helgef » 16 May 2020, 02:27

You didn't correct any of the return values, which is probably the only ones which actually matters in practice. For example LoadCursor returns a handle, and takes two ptr-sized args,

Code: Select all

// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadcursorw
HCURSOR LoadCursorW(
  HINSTANCE hInstance,
  LPCWSTR   lpCursorName
);
You also changed some int args to uint, I don't know why.

Cheers.


User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by haichen » 17 May 2020, 10:11

There is the possibility to include a bitmap in a script.
https://github.com/mmikeww/image2include

Can I use a handle to such a bitmap as cursor?

I tried to add the following to your function, but without success:

Code: Select all

SetSystemCursor(Cursor := "", cx := 0, cy := 0, is_handle := 0) {

   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 (is_handle) {
      Loop Parse, SystemCursors, % ","
      {
        CursorHandle := DllCall("CopyImage", "ptr", Cursor, "uint", 2, "int", cx, "int", cy, "uint", 0, "ptr")
        DllCall("SetSystemCursor", "ptr", CursorHandle, "int", SubStr(A_LoopField, 1, 5)) ; calls DestroyCursor
      }
      return
      ;....
   }

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 17 May 2020, 11:38

Does this work?

Code: Select all

; Source:   Serenity - https://autohotkey.com/board/topic/32608-changing-the-system-cursor/
; Modified: iseahound

SetSystemCursor(Cursor := "", cx := 0, cy := 0) {

   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 = "") {
      VarSetCapacity(AndMask, 128, 0xFF), VarSetCapacity(XorMask, 128, 0)

      Loop Parse, SystemCursors, % ","
      {
         CursorHandle := DllCall("CreateCursor", "ptr", 0, "int", 0, "int", 0, "int", 32, "int", 32, "ptr", &AndMask, "ptr", &XorMask, "ptr")
         DllCall("SetSystemCursor", "ptr", CursorHandle, "int", SubStr(A_LoopField, 1, 5)) ; calls DestroyCursor
      }
      return
   }

   if SubStr(Cursor, 1, 4) = "IDC_" {
      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
      } until (CursorName = Cursor)

      if !(CursorShared:= DllCall("LoadCursor", "ptr", 0, "ptr", CursorID, "ptr"))
         throw Exception("Error: Invalid cursor name")

      Loop Parse, SystemCursors, % ","
      {
         CursorHandle := DllCall("CopyImage", "ptr", CursorShared, "uint", 2, "int", cx, "int", cy, "uint", 0, "ptr")
         DllCall("SetSystemCursor", "ptr", CursorHandle, "int", SubStr(A_LoopField, 1, 5)) ; calls DestroyCursor
      }
      return
   }

   if FileExist(Cursor) {
      SplitPath, Cursor,,, Ext ; auto-detect type
      if ((uType := (Ext = "ani" || Ext = "cur") ? 2 : (Ext = "ico") ? 1 : (Ext = "bmp") ? 0 : "") == "")
         throw Exception("Error: Invalid file type")

      if (Ext = "ani") {
         Loop Parse, SystemCursors, % ","
         {
            CursorHandle := DllCall("LoadImage", "ptr", 0, "str", Cursor, "uint", uType, "int", cx, "int", cy, "uint", 0x10, "ptr")
            DllCall("SetSystemCursor", "ptr", CursorHandle, "int", SubStr(A_LoopField, 1, 5)) ; calls DestroyCursor
         }
      } else {
         if !(CursorShared := DllCall("LoadImage", "ptr", 0, "str", Cursor, "uint", uType, "int", cx, "int", cy, "uint", 0x00008010, "ptr"))
            throw Exception("Error: Corrupted file")

         Loop Parse, SystemCursors, % ","
         {
            CursorHandle := DllCall("CopyImage", "ptr", CursorShared, "uint", 2, "int", 0, "int", 0, "uint", 0, "ptr")
            DllCall("SetSystemCursor", "ptr", CursorHandle, "int", SubStr(A_LoopField, 1, 5)) ; calls DestroyCursor
         }
      }
      return
   }

   if (DllCall("GetObjectType", "ptr", Cursor) == 7) {
      Loop Parse, SystemCursors, % ","
      {
         CursorHandle := DllCall("CopyImage", "ptr", Cursor, "uint", 0, "int", 0, "int", 0, "uint", 0, "ptr")
         DllCall("SetSystemCursor", "ptr", CursorHandle, "int", SubStr(A_LoopField, 1, 5)) ; calls DestroyCursor
      }
      return
   }

   throw Exception("Error: Invalid file path or cursor name")
}

RestoreCursor() {
   static SPI_SETCURSORS := 0x57
   return DllCall("SystemParametersInfo", "uint", SPI_SETCURSORS, "uint", 0, "ptr", 0, "uint", 0)
}
SetSystemCursor(handle)

User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by haichen » 18 May 2020, 01:41

Thank you very much for your quick reply.
Unfortunately, it does not work.
Here is my test script.
Maybe there's an obvious mistake I made.

Code: Select all

#include <cursor>

pic:="test.bmp"
Handle := LoadPicture(pic)

Gui, Margin, 0, 0
Gui, Add, Pic, x0 y0 w80 h50, % "HBITMAP:*" Handle
Gui, Show, , Included Image 

x::
tooltip, test
SetSystemCursor(Handle , 70, 70)            ;does not work
;SetSystemCursor("IDC_Cross", 70, 70)         ;works
return

x Up::
tooltip,
RestoreCursor()
return


iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 18 May 2020, 09:07

My mistake it is not possible to use a bitmap with this function.
https://docs.microsoft.com/en-us/windows/win32/menurc/using-cursors?redirectedfrom=MSDN#_win32_Creating_a_Cursor
https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-bitmap-flat Bitmap::GetHICON is locked in gdiplus.
https://stackoverflow.com/questions/17099962/how-do-i-create-a-cursor-object-from-a-bitmap-object

It would make this function more complex than it needs to be.

EDIT: Also the built in LoadPicture() function does something different than LoadImage(). The two values are not interchangeable.

User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by haichen » 18 May 2020, 11:28

Thank you for your efforts.
I've also tried a little more, but without new insights.

User avatar
JFHenault
Posts: 11
Joined: 10 Jun 2020, 19:21

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by JFHenault » 10 Jun 2020, 19:29

Hi,

The code below works except that the blue ring (Aero_busy_xl.ani) is not animated as supposed. It should be turning.

Does someone could help me with that?

Thanks.

Code: Select all

#SingleInstance Ignore

SetSystemCursor:
   CursorHandle := DllCall("LoadImage", "ptr", 0, "str", "C:\Windows\Cursors\aero_busy_xl.ani", "uint", 0x2, "int", 0, "int", 0, "uint", 0x10, "ptr")
   DllCall("SetSystemCursor", "ptr", CursorHandle, "int", 32512)
Return

RestoreCursors:
	DllCall("SystemParametersInfo", UInt, 0x57, UInt, 0, UInt, 0, UInt, 0)
Return

GoSub SetSystemCursor

+d::
   GoSub RestoreCursors
   ExitApp
Return

User avatar
JFHenault
Posts: 11
Joined: 10 Jun 2020, 19:21

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by JFHenault » 10 Jun 2020, 23:36

I think I found something.

The code below use SetCursor instead of SetSystemCursor, which makes the .ani file animating as wanted. But using SetCursor without a GUI context didn't seem to work.

So, creating a GUI almost transparent (1) with the size of my screen (Resolution : 3840x2160) is what I needed to show my animated cursor all over the screen. Making the GUI completely transparent (0) made the GUI disabled and the cursor wouldn't work.

If someone knows another way, please tell me.

Code: Select all

WaitingCursor()
{
    Gui Show, W3840 H2160, CursorWindow
    WinSet Transparent, 1, CursorWindow
    CursorHandle:=DllCall("LoadCursor", Int, 0, Int, 32514)
    DllCall("SetCursor", Int, CursorHandle)
    OnMessage(0x20, "SetCursor")
}

SetCursor()
{
    CursorHandle:=DllCall("LoadCursor", Int, 0, Int, 32514)
    DllCall("SetCursor", Int, CursorHandle)
    Return True
}

+s::
    WaitingCursor()
Return

+d::
    ExitApp
Return

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 04 May 2021, 21:23

Fixed some bugs that were breaking the script. If you have an older version please update.

@haichen You can use my function ImagePutCursor() found here: https://github.com/iseahound/ImagePut

@JFHenault The only way to use an animated cursor system wide is by editing the registry for the current user. https://stackoverflow.com/questions/42614391/setsystemcursor-not-animating-ani-cursors

doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by doubledave22 » 27 Oct 2021, 15:30

I am having trouble getting the cursor to look right when using this (or any similar) function to change the cursor type. Some of my users seem to like to increase their pointer size (in Cursor and Pointer settings) and when the cursor size is increased these functions don't display the mouse in good quality. Perhaps you can replicate this by setting pointer size to 2 or 3 then calling SetSystemCursor("HAND") to see what I mean.

I'd appreciate any help anyone can give. Can't find any resources related to increased cursor size

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 30 Oct 2021, 14:50

Looks like the legacy APIs GetCursorInfo only return a 32 x 32 bitmap for compatibility reasons. IDXGIOutputDuplication::GetFramePointerShape will get the correct software cursor.

Image

#1 - Regular cursor
#2 - Software cursor at pointer size 15. Note how it always returns a scaled down 32 x 32.
#3 & #4 - Green at pointer sizes 1 and 15.

Some of my projects ImagePut and ScreenBuffer should be able to accommodate the change, but this would balloon the code size of SetSystemCursor. But really malcev is the expert on this stuff.

Side note: The function ImagePutCursor() is able to set a cursor larger than 32 pixels, and is able to retrieve that same cursor that was set at its original size. This is definitely some weird Microsoft decision.

doubledave22
Posts: 343
Joined: 08 Jun 2019, 17:36

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by doubledave22 » 30 Oct 2021, 16:40

iseahound wrote: Looks like the legacy APIs GetCursorInfo only return a 32 x 32 bitmap for compatibility reasons. IDXGIOutputDuplication::GetFramePointerShape will get the correct software cursor.
Thanks for the info. It's not a huge deal. The hand cursor I can get to show up properly with a simple setcursor api call and since it's triggered on WM_MOUSEMOVE it stays a hand while on the things I need it for. The only other thing I use is a crosshair which you can barely tell is blurry anyway at larger sizes. The last issue was I was using some similar code to hide the mouse and when restoring the mouse it would come back blurry if the pointer size was increased. Calling RestoreCursors() after showing the mouse again seemed to fix it. I appreciate the help!

User avatar
AlphaBravo
Posts: 586
Joined: 29 Sep 2013, 22:59

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by AlphaBravo » 02 Nov 2021, 11:29

@iseahound thanks for sharing, may I suggest changing this line :
if (Cursor ~= "i)(AppStarting|Arrow|Cross|Hand|Help|IBeam|Icon|No|Size|SizeAll|SizeNESW|SizeNS|SizeNWSE|SizeWE|UpArrow|Wait)") {

to :
if (Cursor ~= "i)^(IDC_)?(AppStarting|Arrow|Cross|Hand|Help|IBeam|Icon|No|Size|SizeAll|SizeNESW|SizeNS|SizeNWSE|SizeWE|UpArrow|Wait)$") {

Guess what happened when I tried to load a cursor from a folder named c:\folderName\Icons\test.ico ;-)
Which begs the question, why "Icon" is part of RegEx if there are no cursors named "Icon"?

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 02 Nov 2021, 12:57

Thanks. I've updated the version on the first post to the latest from the github.

IDC_Icon is an obsolete cursor from Windows 95 I believe.

AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by AHK_user » 13 Mar 2022, 08:04

I encountered something strange, when set the cursor for a second time, noting Changes.
Is this behavior normal?

Code: Select all

#Include SetSystemCursor.ahk
#Include RestoreCursors.ahk

SetSystemCursor("Cross")
sleep 500
;RestoreCursors() ; If I enable this line it works.
SetSystemCursor("SizeAll")
Tooltip("SizeAll")
sleep 2000
RestoreCursors()
ExitApp 

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor - A modernization of Serenity's script

Post by iseahound » 13 Mar 2022, 11:20

SetSystemCursor overwrites the system cursors. So, (1) All system cursors are now "cross". (2) Calling setsystemcursor("sizeall") will retrieve a cross. If you want the original cursors you'll have to tell windows to restore them all.

Post Reply

Return to “Scripts and Functions (v1)”