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.
Related
What I want is to simply use the XButton1 and XButton2 of my mouse as WheelRight and WheelLeft. I initially did this using:
XButton1::WheelRight
XButton2::WheelLeft
But, I was not satisfied with this because each time I would press the XButton, it would scroll only once and that too only when the key was released. So I came up with this:
~XButton2::
Send {WheelLeft}
Sleep, 500
while GetKeyState("XButton2")
{
Send {WheelLeft}
Sleep, 50
}
return
Similar code can be written for XButton1. This works nicely, but the only issue is that the '~' exposes the native function of the XButton2 (which is 'Forward'), which I do not want. (It is fired when the key is released.) I tried to disable the key from within the mouse software but that just caused the hotkey to stop working completely.
Now, I do know that a simple workaround for this would be to map the XButtons to some rarely used keys like RShift and RCtrl, but what I want is a more elegant and direct solution to this problem. Mapping them to keys on the number pad is not an option for me either since I have uses for it, and I'd rather not have keys on my keyboard locked up solely for this.
Edit:
I realized that the code I wrote initially did not work as the code I wrote later on because I had other hotkeys written for XButton1 and XButton2 such as:
XButton2 & WheelUp::
Send {Volume_Up}
return
When I removed these, the initial code worked as I wanted. I was able to come up with a workaround to keep all of my hotkeys by assigning F13 to XButton1 and F14 to XButton2 since these keys don't even exist on my keyboard. I am still leaving this question open though since I haven't gotten the answer I wanted originally. (The logic is that someone in the future may answer which could help out other people later on who unlike me are unable to find any workaround.)
You can use the up and down events and a timer, for example:
*XButton1::SetTimer, WheelRight, 100
*XButton2::SetTimer, WheelLeft, 100
*XButton1 Up::SetTimer, WheelRight, Off
*XButton2 Up::SetTimer, WheelLeft, Off
WheelRight()
{
SendInput, {Blind}{WheelRight}
}
WheelLeft()
{
SendInput, {Blind}{WheelLeft}
}
Or maybe a cleaner version:
;In the auto execute section
WheelLeft := Func("ScrollWheel").Bind("Left")
WheelRight := Func("ScrollWheel").Bind("Right")
;...
*XButton1::SetTimer, % WheelRight, 100
*XButton2::SetTimer, % WheelLeft, 100
*XButton1 Up::SetTimer, % WheelRight, Off
*XButton2 Up::SetTimer, % WheelLeft, Off
ScrollWheel(direction)
{
SendInput, % "{Blind}{Wheel" direction "}"
}
*(docs) is used so you can also hold down modifiers while using the key, and the blind sendmode is used so the send command doesn't remove the modifiers.
Adjust the timer's delay to scroll faster or slower.
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
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
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.
I got stuck building an ahk shortcut script to increase / decrease Volume. The idea was to hold down LAlt+LShift and tap F12 to increase one step per tap.
The order in which LAlt and LShift are pressed shouldn't matter.
I came up with this so far:
!+::
While (GetKeyState("LShift","P")) and (GetKeyState("LAlt","P"))
{
F12::Send {Volume_Up}
}
Return
But somehow it increases the volume on holding LAlt and taping F12. LShift gets igronred..
What's wrong with that...
This
F12::Send {Volume_Up}
isn't a command, it's a hotkey assignment. You cannot use it within executable context. It is actually the short form for:
F12::
send {volume_up}
return
You wouldn't wanna have a return somewhere in between the lines which should be executed, would you.
As can be read in the documentation, you can only combine two Hotkeys for an action easily, like a & b::msgbox, you pressed a and b. E.g. for a,b AND c, you'd need some workaround like the crossed out, old answer below.
BUT you can add as many modifiers to your hotkey as you want. Modifiers are ! alt, + shift, # win and so on (please have a look # http://ahkscript.org/docs/Hotkeys.htm#Symbols).
So you can simply use
<!+F12::send {volume_up}
-
So, your aim is simply to have volume_up be fired when three Hotkeys are being pressed. You can achieve it like this:
#if getKeyState("LShift", "P")
*<!F12::send {volume_up}
#if
or
*<!F12::
if(getKeyState("LShift","P"))
send {volume_up}
return
For the meaning of * and < and other possible modifiers, see http://ahkscript.org/docs/Hotkeys.htm#Symbols
Your approach wasn't too bad. It would have worked if you had used the Hotkey command instead of an actual hotkey assignment. Still that would have been unneeded work