Reputation: 2758
TLDR: How to programmatically hide the mouse cursor for entire desktop no matter the application that has focus?
Even though I have "Hide pointer while typing" enabled in Windows Mouse Options, it doesn't work for most windows. It only seems to work on windows that use the standard Windows text box control (such as notepad.exe). Everywhere else, the cursor stays right on top of whatever I'm typing.
I want to programmatically hide the mouse cursor across the entire desktop no matter what application has focus, when I execute a specific action (for example, a hotkey is pressed). Then, when the mouse is moved, I want the cursor to reappear.
I can handle the hotkey etc. I just need to know how to hide the cursor.
What I have tried:
Cursor.Hide method only hides cursor for the current form. This is not an acceptable solution.
Windows API ShowCursor function - just doesn't seem to work at all... the cursor never disappears.
Currently my workaround is to listen for the Spacebar to be pressed (implies I'm typing more than a few characters), then move the mouse cursor to the top corner of the desktop, out of the way. However, this is not an ideal solution as it requires the mouse to be moved a lot to return to the original position, as well as messing up mouse position in apps such as Sketchup when entering dimensions containing spaces. I would prefer that the cursor simply become invisible in its current location, then reappear at the same location when moving the mouse, as it is supposed to work with the "Hide pointer when typing" option.
I'm not tied to C# but it's just easy to compile into an executable for Windows, which I can launch on Windows startup.
Thank you for any help!
Upvotes: 1
Views: 1780
Reputation: 94
This is a simpler AHK solution to contrast with Evgeny's answer. By using the SystemCursor function from the DllCall docs the script can be massively simplified. Note though that this is definetly not as fully featured, it very simply hides the cursor when any keyboard key is pressed, and reveals it when the mouse is used, and no more.
InstallKeybdHook
InstallMouseHook
CoordMode("mouse", "screen")
OnExit (*) => SystemCursor("Show") ; Ensure the cursor is made visible when the script exits
SetTimer(Main, 10)
Main() {
if(A_TimeIdleKeyboard < 25)
;A keyboard key has been pressed, hide cursor
SystemCursor("Hide")
else if(A_TimeIdleMouse < 25)
;Mouse have been used, show cursor
SystemCursor("Show")
}
SystemCursor(cmd) { ; cmd = "Show|Hide|Toggle|Reload"
static visible := true, c := Map()
static sys_cursors := [32512, 32513, 32514, 32515, 32516, 32642, 32643, 32644, 32645, 32646, 32648, 32649, 32650]
if (cmd = "Reload" or !c.Count) { ; Reload when requested or at first call.
for i, id in sys_cursors {
h_cursor := DllCall("LoadCursor", "Ptr", 0, "Ptr", id)
h_default := DllCall("CopyImage", "Ptr", h_cursor, "UInt", 2, "Int", 0, "Int", 0, "UInt", 0)
h_blank := DllCall("CreateCursor", "Ptr", 0, "Int", 0, "Int", 0, "Int", 32, "Int", 32, "Ptr", Buffer(32 * 4, 0xFF), "Ptr", Buffer(32 * 4, 0))
c[id] := { default: h_default, blank: h_blank }
}
}
switch cmd {
case "Show": visible := true
case "Hide": visible := false
case "Toggle": visible := !visible
default: return
}
for id, handles in c {
h_cursor := DllCall("CopyImage", "Ptr", visible ? handles.default : handles.blank, "UInt", 2, "Int", 0, "Int", 0, "UInt", 0)
DllCall("SetSystemCursor", "Ptr", h_cursor, "UInt", id)
}
}
Upvotes: 0
Reputation: 390
While writing a similar utility I found that playing with Cursor.Hide (by moving your app' window right under the cursor) doesn't work well with some 3rd party apps (e.g. Microsoft Office). It turns out that this technique produces additional OnHover events which break internal logic of MS Office, causing some popups/menus to stop working properly.
Also, moving the cursor to the corner of a screen is not any better because it causes similar issues.
So I ended up with the following logic:
Here's a working app: https://github.com/johnnyjob/single-cursor
Upvotes: 1
Reputation: 113
Here is the AutoHotkey solution to this, though since this just calls Windows APIs (for the hide/show part, otherwise the script is a bit more complicated in using UIA framework to track editable fields not to hide a pointer when you are not typing and also tracking mouse movements and other keybinding-related machinery) you can just as well translate it to C# (or just launch the AHK script on Windows start)
It uses the same logic that Jimi suggested (iterating through system cursors and replacing their pointers with blank images), although has a tweak to fix an issue when your pointer has a scale size > 1 (it becomes blurry if you simply try to replace it). However, this doesn't work for custom app mouse pointers, only the ones you see in the system settings (IArrow, Hand, etc.)
Alternatively, it uses a conceptually cleaner method of attaching an empty gui element to an app's window and sending a hide pointer ShowCursor
command
Full standalone script source with the libraries that it depends on
The function that hides the pointer
sys🖰Pointer(OnOff := On) {
global is🖰PointerHidden
static C := win32Constant.Misc ; various win32 API constants
static hCur,AndMask,XorMask
, isInit := false, toShow := 1, toHide := 2
, lrDef := C.lrShared | C.lrDefColor ; lrDefSz
, lcDef := C.lrShared | C.lrDefColor | C.lrCcSrc ; lrDefSz
if ( (OnOff = Off)
or (OnOff = Toggle and (not is🖰PointerHidden
or not isInit)) ) { ; hide on first init call as well
; dbgTT(dbgMin:=0, Text:='toHide', Time:=1,id:=6,X:=0,Y:=150)
changeTo := toHide ; use hCur_blank cursors
} else {
; dbgTT(dbgMin:=0, Text:='toShow', Time:=1,id:=8,X:=0,Y:=250)
changeTo := toShow ; use hCur_saved cursors
}
static curSID := [ ;system_cursors learn.microsoft.com/en-us/windows/win32/menurc/about-cursors
Arrow := 32512 ; IDC_ARROW MAKEINTRESOURCE(32512) Normal select
, Ibeam := 32513 ; IDC_IBEAM MAKEINTRESOURCE(32513) Text select
, Wait := 32514 ; IDC_WAIT MAKEINTRESOURCE(32514) Busy
, Cross := 32515 ; IDC_CROSS MAKEINTRESOURCE(32515) Precision select
, UpArrow := 32516 ; IDC_UPARROW MAKEINTRESOURCE(32516) Alternate select
, Size⤡ := 32642 ; IDC_SIZENWSE MAKEINTRESOURCE(32642) Diagonal resize 1
, Size⤢ := 32643 ; IDC_SIZENESW MAKEINTRESOURCE(32643) Diagonal resize 2
, Size↔ := 32644 ; IDC_SIZEWE MAKEINTRESOURCE(32644) Horizontal resize
, Size↕ := 32645 ; IDC_SIZENS MAKEINTRESOURCE(32645) Vertical resize
, Size⤨ := 32646 ; IDC_SIZEALL MAKEINTRESOURCE(32646) Move
, No := 32648 ; IDC_NO MAKEINTRESOURCE(32648) Unavailable
, Hand := 32649 ; IDC_HAND MAKEINTRESOURCE(32649) Link select
; ↓ not in OCR_NORMAL so can't be a restore target? learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setsystemcursor. Or just a doc omission?
, AppStarting := 32650 ; IDC_APPSTARTING MAKEINTRESOURCE(32650) Working in background
, Help := 32651 ; IDC_HELP MAKEINTRESOURCE(32651) Help select
, Pin := 32671 ; IDC_PIN MAKEINTRESOURCE(32671) Location select
, Person := 32672 ; IDC_PERSON MAKEINTRESOURCE(32672) Person select
;, _handwrite := 32631 ; MAKEINTRESOURCE(32631) Handwriting
]
, hCursors := Array()
hCursors.Capacity := curSID.Length
static sys := helperSystem
; Get mouse pointer actual size (https://stackoverflow.com/a/65534381)
; GetIconInfo will return a bitmap sized for the primary display only
; If your main display is 150%, but the cursor is on a 100% secondary monitor, you'll get an incorrect 48x48 bitmap instead of 32x32
; 1 get monitor DPI via GetDpiForMonitor
; 2 get proper icon size via GetSystemMetricsForDpi
; 3 scale by the "cursor magnification" settings from accessibility
dpi🖥️ := sys.getDPI🖥️(), dpi🖥️x:=dpi🖥️[1], dpi🖥️y:=dpi🖥️[2] ; 1) monitor dpi
sysCurMagniF := RegRead('HKEY_CURRENT_USER\SOFTWARE\Microsoft\Accessibility','CursorSize',1) ; 2) pointer size @ Settings/Ease of Access/Mouse pointer
dpi🖰Pointer := sys.getDPI🖰Pointer(dpi🖥️x), width🖰Pointer:=dpi🖰Pointer[1], height🖰Pointer:=dpi🖰Pointer[2] ; 3) get dpi-scaled system metric for mouse cursor size learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetricsfordpi
cx := sysCurMagniF * width🖰Pointer, cy := sysCurMagniF * height🖰Pointer ; LoadImageW
cxc := 0, cyc := 0 ; copy
; cxc := cx, cyc := cy ; copy
if (OnOff = Init or isInit = false) { ; init when requested or at first call
dbgTT(dbgMin:=3, Text:='init', Time:=2,id:=5,X:=0,Y:=450)
hCur := Buffer( 4*A_PtrSize, 1) ;
AndMask := Buffer(32*A_PtrSize, 0xFF) ;
XorMask := Buffer(32*A_PtrSize, 0) ;
loop curSID.Length {
hCur := DllCall('LoadImageW' ; ↓ LoadImage ret HANDLE to the newly loaded image; NULL on error, use GetLastError
, 'Ptr',0 ; opt HINSTANCE hInst handle to the module DLL/EXE that contains image to be loaded
,'uint',curSID[A_Index] ; LPCWSTR name if ↑Null and fuLoad≠lrLOADFROMFILE, predefined image to load
,'uint',C.imgCursor ; uint type type of image to be loaded
, 'Int',cx, 'Int',cy ; int cx|xy icon/cursor's width|height px
; 0 & fuLoad=lrDefSz use SM_CXICON/CURSOR (/Y) system metric value to set W/H
; 0 & not lrDefSz use actual resource height
,'uint',lrDef ; uint fuLoad
, 'Ptr') ; learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadimagew
hCur_saved := DllCall("CopyImage" ; create a new image (icon/cursor/bitmap) and copy its attributes to the new one. Stretch the bits to fit the desired size if needed, learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-copyimage
,"Ptr" ,hCur ; HANDLE h
,"uint",C.imgCursor ; uint type
, "int",cxc, "int",cyc ; int cx|cy 0=returned image will have same width|height as original hImage
,"uint",lcDef ; uint flags
, 'Ptr')
hCur_blank := DllCall("CreateCursor" ; create a monochrome cursor
, "Ptr",0 ;opt HINSTANCE hInst,
, "int", 0,"int", 0 ;int xHotSpot / yHotSpot
, "int",32,"int",32 ;int nWidth / nHeight
, 'Ptr',AndMask ;const VOID *pvANDPlane array of bytes with bit values for the AND mask of the cursor, as in a device-dependent monochrome bitmap
, 'Ptr',XorMask ;const VOID *pvXORPlane array of bytes with bit values for the XOR mask of the cursor, as in a device-dependent monochrome bitmap
, 'Ptr')
hCursors.Push([hCur_saved, hCur_blank]) ; toShow=1 toHide=2
}
; isInit := true ; move to the end to allow hiding cursor on 1st toggle call
}
; MouseGetPos(&🖰x, &🖰y) ; Get the current mouse position, and store its coordinates
dbgOut := "changeTo=" changeTo
. "`nsysCurMagniF=" sysCurMagniF
. "`nmonDPIx|y`t= " dpi🖥️x " | " dpi🖥️y ;"`n" 🖰x " " 🖰y
. "`ncurW|H_dpi`t= " width🖰Pointer " | " height🖰Pointer
. "`ncX|Y`t= " cx " | " cy
loop curSID.Length {
hCur := DllCall("CopyImage", "Ptr", hCursors[A_Index][changeTo]
,"uint",C.imgCursor, "int",cxc,"int",cyc, "uint",lcDef)
DllCall("SetSystemCursor" ; replace the contents of the system cursor specified by id with the contents of the cursor handled by hcur
, "Ptr",hCur ; cursor handle, destroyed via DestroyCursor, so can't be LoadCursor, use CopyCursor
,"uint",curSID[A_Index] ; system cursor to replace
)
; dbgOut .= "`nhCur=" hCur
}
is🖰PointerHidden := (changeTo = toHide) ? true : false
dbgOut .= "`nis🖰PointerHidden=" is🖰PointerHidden
dbgOut .= "`nOnOff=" OnOff
if changeTo = toShow {
restore🖰Pointers()
sys🖰Btn(On)
} else if changeTo = toHide {
sys🖰Btn(Off)
}
dbgTT(dbgMin:=4, Text:=dbgOut, Time:=3,id:=3,X:=0,Y:=750)
isInit := true
}
And the function that restores the pointer
restore🖰Pointers() {
static C := win32Constant.Misc ; various win32 API constants
DllCall("SystemParametersInfo"
,'uint',C.curReload ; uint uiAction system-wide parameter to be retrieved or set
,'uint',0 ; uint uiParam parameter whose usage and format depends on the system parameter being queried or set., see uiAction. Must be 0 if not otherwise indicated
,'uint',0 ;io PVOID pvParam parameter whose usage and format depends on the system parameter being queried or set, see uiAction. Must be NULL if not otherwise indicated
,'uint',0 ; uint fWinIni If a system parameter is being set, specifies whether the user profile is to be updated, and if so, whether the WM_SETTINGCHANGE message is to be broadcast to all top-level windows to notify them of the change
)
}
And the alternative approach
app🖰Pointer(OnOff := '') { ; create our own gui element, make the target app its owner, then show a pointer there so it's redirected from the app to our invisible element
static guiBlankChild := Gui()
, guiOwner := 0
, displayCounter := 0 ; track thread pointer counter, pointer is shown only if >=0, no way to get current value
is🖰vis := is🖰PointerVisible() ; check if pointer is visible otherwise ShowCursor can stack hiding it requiring multiple calls to unstack
MouseGetPos(,,&winID,)
if OnOff = Off ; hide if explicit command to hide is given
or (OnOff = Toggle and is🖰vis = 0) ; or if explicti command to toggle is given and it's not hidden yet
or (OnOff = '' and is🖰vis = 0) { ; or no command and it hasn't been hidden yet
guiBlankChild.Opt("+Owner" . winID) ; make the GUI owned by winID
guiOwner := winID
if is🖰vis {
if displayCounter < -1 { ;;; likely an issue with being unable to hide the pointer
} else {
displayCounter := DllCall("ShowCursor", "int",0)
}
}
} else {
if not is🖰vis {
if not winID = guiOwner {
guiBlankChild.Opt("+Owner" . winID) ; make the GUI owned by winID
guiOwner := winID
displayCounter := DllCall("ShowCursor", "int",1)
guiBlankChild.Opt("-Owner")
} else {
displayCounter := DllCall("ShowCursor", "int",1)
guiBlankChild.Opt("-Owner")
}
}
}
}
P.S. "Hide pointer while typing" is indeed deficient as it relies on apps polling its value and hiding the pointer themselves, which few apps do
P.P.S. Also found a simpler C++ GUI app MousePuff
Upvotes: 0