Autohotkey, toggle boolean variable not work properly - boolean

I've written an Autohotkey program to toggle zoom in level of Windows built-in magnifier. It's very simple. When you move your mouse to the top left corner, it zooms in, and it zooms out next time you move to the top left corner. Below is my code. The problem is that it always zooms in. I don't know where is the problem. Can anyone help to take a look?
; Timer to check mouse position
SetTimer, CheckMouse, 300
#Persistent
#SingleInstance force
WinGetPos,,,Xmax,Ymax,ahk_class Progman ; get desktop size
T = 4 ; adjust tolerance value if desired
Xmax := Xmax - T ; allow tolerance to mouse corner activation position
Ymax := Ymax - T
CheckMouse: ; check mouse position
CoordMode, Mouse, Screen
MouseGetPos, MouseX, MouseY
GetKeyState, SState, Shift
GetKeyState, AState, Alt
GetKeyState, CState, Control
zoom_toggle := True
if (MouseY < T and MouseX < T and CState = "U" and AState = "U" and SState = "U")
{
if(zoom_toggle)
{
Send #{+}
zoom_toggle := False
}
else
{
Send #{-}
zoom_toggle := True
}
}
Return

Few things to fix/improve.
I'll start off with the actual problem, and then go over stuff you should/could improve.
So the actual problem, every time you run you timer, you set the zoom_toggle variable to be true. So yeah, not much help trying to do any toggles when the value is reset every time.
Move the definition to be at the top of the script, or due to how forgiving ahk is, you could actually entirely skip declaring the variable. That way when it's first used, it's created with the default value of nothing, which evaluates to false.
And now onto other fixes/improvements.
Location of your #directives.
Good/common practice is defining these types of #directives at the very top of your script.
Usage of WinGetPos to get screen width/height.
You could use the A_ScreenWidth and A_ScreenHeight variables to easily get your screen width and height.
Missing return that should end your auto-execute section.
When you launch the script for the very first time, there is nothing that stops the code execution from falling through all the way to your timer's callback label.
Nothing bad will result of this in your case, but for future reference, you don't want this to happen. Use a return to stop code execution and end the auto-execute section.
Redundant code
There is no need to set CoordMode every time you run the timer. Move this command to be at the top of your script.
In your send command, there is no need to wrap - inside { }. That is only done to escape characters that have special meaning in a send command, and - has none. In special cases you may even encounter unwanted behavior by doing this. More about this in the documentation.
Why are Xmax and Ymax being created? They're doing nothing for us?
Usage of Send instead of SendInput
SendInput is faster and more reliable. Should almost always be used over Send.
You can specify SendMode, Input at the top of your script to turn all Send commands into SendInput. Personally I prefer just writing out SendInput.
Legacy code
Technically nothing wrong with using legacy code, but it's definitely not recommended.
Compatibility with future AHK versions is non-existent as well. Expression syntax is what should always be used nowadays.
Use the function GetKeyState() instead of the legacy command.
Use the non-legacy operators (&&, ||, etc) instead of legacy AND, OR, etc.
Always use := instead of =. Legacy assignment is never used.
Usage of a label is pretty much legacy as well. Should replace with a function, but then I should ensure you understand function scopes as well. If you want, I can.
Here's your revised code:
#Persistent
#SingleInstance, Force
CoordMode, Mouse, Screen ;move this to the top, needs to be executed only once
; Timer to check mouse position
SetTimer, CheckMouse, 1000
T := 4 ; adjust tolerance value if desired
Xmax := A_ScreenWidth - T ; allow tolerance to mouse corner activation position
Ymax := A_ScreenHeight - T
;not sure why we're creating these two variables though, they're doing absolutely nothing for us?
return ;end auto-execute section
CheckMouse: ; check mouse position
MouseGetPos, MouseX, MouseY
SState := GetKeyState("Shift", "P")
AState := GetKeyState("Alt", "P")
CState := GetKeyState("Control", "P")
;returns true/false (1/0)
;true meaning the key is down
if (MouseY < T && MouseX < T && !SState && !AState && !CState)
{
;I skipped definin the zoom_toggle variable so it's created for us
;with the default value of nothing, which evaluates to false
;had to flip around the if statement to account for this as well
if(!zoom_toggle)
{
SendInput, #{+}
zoom_toggle := true
}
else
{
SendInput, #-
zoom_toggle := false
}
}
return ;ends the label
To ends off things, I'd like to say this isn't a very good implementation though. To make it at least usable, change the timer to run slower. I made it 1sec in that revised code.
If you want to make it better, a super simple way I could mention, is make it run code only once while the mouse is in that region. Adding another toggle for whether the mouse has exited that region would do it.
Also if you were to have a second monitor that's on the left of your main monitor in Windows, things would get pretty bad haha.

Related

No 'OnMouseClick'-Trigger?

I wanted to have a window closed by Autohotkey as soon as I click somewhere else and was confident that there would be a fitting trigger / listener like e.g. an OnMouseClick - function. But seems not; MouseClick just actively clicks the mouse. So the best way I could figure was this:
SetTitleMatchMode, 2
Loop{
WinGetActiveTitle, OutputVar
if (OutputVar <> "My_Window_Name_to_close.ahk"){
WinClose , AutoTasks 2.ahk
}
Sleep, 1000
}
return
While the above code does work, it leaves me unsatisfied that that's really the best AHK can do (?), because those few lines of code, while not a real problem, do cost CPU and memory (according to my Task Manager e.g. more than Skype or my Cloud Background service). The 1-second-sleep also introduces a discernible lag, but without it, the loop would even cost 2% CPU!
Can you come up with a more efficient / AHK-native solution (except for further increasing the Sleep-time)?
I've found the answer myself in the meantime:
SetTitleMatchMode, 3 ; exact match
WinWaitNotActive, My_Window_Name_to_close.ahk
WinClose, My_Window_Name_to_close.ahk
Even though you found a solution to your problem, I wanted to provide an answer to the actual question of detecting a click. That can be done using a hotkey for "LButton" in combination with the tilde "~" modifier like so:
~LButton:: ; The "~" sets it so the key's native function will not be blocked
GoSub CheckActiveWindow
Return
CheckActiveWindow:
sleep, 1 ; Wait a small amount of time in case the desired Window is changing from inactive to Active
WinGetActiveTitle, OutputVar
if (OutputVar <> "My_Window_Name_to_close.ahk"){
WinClose , AutoTasks 2.ahk
}
return

AHK Click event while Toggled

I've found AHK codes that separately work "ok" but I need one inside another. So, I have:
1. The first rapidly fires click when you hold down the left mouse button:
~$LButton::
While GetKeyState("LButton", "P"){
Click
Sleep .1 ; milliseconds
}
return
2. The second is a toggle script that sends the same left mouse button firing events but can be toggled on and off with a button press (F8 in this case)
toggle = 0
#MaxThreadsPerHotkey 2
F8::
Toggle := !Toggle
While Toggle{
Click
sleep 1
}
return
What I need is: when I push F8 once, I want my left mouse button to fire click events rapidly while holding it. When I push F8 again it should do nothing. If it's important, I need those clicks while holding Ctrl in-game.
I've read a bit about AHK and tried this code but it doesn't work any close to what I want:
toggle = 0
#MaxThreadsPerHotkey 2
F8::
Toggle := !Toggle
If Toggle{
~$LButton::
While GetKeyState("LButton", "P"){
Click
Sleep .5 ; milliseconds
}
}
return
This one gives me errors about missing "return" but I tried a lot of merging variations.
Also, I've read a lot about MaxThreads and still don't know why there should be 2 and what is it for.
Firstly, not sure what amount of time you're trying to give the Sleep commands, but decimal numbers wont work. Just whole numbers, and they're in milliseconds. .1 and .5 are likely interpreted as 0, not sure though. Also, the sleep command isn't as accurate as you may think it is. Read the remarks section in the documentation for more.
Secondly, you shouldn't loop inside hotkey labels. It's bad practice due to AHK not offering true multithreading.
Though, at the end of the day, it wont make any difference if this is all your script it.
For future reference if you want to start writing nicer and bigger scripts, I'll show you the usage of timers though. They should be used for this.
LButton::SetTimer, MyCoolFunction, 0 ;when LButton is clicked down start a timer with the shortest possible period
LButton Up::SetTimer, MyCoolFunction, Off ;when LButton is released, stop the timer
MyCoolFunction()
{
Click
}
And same for your toggle version, you don't want to loop inside a hotkey label:
F8::
toggle := !toggle
if(toggle) ;if true
SetTimer, MyCoolFunction, 0
else
SetTimer, MyCoolFunction, Off
return
MyCoolFunction()
{
Click
}
And if you don't know what toggle := !toggle actually is, and want to know, you can read a previous answer of mine here. It also shows how you can compact that code down to just one line of code. And also explains why there's no need to define the variable toggle on top of your script (as you were doing).
And about #MaxThreadsPerHotkey2:
It's because AHK doesn't offer true multithreading. When you're looping side a hotkey definition, that hotkey is completely locked up. You can't run the hotkey again to stop the loop. Unless, you set the hotkey to use more threads (better to call them instances) than one.
That way you're able to launch the hotkey again and you're able to change the toggle variable's value so you can stop the loop.
But again, you shouldn't loop inside hotkeys. If you use a timer, like I showed above, you don't need to worry about this dirty workaround.
And then to the new code you want to create.
Well first about what went wrong in your attempt. I guess it was a good thought, but it's not even close. Hard to say what exactly is wrong in it, since it's not even close to working. I guess what I can say is that hotkey labels (hotkey::) are evaluated once when the script starts, and then never again. So you can't put them inside some runtime logic. The Hotkey command would be used for that.
Luckily your problem is actually much simple than that. You don't need to mess around with the Hotkey command.
All you're looking to do is toggle the hotkeys on/off. Suspend is used for that like so:
F8::Suspend
And now the script's hotkeys (and hotstrings) toggle on/off every time you press F8.
So here's your final script:
LButton::SetTimer, MyCoolFunction, 0 ;when LButton is clicked down start a timer with the shortest possible period
LButton Up::SetTimer, MyCoolFunction, Off ;when LButton is released, stop the timer
MyCoolFunction()
{
Click
}
F8::
Suspend
SetTimer, MyCoolFunction, Off ;set the timer off just incase we hadn't released LButton before we hit F8
return

Holding LButton using XButton1 (side button) in AutoHotKey

I need a script where if I hold down XButton1 in my mouse, it auto clicks for me until I release.
I was able to find this script: F1::Click % GetKeyState("LButton") ? "Up" : "Down" but when I change F1 to XButton1 it doesn't seem to hold down like it does with F1.
It appears to only send a left-click down/up once. I think this happens because keypresses and mouse clicks behave differently in that when you hold a key you expect it to repeat after a short delay, whereas if you hold a mouse button you expect it to just stay held and not repeatedly click. Since the XButtons are mouse buttons, they will behave as such, even though the intuitive expected behavior is that of a keypress. Anyway, that aside, we'll just have to make the script longer.
XButton1 Up::bT := false
XButton1::
bT := true
While( bT )
{
Click
Sleep , 50 ; Added sleep to make it a bit more stable (add more if needed)
}
Return

Using AutoHotKey to temporarily disable stuck modifier keys

I want to temporarily disable all modifier keys if it seems like one or more of them has become 'stuck' - basically, I need the opposite of Windows' StickyKeys.
I'm working with a Windows tablet (ie. no physical keyboard) with a faulty input device, and it sometimes just jams a bunch of modifier keys, with no predictable trigger. Until I have time to actually troubleshoot the (potentially hardware-level) bug, this script will be a stopgap.
I just need a few seconds to sleep and unsleep the system, since that usually smacks input back into order - unfortunately, the stuck modifier keys interfere with the machine's normal sleep button behavior.
I'm trying to work with the ctrl key first, just to get the concept working, then test for the other modifiers later.
TimerVar := 0
CtrlIsStuck := False
Loop {
CtrlKeyPhysicallyDown := GetKeyState("Ctrl", "P")
If CtrlKeyPhysicallyDown
TimerVar++
Else
TimerVar := 0
If TimerVar > 1 ; TODO: make sane before deploy
{
CtrlIsStuck := True
Break
}
Sleep, 500 ; TODO: make sane before deploy
}
#If CtrlIsStuck
ToolTip, Stuck keys detected; jamming for 15 seconds. Use Sleep button now.
SetTimer, DoReload, 15100
Hotkey, Ctrl, DoNothing
Send, {Ctrl Up}
Sleep 15000
DoReload:
Reload
DoNothing:
Return
I expect this to check in a loop to see if the ctrl key has been held for a span of time, and if it has, bind ctrl to something that does nothing, claim ctrl has been physically released, then wait for a bit.
The 'check if it's held' logic is working, but once it gets past that #If line, things start behaving in a way that, after reading the manual for a while, I still don't understand. While it definitely runs, the Hotkey statement doesn't seem to do anything useful, and the SetTimer line effectively behaves redundantly; I suspect I'm missing something obvious about AutoHotKey's script flow, but I'm unsure what.

can somebody help to write a autohotkey script for select all the pasted content after paste?

the autohotkey script should do follows:
after I tap the hotkey,
it will paste the content from the clipboard, then immediately select all the pasted content?
I write a script as follows:
^+p::
clipvar:=Clipboard
num:=strlen(clipvar)
send ^v
send +{left %num%}
return
This script works.
But the selecting process is too slow!!!
Can somebody write a better script?
SendMode, Input optionally combined with SetBatchLines, -1 and variations of SetKeyDelay can accelerate key sequences.
However, the selection of large texts will still take some time, and slow machines may slow it down even further.
Here's another approach which - in terms of sending keystrokes - is more efficient:
^+p::
oldCaretX := A_CaretX
oldCaretY := A_CaretY
Send, ^v
WaitForCaretChange()
MouseGetPos, mX, mY
MouseClickDrag, Left, %A_CaretX%, %A_CaretY%, %oldCaretX%, %oldCaretY%
MouseMove, %mX%, %mY%
return
WaitForCaretChange() {
oldCaretX := A_CaretX
oldCaretY := A_CaretY
while(A_CaretX = oldCaretX && A_CaretY = oldCaretY) {
Sleep, 15
}
}
This code relies on the window to expose the caret position, which unfortunately, not every window does. It remembers the caret position before the paste and selects text up to the old position after pasting new text; this should be equal to selecting all the newly inserted text. If you're only working with editors that expose their caret position, I recommend you go with this one since it's faster. Otherwise, you can still think about using both your method and this one, depending on the window and/or the text length.