Page 1 of 1

SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 27 Aug 2021, 15:07
by iseahound
SetSystemCursor() and RestoreCursor()
Changes the pointer. Original by Serenity.
SetSystemCursor("Cross")
SetSystemCursor("IDC_Cross")

Set cursor size in pixels.
SetSystemCursor("IDC_Cross", 100, 100)

Supports .ani, .cur, and .ico files.
SetSystemCursor("Animation.ani")

Creates an invisible cursor.
SetSystemCursor()

If the system cursor is not changing, restore the original cursor.
RestoreCursor()

This code has been updated for v2. Github: 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 := Map("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 = "") {
      AndMask := Buffer(128, 0xFF), XorMask := Buffer(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[StrUpper(Cursor)], "ptr"))
         throw Error("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 Error("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 Error("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 Error("Error: Invalid file path or cursor name")
}

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


The following is a demonstration script that switches your mouse pointer between all 14 built-in Windows mouse cursors.

Code: Select all

#Requires AutoHotkey v2-a138+
#include Cursor.ahk

dict := {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}

for name, id in dict.ownprops() {
   SetSystemCursor name
   MsgBox name
   RestoreCursor
}

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 20 Oct 2021, 23:41
by iseahound
Uploaded a version that uses static map instead of Loop Parse. It is about 2% slower, unless cx or cy is set in which it is 10% faster. I doubt that such a performance hit matters—but I really liked Serenity's old script and wanted to keep it as simple as possible.
Benchmark

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 21 Oct 2021, 07:34
by kczx3
Why regex instead of Map.Has()? Also, why would you need to loop over the Map if the Map has the value?

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 21 Oct 2021, 13:20
by iseahound
Good points. The Regex is for detecting the IDC_ prefix. Do you know if a case insensitive map can be created in one line? Currently I'm using StrUpper which is fine.

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 21 Oct 2021, 13:48
by kczx3
I'd just use StrUpper.

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 24 Oct 2021, 12:17
by safetycar
iseahound wrote:
21 Oct 2021, 13:20
Do you know if a case insensitive map can be created in one line?
Not the most beautiful thing but it's possible to do... static m := (m:=Map(), m.caseSense:=False, m.set("APPSTARTING", 32650, "ARROW", 32512, ...)).
I don't know if that's fits your idea.

Edit: .Set() returns the map. For a second I thought I created a bug but it actually works like that, without adding a ", m" at the end.

Re: SetSystemCursor() and RestoreCursor() - Changing the system cursor

Posted: 22 Sep 2023, 13:10
by ntepa
I modified the SetSystemCursor function to also accept a base64 string:

Code: Select all

;example
B64 := "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAM6wAADOsB5dZE0gAAAAZQTFRFAAAALuUr"
       . "GAEszAAAAAJ0Uk5TAP9bkSK1AAAAF0lEQVR4nGNgYGxgYKAO8R8IIAT1DAUAMHgXFyzrq5IAAAAASUVORK5CYII="
SetSystemCursor(B64)
Sleep 5000
RestoreCursor()

Code: Select all

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

   static SystemCursors := Map("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 = "") {
      AndMask := Buffer(128, 0xFF), XorMask := Buffer(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[StrUpper(Cursor)], "ptr"))
         throw Error("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 Error("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 Error("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
   }

   ; Base64PNG_to_HICON https://www.autohotkey.com/boards/viewtopic.php?p=524691#p524691
   BLen := StrLen(Cursor), nBytes := Floor(StrLen(RTrim(Cursor,"=")) * 3/4 ),  Bin :=Buffer(nBytes)
   CursorShared := DllCall("Crypt32\CryptStringToBinary", "str", Cursor, "uint", BLen, "uint", 1, "ptr", Bin, "uint*", nBytes, "uint", 0, "uint", 0)
                   && DllCall("User32\CreateIconFromResourceEx", "ptr", Bin, "uint", nBytes, "int", true, "uint", 0x30000, "int", cx, "int", cy, "uint", 0, "uptr")
   if CursorShared {
      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 Error("Error: Invalid file path or cursor name")
}

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