AHK script designed to toggle based on CapsLock Status always toggles on 'off' and I cannot change it to 'on' - toggle

(Note: Very new to scripting, borrowed some phrases from other scripts I've online.) I have Carpal Tunnel and play a video game that does not have any key-bind options not set to the F1-F0 keys so I want to rebind the F1-F4 keys to Z-V but only when capslock is enabled, to prevent being unable to type in chat windows and in other programs without closing the script. However, the script re-binds the basic keys to the f-keys ONLY when capslock is OFF, rather than allowing me to change it to ON. Not sure why.
I've tried 'hotfixing' it by rebinding it to Numlock, but when I moved to CapsLock changing 'OFF' to 'ON' did not keep the script from only rebinding the keys while CapsLock was OFF. Not sure why.
$Z::
GetKeyState, state, NumLock, T
if state = D ; NumLock is toggled ON
send, {z}
else
send, {F1}
Return
$X::
GetKeyState, state, NumLock, T
if state = D ; NumLock is toggled ON
send, {x}
else
send, {F2}
Return
etc...
etc...
I expected changing the value 'OFF' to 'ON' would result in the key rebinds only happening during the CapsLock status being toggled on.

Have you checked if you restarted the script after making changes? It's a very common mistake, not only among beginners. According to your example, your keys Z..V should behave as F1..F4 only when the NumLock is toggled off.
Given the nature of your script, you can consider to add the directive #SingleInstance Force which will automatically replace any older instance of your script by a new one each time you run the script again, making testing easier.
You can do conditional binding much easily with an #if directive, which makes the subsequent hotkeys and hotstring only effective when a condition is met.
To check the state of the CapsLock or NumLock keys you can also use the built-in function GetKeyState, which for toggle keys, such as CapsLock or NumLock, with the "T" mode returns either True or False based on the toggle state of the key.
Also, if you want to remap keys, you can simply write the target key's name at the right of the hotkey, which will completely bind the keys, on both Down and Up events. However, for this to work, you must specify your triggering keys as lowercase, since specifying uppercase would only trigger the remap when pressed the keys with the Shift key as well (the CapsLock would have no effect), and that is not your desired behaviour. [More on remapping keys]
Here is an example of what you could do:
#If GetKeyState("CapsLock", "T")
z::F1
x::F2
c::F3
v::F4
Note that, since key remapping always uses the keyboard hook (because it needs to register the Up events as well), there is no need to use the $ prefix in your hotkeys at all.
Nonetheless, you can automate your script even more if you use as condition for your hotkeys the currently active window and bind them to your game using the #IfWinActive directive.
However if there are also chats in the game, you might want to combine both conditions in a single #If, using the built-in function WinActive like this:
#If GetKeyState("CapsLock", "T") and WinActive("My Game Title")
z::F1
; ...
You can check how to narrow your search for the window by its title on the documentation for the WinTitle parameter.
If you want to improve your script even further, you could explore if there is any detectable change on the game window when the chat is active, such as if a certain control exists (you can check that as if it was another window using the WinExist function to check for a certain Window class.
To seek for such changes, you can use a script as the following (from the MouseGetPos documentation):
#Persistent
SetTimer, WatchCursor, 100
return
WatchCursor:
MouseGetPos, , , id, control
WinGetTitle, title, ahk_id %id%
WinGetClass, class, ahk_id %id%
ToolTip, ahk_id %id%`nahk_class %class%`n%title%`nControl: %control%
return
Which would allow you to see the window information of the windows below your mouse. You can use it to check for the name or class of the chat control by placing your mouse over it.
However, keep in mind that many games do not use Windows controls at all in their interfaces and rather just draw them on screen by themselves, so if you're trying this and can't progress much after a while you shouldn't waste too much time on it and rather enjoy playing with your CapsLock toggled binding.
Another tricky way to check if the chat is active is searching for an image on the screen or a pixel color using ImageSearch or much simpler PixelGetColor, but you can only do that if your game's interface is not very complex/animated.

Related

Unable to utilize numpad as my keyboard trigger

I am trying to change media using Autohotkey. I want to utilize Left Shift (<+) + LWin (<#) + Numpad8. Here is my code below but it does not work. I am having issues trying to utilize my Numpad8 to change music. I already know I can use the regular numbers on top of my keyboard but I would like to utilize my numpad instead. Please advise, thanks.
<+<#NumPad8::
Send {Media_Next}
return
From the Numpad Section of the docs, they mention:
If NumLock is OFF but Shift is pressed, the system temporarily releases Shift and acts as though NumLock is ON.
This appears to also be applicable vice versa, as holding Shift while Numlock is on before clicking NumPad8 instead appears to send NumpadUp instead of Shift+Numpad8.
We can work around this by creating a Hotkey to activate when Win+NumpadUp[+ any other modifier keys (such as Shift)] is pressed, and then check ourselves whether Shift and Numlock are active. If they are, then we Send the Media key.
Current Code:
*#NumPadUp::
if(GetKeyState("NumLock", "T") and GetKeyState("Shift", "P"))
Send {Media_Next}
return
However, if we hold down Win+Shift while repeatedly pressing the key, it appears to revert back from NumpadUp to Numpad8. In order to account for this, we can reuse the body of the previous Hotkey in a new Hotkey that activates when *#NumPad8 is used.
Final Code:
*#NumPadUp::
*#NumPad8::
if(GetKeyState("NumLock", "T") and GetKeyState("Shift", "P"))
Send {Media_Next}
return
Edit: Thank you #samthecodingman for bringing up an optimization that I missed- code has been edited to include this

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.

Autohotkey. Hold two buttons and tap another to increase volume

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

Send command, isn't something wrong with AutoHotkey?

So I have this game, called AirMech. It doesn't recognize mouse buttons as controls (yet) so I tried to use AutoHotkey to circumvent it until it's implemented.
#IfWinActive, AirMech
XButton1::Send c
Didn't work. So I tried SendGame, SendPlay and everything else, didn't work either. I googled it, and found out that some games don't recognize any Send commands at all.
Before giving up, I just tried a simple mapping:
#IfWinActive, AirMech
XButton1::c
It actually worked.
Is it expected than no Send command works, but the latter does? What if I wanted to trigger other actions ('c' plus a MsgBox, for instance)?
AutoHotkey has the ability to send keystrokes in a variety of different ways (SendRaw / SendInput / SendPlay / SendEvent). I'm not quite sure what approach the simple key::key mapping uses, but it must be one of them. My guess is that one of SendRaw, SendInput, SendPlay, or SendEvent will work the same as key::key.
Also #IfWinActive sometimes doesn't work exactly the way you expect, especially with fullscreen games. So I usually test my AHK scripts without the #IfWinActive to make sure they're working correctly. Once it's working, I introduce the conditional.
UPDATE
From http://www.autohotkey.com/docs/misc/Remap.htm:
When a script is launched, each remapping is translated into a pair of
hotkeys. For example, a script containing a::b actually contains the
following two hotkeys instead:
*a::
SetKeyDelay -1 ; If the destination key is a mouse button, SetMouseDelay is used instead.
Send {Blind}{b DownTemp} ; DownTemp is like Down except that other Send commands in the script won't assume "b" should stay down during their Send.
return
*a up::
SetKeyDelay -1 ; See note below for why press-duration is not specified with either of these SetKeyDelays. If the destination key is a mouse button, SetMouseDelay is used instead.
Send {Blind}{b Up}
return
My notes:
I suspect the reason a::b is working but a::Send b is not is because of how a::b breaks button down and button up handlers into two separate mappings. The game's gameloop probably polls the gameplay keys for "keydown" state, which would not be maintained consistently if AHK is synthesizing repeats. Remapping a_down->b_down and a_up->b_up probably makes AHK emulate more accurately the act of holding the key down, which may matter for programs which test for key state in particular ways (GetAsyncKeyState?).
The asterisk in the mapping means "Fire the hotkey even if extra modifiers are being held down."

How to remap key in certain case and not remap in other case with autohotkey?

I want to remap alt+e when caps is on in autocad.
And when capslock is not on, alt+e should open menu edit.
I use script like this
<!e::
if(GetKeyState( "CAPSLOCK", "T" ))
{
SendInput erase{space}wp{space}
}
else
{
Send !e
}
When I turn on capslock, remap key is OK.
When I turn off capslockand alt+e, menu edit opened, but closed immediately.
Thanks.
You will want a $ at the beginning of your hotkey to prevent the endless loop that the !e in your else block will trigger. You will also want to add a Return at the end of the hotkey to prevent the script from continuing into what is below this hotkey.
$!e::
if GetKeyState( "CapsLock", "T" )
Sendinput, erase{space}wp{space}
else
Sendinput, !e
Return
(Brackets are only required when if/else blocks are more than one line.)
Beyond that, the likely issue is that it's an alt hotkey that is also set to send alt.
I say this is an issue because if you press and hold alt, it activates menus,
and then the script sends alt, which will be in conflict with that.
As Ricardo said, the ideal way to script this is with the #IF command (only included with AHK_L).
#If GetKeyState("CapsLock", "T") and WinActive("AutoCAD")
!e:: SendInput, erase{space}wp{space}
#If
Notice that you can add the WinActive() function to the #If command's expression.
Try it without that first, and also realize that the application's title needs to be exactly "AutoCAD" at all times for that to work. I would recommend finding AutoCad's ahk_class,
with AHK's window spy, instead of using the title.
If it still does not work, it is likely that AHK is sending faster than AutoCAD would like to receive.
Info on how to deal with that can be found here.
Try to change your else block to this:
Send, {ALTDOWN}e{ALTUP}
I do not rely on these symbols to send keystrokes in AutoHotKey.