How to get ExitApp to take effect immediately in AHK? - autohotkey

If I run the following code, if I hit the ^+q it does not stop entering the numbers 1-100. Only after it completes does the script exit. Is there a way to get the script to stop even if it is in the middle of sending keystrokes?
^j::
ArrayCount := 100
Loop % ArrayCount
{
Send, %A_index%
}
return
^+q::ExitApp ; Exit script with Escape key

There are 2 issues with your code.
Sendreleases modifier keys when it simulates input, using it in a loop this way is going to interfere with autohotkey's hotkey detection. You can still activate ^+q if you press the 3 buttons simultaneously but it's much easier to use a hotkey without modifiers for example the Escape key. This is also what your comment says you're doing
^+q::ExitApp ; Exit script with Escape key
so as a bonus it will fix the discrepancy between your comment and your code ;).
The second problem is that the loop in which you execute the Send command is going to finish very quickly if you use SendInput and by the time ExitApp is executed all the numbers were already sent(even if you don't yet see the effect). In case of SendEvent there is some other problem which prevents other threads from being executed when you do it in a loop(don't know what causes it, might be a bug).
To solve it you need to add Sleep. At my system doing Sleep 1 works well. You can experiment with different numbers and send modes until you get the desired effect(you can also try 0 and -1.
Full code:
^j::
ArrayCount := 100
Loop % ArrayCount
{
Send %A_index%
Sleep 1 ; experiment with how long to sleep
}
return
Escape::ExitApp ; Exit script with Escape key

Related

AutoHotKey WinActive returns wrong value when changing window focus

In my AutoHotKey script I'm using #IfWinActive to detect if the Roblox window is in focus, and then press the number 1 button whenever left clicking the mouse, like this:
#IfWinActive, Roblox
LButton::
MouseClick, Left
SendInput, {1}
return
#IfWinActive
It works great, except for when I'm clicking out of the Roblox window back to another window. It still fires this code on the first click, resulting in it typing the number 1 into Notepad (or whatever window I switch focus to).
I figured that when I'm clicking on Notepad the focus is still on the Roblox window, which is why the code still fires. So I tried changing the code to this:
#IfWinActive, Roblox
LButton::
Sleep, 100
if WinActive("Roblox")
{
MouseClick, Left
SendInput, {1}
}
return
#IfWinActive
Assuming that by the time the Sleep finished the focus would have shifted to the Notepad window and If WinActive("Roblox") would return false, but it still returns true and types 1 into Notepad.
I also tried using StartTimer and a label, thinking that maybe the Sleep wasn't asynchronous, but that has the same problem as well.
Anybody know how to get around this issue? Thanks in advance!
The main problem in this case is that the hotkey is fired immediately after LButton is pressed down and the Roblox window is still active.
The only solution I see is to fire the hotkey upon release of the LButton using the tilde prefix (~) to prevent AHK from blocking the key-down/up events:
#IfWinActive, Roblox
~LButton Up:: SendInput, 1
#IfWinActive
There are a couple of ways we can achieve this. TL;DR for solution, check the yellow part of this post.
Firstly I'll address the problems in your code:
Usage of MouseClick over Click. Technically nothing wrong, but Click is said to be more reliable in some situations and easier to use. Looks cleaner as well.
Wrapping 1 in {} is not needed and does nothing for you here. In some cases you may even produce unwanted behavior by doing this. In a send command, {} is used to escape keys that have special meaning, or to define keys that you can't just type in. More about this from the documentation.
Having a somewhat of a bad WinTitle that you're matching against. Again, nothing technically wrong, but right now you match any window that starts with the word Roblox. Shouldn't be too hard accidentally match the wrong window.
A quick and a very effective solution would be matching against the process name of your Roblox window.
So #IfWinActive, ahk_exe Roblox.exe or in an if-statement if (WinActive("ahk_exe Roblox.exe")) (assuming that's the process' name, I have no idea)
For an absolutely fool proof way could match against the hwnd of the Roblox window. However, that's maybe a bit overkill and you couldn't really use it with #IfWinActive either. An example I'll write below will use this that though.
However, problems 1 and 2 can be entirely avoided by doing this neat way of remapping a key (remapping is pretty much what you're doing here).
~LButton::1
Ok, so why does that work?
key::key is just the syntax to easily do a basic remap, and with ~ we specify that the hotkey isn't consumed when it fires.
Cool, but now onto the actual problem you're having.
So what went wrong with the sleeping thing? Well since you're consuming the hotkey, all you're actually doing is firing the hotkey, waiting 100ms, then checking if Roblox is active. Well yes, it will still be active since nothing was ever done to switch focus away from it.
If you were to not consume the left clicking action, it would work, but it's definitely not a good idea. You do not want to sleep inside a hotkey statement. AHK does not have true multithreading and unless you would've specified a higher #MaxThreadsPerHotkey for your hotkey, all subsequent presses of the hotkey would be totally ignored for that 100ms.
So yes, with specifying a higher amount of threads that can run for that hotkey, it would kind of make this solution work, but it's still bad practice. We can come up with something better.
With timers you can avoid sleeping in the hotkey statement. Sounds like you tried the timers already, but I can't be sure it went right since code wasn't provided so I'll go over it:
#IfWinActive, ahk_exe Roblox.exe
~LButton::SetTimer, OurTimersCallbackLabel, -100 ;-100 specifies that it runs ONCE after 100ms has passed
#IfWinActive
OurTimersCallbackLabel:
if (WinActive("ahk_exe Roblox.exe"))
SendInput, 1
return
And now onto the real solution, to which #user3419297 seems to have beat me to, just as I'm writing this line of text.
Using the up event of your LButton press as the hotkey.
#IfWinActive, ahk_exe Roblox.exe
~LButton Up::SendInput, 1
#IfWinActive
This way the down event has already switched focus of the window and our hotkey wont even fire.
Note that here we unfortunately can't use the key::key way of remapping I described above.
Bonus:
Here's something that could be used if the up event of our keypress wouldn't be desirable, or somehow the window switching of the active window was delayed.
RobloxHwnd := WinExist("ahk_exe Roblox.exe")
#If, RobloxUnderMouse()
~LButton::1
#If
RobloxUnderMouse()
{
global RobloxHwnd ;specify that we're using the variable defined outside of this function scope
;could've also ran the code to get Roblox's hwnd here every time, but that would be wasteful
MouseGetPos, , , HwndUnderMouse ;we don't need the first two parameters
return RobloxHwnd == HwndUnderMouse ;are they the same hwnd? (return true or false)
}
Here we're first storing the hwnd of our Roblox to the variable RobloxHwnd.
Note that Roblox would need to be running before we run this script, and if you restart robox, script would need to be restarted as well.
So adding some way of updating the value of this variable on the fly would be good, maybe under some hotkey.
Then by using #If we're evaluating an expression (in our case, running a function and evaluating its return value) every time we're about to attempt to fire the hotkey. If the expression evaluates to true, we fire the hotkey.
Usage of #If is actually not recommended, and it is good practice to avoid using if at all possible. However, you wont encounter any problems in a script this small, so using #If is going to be very convenient here.
If you were to have a bigger script in which there's a lot of code running often, you'd be likely to run into problems.

How to repeat an ongoing script if the same input is given in autohotkey

i use this code to send more mouse wheel scrolls using one notch, the problem is when an input is sent again before the loop is finished, it is not taken in account and the loop needs to end before relaunching
I tried looking for a way to break up the code if there is an input and relaunch it with no success
WheelUp::
loop 100
{
sendInput {WheelUp}
sleep 2
}
return
Expected behavior: on the 50th loop, if input is given again, reset the loop count (stop and relaunch the script) so the total repetitions would be 150
Try this approach.
You have to change the #MaxThreadsPerHotkey setting.
This setting is used to control how many "instances" of a given hotkey
or hotstring subroutine are allowed to exist simultaneously. For
example, if a hotkey has a max of 1 and it is pressed again while its
subroutine is already running, the press will be ignored.
That will allow the hotkey to "interupt" itself.
Each time you scroll up while the hotkey is already active it will add another 100 scrolls to the loop.
#MaxThreadsPerHotkey 2
WheelUp::
if (counter > 0) ; this means the hotkey is already active
{
counter := counter + 100 ; in that case we just add another 100 scrolls to the loop
return
}
else ; this means the hotkey is not active, we start fresh with 100 scrolls
{
counter := 100
}
while, counter > 0
{
sendinput, {WheelUp}
sleep 40 ; change this to your requirement
counter--
; tooltip, % counter ; un-comment this line for testing
}
return
~WheelDown::counter := 0 ; extra hotkey to stop the loop immediately if needed
I cannot respond to the other comment but WheelUp::Send, {WheelUp 100} won't break the current 100 Wheelups and will add another 100 WheelUps. So if the input was given again on the 50th WheelUp, it would still send 200 WheelUps.
In the loop you will need a conditional statement to detect if a physical scroll up was detected recently, and then restart the loop if it does. For most keys you would use GetKeyState as the check in the if statement, however the mouse wheel has no state to detect.
In my example code, the Enter button will restart the loop. I'm not sure how to restart the loop if trying to use WheelUp again since there is no state to detect, and A_TimeSincePriorHotkey is not reliable because the loop is sending WheelUps.
WheelUp::
loop 100
{
sendInput {WheelUp}
sleep 2
if (GetKeyState("Enter", "P"))
Goto WheelUp
}
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.

hotkeys does not work when send is in loop

Assume this code:
Loop
{
if enabled
Send, /
}
m::
enabled := !enabled
Return
I want to toggle sending / to a Notepad for example. But if I run this code by pressing M on keyboard, then pressing the M key again does not disable sending.
Looks like the send command in the Loop cause this issue since Ive tried using msgbox which does not disable the m key.
How can I make this code to work? (SendInput and Play does not work too)
It's because your loop is blocking any other execution. Unless that loop is the only thing in your script, you generally want to avoid using loops and use timers instead.
Timers don't block further execution but act more like their own thread. Here's an example using a timer:
slashTimerActive := 0
m::
if (!slashTimerActive)
SetTimer, SendSlash, 100 ; Call the sub every 100ms
else
SetTimer, SendSlash, Off
slashTimerActive := !slashTimerActive ; Flip the variable
return
; Subroutine
SendSlash:
SendInput, /
return

Is it possible to create a macro like that with AHK?

I'm something like a GM in a MMORPG game. Our job is reporting people who using cheat and sending them to jail. But leaving that jail zone is not really hard so we have to send them again and again. I have a loooong nickname list (I have about 400 nicknames to report repeatly) so It's really boring.
What i wanna ask is, I don't know anything about AHK. If that kind of macro is possible, I'll do a loooong research to create that macro. But if It's not possible, I'm not even gonna try.
What i need is; The Macro will press "enter" to activate chat mode. Then will write "/report -cheater nickname-" and remember there's 400+ nicknames exist so I need to repeat the macro for different nicknames. After it write "/report -cheater nickname-" Macro will press enter. Then a little chat box will pop-up. Macro will click to the box, will write the report reason, then click confirm. then another chat box will pop-up to say something like "your report is received." And macro will click to confirm for that too. And will do it for 400+ nicknames with 400+ different reasons. Is that actually possible to do? Just wondering that. Not asking you to creating this macro. If you answer that, I'll try to make it myself :D
Thanks.
This script is to perform a series of searches on Google. The search strings are stored in a text file and read into an array, they are then executed one by one, based on hitting the {Tab} key (you can make this repeat automatically).
When the script is interrupted, you can start it again and give it a (new) starting number, or tell it to start from 1 again.
Not exactly what you were looking for, but it gives you a lot of starting points.
#Persistent
#SingleInstance Force
#installKeybdHook
SetWorkingDir %A_ScriptDir%
TempDir = C:\Temp
Menu, Tray, Icon , %A_AhkPath%, 2, 1
TrayTip, JobSearch, Started, 1
SetTitleMatchMode, 2
TextCounter = 0
Return
+Launch_App1::
Run, Notepad %TempDir%\Google.txt
Return
Launch_App1:: ; vacatures Job Search
+CapsLock::
Restart:
MouseGetPos, XPos2, YPos2
XPos3 := 50
YPos3 := 100
IniRead, TextCounter, %TempDir%\GoogleCounter.ini, Counter, Nr
ArrayCount = 0
Loop, Read, %TempDir%\Google.txt ; This loop retrieves each line from the file, one at a time.
{
ArrayCount += 1 ; Keep track of how many items are in the array.
Array%ArrayCount% := A_LoopReadLine ; Store this line in the next array element.
}
MaxSearchCount = %ArrayCount%
TextCounter += 1
If (TextCounter > 1)
InputBox, TextCounter , Start, Number (1..%MaxSearchCount%),,,,,,,10,%TextCounter% ; InputBox, OutputVar [, Title, Prompt, HIDE, Width, Height, X, Y, Font, Timeout, Default]
TextCounter += 0
IniWrite, %TextCounter%, %TempDir%\GoogleCounter.ini, Counter, Nr
SearchText:=Array%TextCounter%
MouseClick, left
gosub, SendNewSearch
Return
;=======================================================================================================================================
Browser_Favorites:: ; Search for next Vacature string (Vacatures)
CapsLock::
If (TextCounter = 0) ; Restart with previous script if Textcounter is set to 0
{
GoSub, Restart
Exit
}
IniRead, TextCounter, %TempDir%\GoogleCounter.ini, Counter, Nr
TextCounter += 1
IniWrite, %TextCounter%, %TempDir%\GoogleCounter.ini, Counter, Nr
SearchText:=Array%TextCounter%
If (SearchText = "")
{
TextCounter := 0
IniWrite, %TextCounter%, %TempDir%\GoogleCounter.ini, Counter, Nr
Send, ^{F4}
SplashTextOff
ExitApp
}
Sleep, 200
Send, {Home 2}
Sleep, 700
Send, {WheelUp 10}
Sleep, 400
gosub, SendNewSearch
Exit
SendNewSearch:
MouseGetPos, XPos3 ,YPos3
SetTitleMatchMode, 2
IfWinActive, Chrome
{
while (A_Cursor = "AppStarting")
Sleep, 200 ; Continue
Sleep, 100
SplashTextOff
MouseClick, left, %XPos2%,%YPos2%
WinGetTitle, this_title, A
IfInString, this_title, Google
{
Send, {Home}+{End}{DEL}%SearchText%{Enter}
}
ToolTip, Waiting....
DisplayText = Nr%TextCounter% %SearchText%
Sleep, 500
SplashTextOn, 200, 0,%DisplayText%
WinMove, %DisplayText%, , 800, 25
ToolTip
;MouseMove,(50),(500)
MouseMove,%XPos3%,%YPos3%
ClipBoard = %SearchText%
}
Exit
Exit
+Browser_Favorites::
run, %TempDir%\Google.txt
Return
It is possible to do. You can create two txt files which have list of +400 user names and +400 different reasons. Macro can read lines one by one and can makes all things, what you want, more than 400 times.
You will need this loop for writing lines from txt file to an array, a loop in a function for checking expected color at the specified pixel with PixelGetColor (http://www.autohotkey.com/docs/commands/PixelGetColor.htm) for detecting buttons. You may also use PixelGetColor command or AutoIt3 Window Spy, which will be installed with autohotkey, to see colors of buttons. Finally you can start to code from here (http://www.autohotkey.com/docs/).
PS. Sorry, site did not allow me to use more than 2 hyperlinks.
Basically you are trying to write a script that types enter, then a list of characters then enter again?
Something quite simple you could do would be to create a .txt file that includes everything you want it to type (excluding enters, except between lines), and create a macro like this:
#n::
Loop, Read, inputFile.txt
{
Send {Enter}%A_LoopReadLine%{Enter}
}
return
Basically you run the macro and open the game to the point that you can start typing enter, character information, enter, but press the windows key and the 'n' key. The macro would then loop through each line of 'inputFile.txt' and stimulate typing an enter, the line, and then an enter.