Advertisement
Lorenzo501

Multi-Function MButton Hotkey For Caret & Cursor Control (discontinued).ahk

May 7th, 2024 (edited)
1,122
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #Requires AutoHotkey 2.0
  2. #Include <UIA> ; Download and unzip archive in "Lib" folder: https://github.com/Descolada/UIA-v2/blob/main/Lib/UIA.ahk
  3.  
  4. ; TODO: make cursor auto-hide for notepad, Visual Studio & VS Code
  5. ; THIS MIGHT BE WAY BETTER (TO ADDRESS THE ISSUES MENTIONED BELOW), MAKING THE CURSOR AUTO-HIDE BY DEFAULT WHILE ONE OF THE 3 APPS ARE ACTIVE AND THEN HAVING LONG-PRESS TO TEMPORARILY DISABLE THE CURSOR AUTO-HIDE (AND IT CAN REENABLE IT AFTERWARDS, BUT REACTIVATING THE WINDOW WILL REENABLE IT AUTOMATICALLY). COULD PERHAPS ALSO CHECK IF THE EDIT CONTROL IS FOCUSED (MAYBE EVEN BY USING `editControlElement.GetRuntimeId()` ALTHOUGH COULD ALSO JUST MAKE IT WORK FOR ALL EDIT CONTROLS BY USING THE CLASSNAME OR SOMETHING), IF IT ISN'T THEN TEMPORARILY SHOW THE CURSOR (INSTEAD OF IT BEING DEPENDANT ON THE WINDOW BEING ACTIVE)
  6.  
  7. ; USE THIS IN NOTEPAD ONLY
  8. ;MButton::WinActivate("ahk_class Progman")
  9.  
  10. ; USE THIS IN VS CODE ONLY
  11. ;MButton::Send("{Alt}")
  12.  
  13. ; USE THIS IN VISUAL STUDIO ONLY (THIS IS THE OLD VERSION, I GOT A BETTER VERSION AS WELL)
  14. ; TODO: take temporary screenshot, show screenshot in GUI, (maybe activate VS if it doesn't work w/o doing so), make VS invisible, use workaround below, make VS visible & remove GUI
  15. MButton::
  16. {
  17.     static toggle := false
  18.  
  19.     if (toggle := !toggle)
  20.     {
  21.         A_CoordModeMouse := "Screen"
  22.         A_KeyDelay := -1
  23.         A_MouseDelay := -1
  24.         MouseGetPos(&previousCursorX, &previousCursorY)
  25.         visualStudioElement := UIA.ElementFromHandle("Microsoft Visual Studio ahk_exe devenv.exe")
  26.         thumbOld := visualStudioElement.WaitElement({Type: "Thumb"})
  27.         thumbOldPos := thumbOld.GetPos("Screen")
  28.         ControlSend(zeroWidthSpace := "​") ; This will unintentionally overwrite the redo actions of the current session
  29.         Send("{Ctrl down}")
  30.         ControlSend("z{Home}", thumbOld.ControlId) ; This will undo the caret marker (its pos remains usable by Ctrl+Shift+Backspace) & moves caret to top
  31.         Send("{Ctrl up}")
  32.         thumbNew := visualStudioElement.WaitElement({Type: "Thumb"})
  33.         thumbNewPos := thumbNew.GetPos("Screen")
  34.         ;UIA.GetFocusedElement().ControlClick("WD", 1, "NA") ; Scrolling like this is more precise if it scrolls one line at a time, which it doesn't but Ctrl+DownArrow does^^
  35.         MouseClickDrag("L", thumbNewPos.x + 1, thumbNewPos.y + 1, thumbOldPos.x, thumbOldPos.y, 0) ; Drag the moveable part of the scrollbar to the previous pos
  36.         MouseMove(previousCursorX, previousCursorY, 0)
  37.     }
  38.     else
  39.         Send("^+{Backspace}") ; Moves caret back to the previous pos (where the caret marker had been temporarily placed to make this possible)
  40. }
  41.  
  42. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DOESN'T WORK
  43. F1::WinSetTransparent(0, "Microsoft Visual Studio ahk_exe devenv.exe")
  44. F2::WinSetTransparent("Off", "Microsoft Visual Studio ahk_exe devenv.exe")
  45. F3::MsgBox(WinExist("Microsoft Visual Studio ahk_exe devenv.exe"))
  46.  
  47. /*
  48. [Could also just overwrite the native middle-btn-press scroll hotkey when visual studio is active, b/c nobody uses that scroll feature in visual studio I think]
  49. Maybe nice for the cursor/caret AHK script to toggle it and which will cause the caret's position to be saved and the caret removed until another toggle places it back where it was, and the toggle hotkey will cause the cursor to hide automatically after some time (repeatedly ofc), temporarily showing the cursor again when the cursor moves. And ~*LButton:: will disable it if it was enabled (if I don't want to click then I can just toggle it w/ middle-click but clicking takes care off toggling only the cursor back for you and it can put the caret somewhere depending on where you clicked, so if you didn't click on text then the caret won't appear ofcourse). That works for multiple scenarios and is useful, keep cursor auto-hide enabled until EITHER foreground change OR CLICK HAS HAPPENED. On middle-click it has to skip the first cursor auto-hide delay. 750ms long-press if I merely want the caret gone but preventing the cursor from hiding. So quick-press is a toggle that can even toggle back after double-press/long-press (is convenient), and both double-press and long-press are toggles as well but toggling back with them is only necessary when you want to keep the other one hidden when both were hidden
  50.  
  51. Recommend in the hotkey remote tooltip and script comment that you shouldn't use Quick-Press to disable two active hide modes at once and then reenabling one of them straight after, because you can simply toggle the one you don't want with either Double-Press or Long-Press directly. I can technically save the startTime in Quick-Press and checking if very little time has elapsed since it was set when Double-Press/Long-Press is used, to warn the user in a traytip. Might be worth considering when everything else works but is probably not needed, since it's quite obvious
  52.  
  53. [Can try to document this somewhat, such as "quick-press toggles ANY mode back that's currently in effect" and gotta program it properly]:
  54.  
  55. If you use quick-press by mistake, then quick-press again to undo it or double-press to disable cursor auto-hide while keeping the caret hidden, or if you instead wanted to see the caret but not the cursor then long-press to toggle only the caret back while keeping the cursor hidden
  56.  
  57. [Yes this is possible but it might be better if
  58. |
  59. double-(quick-)press would merely toggle the cursor auto-hide which then works for multiple scenarios, which is useful; like it can even toggle the cursor back when both the caret and cursor were hidden (by having performed either a quick-press or both double-press and long-press),
  60. |
  61. but if you use double-(quick-)press first and then (single-)quick-press, it should merely disable the cursor auto-hide and not hide the caret (SO IT'S RECOMMENDED TO DO THIS INSTEAD OF DOUBLE-PRESS TWICE), to make it easier to use.
  62. |
  63. However if you use long-press first and then double-(quick-)press it should just cause the cursor to auto-hide on top of the caret already being hidden]
  64. If you use long-press by mistake, then quick-press will disable it again or double-press instead to hide the cursor on top of the already hidden caret,
  65. |
  66. or if you instead only wanted to hide the cursor then quick-press once to disable it and then double-press (THERE MIGHT BE AN ISSUE IF IT THINKS DOUBLE-PRESS OCCURRED FIRST OR THE THIRD PRESS BEING IGNORED, SO I'M PRETTY SURE IT NEEDS TO DETECT A TRIPLE-PRESS FOR THIS AS WELL, THEN EITHER THE USER DELAYS THE DOUBLE-CLICK AND IT WILL WORK OR THE USER PRESSES 3 TIMES AND IT WILL WORK, IF THE USER DELAYS THE THIRD PRESS THEN IT'S THEIR OWN FAULT, WHICH MOST LIKELY WON'T HAPPEN! AND IF IT DOES THEN IT'S NEGLIGIBLE AND CAN BE EASILY CORRECTED ANYWAY. THEY WOULD SIMPLY EXPECT THE CARET TO BE VISIBLE AND THE CURSOR TO BE GONE BUT BOTH WOULD BE VISIBLE, SO THEN THEY JUST DO ONE MORE QUICK-PRESS. EASY).
  67.  
  68. If you use long-press to hide the caret and then quick-press, it will show the caret and SHOULDN'T affect the cursor
  69. If you use double-press by mistake, then quick-press will disable it again or long-press to hide the caret on top of the already hidden cursor, or if you instead only wanted to hide the caret then quick-press once to disable it and then long-press (gotta make sure that these two actions don't get detected as a double-press)
  70.  
  71. Make the double-press and long-press toggle back like described above but also show a traytip if not both were hidden when toggling a specific one back, explaining to the user; title: "Only One Hide Mode Was Active" content: "Quick-Press Disables It Conveniently!"
  72. */
  73.  
  74. /*
  75. resetting caret+cursor on left-click (on selection change event is probably overkill, if both caret and cursor get hidden & then the cursor gets shown b/c of left-click but caret didn't..)
  76.  
  77. remember: `using MButton > clicking another Edit control [> CAN STILL RESET PREV CARET IF REFOCUS PREV EDIT CONTROL HERE] > using MButton again` will overwrite the previous selection
  78.  
  79. maybe I can check on left-click if the same element is still focused, in which case it shouldn't reset the selection (and if I make the caret + cursor script, then maybe something similar). not the focus change event b/c it won't trigger when different text gets selected
  80. */
  81.  
  82. /*
  83. clicking the scrollbar or any non-Edit element (after the MButton hide fn) could be the reason for it not going to work the way it's supposed to
  84.  
  85. Ik skip voor nu de cursor, ik denk dat de resultaat anders niet echt chill zal zijn om te gebruiken. Dat je 2x moet klikken of lang ingedrukt houden, om dus iets er nog bij te verbergen als je bijvoorbeeld ergens klikt en de cursor weer zichtbaar blijft en je dan dus de cursor opnieuw moet verbergen.. Zelfs als long-press nogal kort is en dus onzichtbaar word tijdens het ingedrukt houden. Is vast beter om gwn de cursor zelf naar rechts te bewegen, ook al komt die dan op een tweede scherm terecht ooit. Je hebt ook VSCode en Notepad waar het dan ws weer anders moet werken enzo. Ander keer misschien proberen
  86. */
  87.  
  88. ; https://superuser.com/questions/1270165/how-to-remove-the-blinking-caret-and-temporarily-hide-my-cursor-with-a-single-ho
  89.  
  90. ;on WinAPI about caret
  91. ;HideCaret        ; DOESN'T SEEM TO WORK
  92. ;ShowCaret        ; USELESS B/C THE ONE ABOVE DIDN'T WORK
  93. ;CreateCaret
  94. ;SetCaretPos
  95.  
  96. ; THESE PROBABLY DO NOT WORK IN MICROSOFT VISUAL STUDIO DUE TO HAVING A SPECIAL EDIT CTRL (`GetCaretPos` BELOW PROBABLY DOES WORK THO)
  97. ;EditGetCurrentCol  Returns the column number in an Edit control where the caret (text insertion point) resides
  98. ;EditGetCurrentLine Returns the line number in an Edit control where the caret (text insert point) resides
  99.  
  100. /**
  101.  * Gets the position of the caret with UIA, Acc or CaretGetPos.
  102.  * Credit: plankoe (https://www.reddit.com/r/AutoHotkey/comments/ysuawq/get_the_caret_location_in_any_program/)
  103.  * @param X Value is set to the screen X-coordinate of the caret
  104.  * @param Y Value is set to the screen Y-coordinate of the caret
  105.  * @param W Value is set to the width of the caret
  106.  * @param H Value is set to the height of the caret
  107.  */
  108. GetCaretPos(&X?, &Y?, &W?, &H?) {
  109.     /*
  110.         This implementation prefers CaretGetPos > Acc > UIA. This is mostly due to speed differences
  111.         between the methods and statistically it seems more likely that the UIA method is required the
  112.         least (Chromium apps support Acc as well).
  113.     */
  114.     ; Default caret
  115.     savedCaret := A_CoordModeCaret
  116.     CoordMode "Caret", "Screen"
  117.     CaretGetPos(&X, &Y)
  118.     CoordMode "Caret", savedCaret
  119.     if IsInteger(X) && (X | Y) != 0 {
  120.         W := 4, H := 20
  121.         return
  122.     }
  123.  
  124.     ; Acc caret
  125.     static _ := DllCall("LoadLibrary", "Str","oleacc", "Ptr")
  126.     try {
  127.         idObject := 0xFFFFFFF8 ; OBJID_CARET
  128.         if DllCall("oleacc\AccessibleObjectFromWindow", "ptr", WinExist("A"), "uint",idObject &= 0xFFFFFFFF
  129.             , "ptr",-16 + NumPut("int64", idObject == 0xFFFFFFF0 ? 0x46000000000000C0 : 0x719B3800AA000C81, NumPut("int64", idObject == 0xFFFFFFF0 ? 0x0000000000020400 : 0x11CF3C3D618736E0, IID := Buffer(16)))
  130.             , "ptr*", oAcc := ComValue(9,0)) = 0 {
  131.             x:=Buffer(4), y:=Buffer(4), w:=Buffer(4), h:=Buffer(4)
  132.             oAcc.accLocation(ComValue(0x4003, x.ptr, 1), ComValue(0x4003, y.ptr, 1), ComValue(0x4003, w.ptr, 1), ComValue(0x4003, h.ptr, 1), 0)
  133.             X:=NumGet(x,0,"int"), Y:=NumGet(y,0,"int"), W:=NumGet(w,0,"int"), H:=NumGet(h,0,"int")
  134.             if (X | Y) != 0
  135.                 return
  136.         }
  137.     }
  138.  
  139.     ; UIA caret
  140.     static IUIA := ComObject("{e22ad333-b25f-460c-83d0-0581107395c9}", "{34723aff-0c9d-49d0-9896-7ab52df8cd8a}")
  141.     try {
  142.         ComCall(8, IUIA, "ptr*", &FocusedEl:=0) ; GetFocusedElement
  143.         /*
  144.             The current implementation uses only TextPattern GetSelections and not TextPattern2 GetCaretRange.
  145.             This is because TextPattern2 is less often supported, or sometimes reports being implemented
  146.             but in reality is not. The only downside to using GetSelections is that when text
  147.             is selected then caret position is ambiguous. Nevertheless, in those cases it most
  148.             likely doesn't matter much whether the caret is in the beginning or end of the selection.
  149.  
  150.             If GetCaretRange is needed then the following code implements that:
  151.             ComCall(16, FocusedEl, "int", 10024, "ptr*", &patternObject:=0), ObjRelease(FocusedEl) ; GetCurrentPattern. TextPattern2 = 10024
  152.             if patternObject {
  153.                 ComCall(10, patternObject, "int*", &IsActive:=1, "ptr*", &caretRange:=0), ObjRelease(patternObject) ; GetCaretRange
  154.                 ComCall(10, caretRange, "ptr*", &boundingRects:=0), ObjRelease(caretRange) ; GetBoundingRectangles
  155.                 if (Rect := ComValue(0x2005, boundingRects)).MaxIndex() = 3 { ; VT_ARRAY | VT_R8
  156.                     X:=Round(Rect[0]), Y:=Round(Rect[1]), W:=Round(Rect[2]), H:=Round(Rect[3])
  157.                     return
  158.                 }
  159.             }
  160.         */
  161.         ComCall(16, FocusedEl, "int", 10014, "ptr*", &patternObject:=0), ObjRelease(FocusedEl) ; GetCurrentPattern. TextPattern = 10014
  162.         if patternObject {
  163.             ComCall(5, patternObject, "ptr*", &selectionRanges:=0), ObjRelease(patternObject) ; GetSelections
  164.             ComCall(4, selectionRanges, "int", 0, "ptr*", &selectionRange:=0) ; GetElement
  165.             ComCall(10, selectionRange, "ptr*", &boundingRects:=0), ObjRelease(selectionRange), ObjRelease(selectionRanges) ; GetBoundingRectangles
  166.             if (Rect := ComValue(0x2005, boundingRects)).MaxIndex() = 3 { ; VT_ARRAY | VT_R8
  167.                 X:=Round(Rect[0]), Y:=Round(Rect[1]), W:=Round(Rect[2]), H:=Round(Rect[3])
  168.                 return
  169.             }
  170.         }
  171.     }
  172. }
  173.  
  174. OnExit (*) => SystemCursor("Show")  ; Ensure the cursor is made visible when the script exits.
  175.  
  176. #c::SystemCursor("Toggle")  ; Win+C hotkey to toggle the cursor on and off.
  177.  
  178. SystemCursor(cmd) ; cmd = "Show|Hide|Toggle|Reload"
  179. {
  180.     static visible := true, c := Map()
  181.     static sys_cursors := [32512, 32513, 32514, 32515, 32516, 32642, 32643, 32644, 32645, 32646, 32648, 32649, 32650]
  182.  
  183.     if (cmd = "Reload" || !c.Count) ; Reload when requested or at first call
  184.     {
  185.         for (i, id in sys_cursors)
  186.         {
  187.             h_cursor  := DllCall("LoadCursor", "Ptr", 0, "Ptr", id)
  188.             h_default := DllCall("CopyImage", "Ptr", h_cursor, "UInt", 2, "Int", 0, "Int", 0, "UInt", 0)
  189.             h_blank   := DllCall("CreateCursor", "Ptr", 0, "Int", 0, "Int", 0, "Int", 32, "Int", 32, "Ptr", Buffer(32 * 4, 0xFF), "Ptr", Buffer(32 * 4, 0))
  190.             c[id] := {Default: h_default, Blank: h_blank}
  191.         }
  192.     }
  193.  
  194.     switch (cmd)
  195.     {
  196.         case "Show": visible := true
  197.         case "Hide": visible := false
  198.         case "Toggle": visible := !visible
  199.         default: return
  200.     }
  201.  
  202.     for (id, handles in c)
  203.     {
  204.         h_cursor := DllCall("CopyImage", "Ptr", visible ? handles.Default : handles.Blank, "UInt", 2, "Int", 0, "Int", 0, "UInt", 0)
  205.         DllCall("SetSystemCursor", "Ptr", h_cursor, "UInt", id)
  206.     }
  207. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement