AHK | prevent resetting Hotstring count after "spaceless" hotstrings - autohotkey

I use some hotstrings either with :o: option or with manually sending a {bs} for other purposes.
In Both cases, if the string i want to add to the end of the previous hotstring happens to be aslo a hotstring in itself, it Will trigger the hotstring!
How to prevent this?
Example:
:o:ahk::AutoHotKey
=> ahk+space+ahk should be: "AutoHotKeyahk", but instead it gets: "AutoHotKeyAutoHotKey"
Note: IF the text behind was not a hotkey it does not trigger again ofcourse.
AutoHotkey+ahk => AutoHotkeyahk
Also in general it would be nice to know, how to make hotstrings more aware if some letters are physically behind the cursor even though for various reasons it is not registered properly in the keycount -ergo prevent malfires of hotkeys in improper context.

So I have tried hard with various approaches and ran into several errors/bugs.
Finally I have a method that works reliably under all tested circumstances.
Ill post the code... feel free to post a better/more elegant one if you have.
IsModifier:=1
#inputlevel 1
SendLevel 1
LAlt & space up::
{
SendInput,{space}
sleep 50
SendInput,{bs}
IsModifier:=0
Loop
{
Sleep,10
if (GetKeystate("Enter") = 1) or (GetKeystate("?") = 1) or (GetKeystate(".") = 1) or (GetKeystate("Space") = 1)
{
break
}
}
IsModifier:=1
return
}
#if (Ismodifier=1)
#inputlevel 0
#Include GeneralHotStrings.AHK

Related

How to combine keys in hotkeys in autohotkeys?

I want to set up a hotkey by pressing Alt, q and the Left arrow. So far I've tried
# opt 1
!q & Left::
#opt 2
Alt & q & Left::
# opt 3
!qLeft:: M
but the only result I get once I run the script is Error: invalid hotkey in the line above. There seems something is wrong with the ampersand.
I can share the whole script if needed.
According to the Custom Combinations section of the documentation:
Combinations of three or more keys are not supported.
However, I found this class that claims to
enable easy usage of custom multi-key hotkeys.
I haven't tested it myself, but if that hotkey is important to you, then it might be worth trying!
Here's a slightly janky solution using vanilla ahk and a timer.
Left::
If(keyPressed = 1){
...Do a thing...
}
return
!Q::
keyPressed := 1
SetTimer, altQTimer, 200
return
altQTimer:
keyPressed := 0
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.

How to Release AutoHotkey Control in a Logic Statement?

Goal:
Use the hotkey 'C' to mimmic 'New Email Window' in Outlook, similar to Gmail.
Attempt:
c::
SetTitleMatchMode, 2
If Not WinActive("Message")
Send, ^n
return
Problem:
This script does work, but then inside that 'New Email' Window the 'c' char is locked out, and I can't use it while typing.
Tried Solutions:
I tried adding and empty an 'else {}' but that does not seem to work. Thoughts?
Environment:
Windows 10 / Outlook 2016+
This is probably what you want,
SetTitleMatchMode,2
#if WinActive("Microsoft Outlook")
{
c::
Send, ^n
return
}
This way, it will allow to type the char 'c' anywhere, including the new email window. But, it will trigger if you are in the main Outlook window, opening a new email window for you.
This uses context-sensitive title matching to map C to CTRL+Nwhen "Microsoft Outlook" is in the window title:
SetTitleMatchMode 2 ; All #If statements match anywhere in title
#IfWinActive Microsoft Outlook
c::^n
#IfWinActiv
This worked. I think in my case it needed to know specifically what to do if the window was not active, and in my case it was to fire a regular 'c', I would have thought that logic was built in by default recognize.
c::
SetTitleMatchMode 2
IfWinActive, Outlook
Send, ^n
Else
send, c
return
$c::
If (WinActive("Microsoft Outlook") { ;may/may not be "Microsoft Outlook' use window spy to find out more
send ^N ; if you got that to work then dont mess with this part
} else {
send {c}
}
Return
Basically this checks if Outlook is active then if so sends ^N and if not it will send the character C.
However this isnt the best idea: creating single key hotkeys that are used for typing...
Better idea: you change the hotkey ( the part before "::` ) to something like $^!c
( the $ is so that any other hotkey sending c wouldn't activate this one )
Another aproach would be:
$~c::
If (WinActive(ahk_class "outlook.exe") { ; or something along the lines of that...
sleep 250
send ^N
}
Return
For the most part this does the same thing except it retains the functionality of the c key better, however it may cause issues with typing in outlook so once again,
Please consider either a non-typing single key hotkey such as Rcontrol for example. The ~ in the hotkey means don't revoke original key functionality, The sleep is added to ensure that when you are using the hotkey that the retained c character isn't added to your new email.
Hope this helped ( I am fairly certain that this works ) I don't use outlook so I don't know how well Outlook will respond to this type of thing, and haven't tested this out but I do know my AHK basics and did do function syntax double checking so good luck to you, read my cmments in the code because they are essential to these code snippets functionality.
you may want to add && If (Not WinActive("Message")) ;or whatwver the message window is called if the two snippets didn't work for your needs add this because your new message window may also be called outlook so this will interfere with the typing.

How to manually trigger Autohotkey hotstrings?

In my main Autohotkey script I have several hundred hotstrings like these:
::fe::for example
::f::and
::fi::for instance
::fo::fortunate
::foy::fortunately
::glo::global
::gloy::globally
::ha::have
::hv::however
Fairly often it would be convenient to trigger a hotstring manually (e.g. by pressing ALT-9) rather than pressing and end character. Is there a way to do this? I haven't found anything in my Googling, so maybe there isn't. But it would be useful.
I've read the hotstrings options e.g. :*: but this isn't the same - I want normal hotstring operation, but also the option to manually force them to trigger as needed.
Updated:
So what you are in fact looking for is using ALT+9 as an end character. I'm not sure but you can probably not use key-combinations for that (see hotstring doc). I cannot think of a really clever way of doing that right now. You might try something like
::fe::
Transform, CtrlC, Chr, 3 ; comes from ahk input documentation, I never really understood how this is supposed to work but I guess there is a code for alt 9 as well somehow
input, key, L1 I M ; wait for the next 1 key
if(key==CtrlC)
sendraw forExample
return
Old answer:
You have to outsource the hotstring body:
::fe::gosub forExample
forExample:
send for example
return
, then you can define a hotkey somewhere:
!9::gosub forExample
If you want to be cool, use functions instead of subroutines.
Note:
::fe::something
is just a short form for
::fe::
send something
return
::fe::for example
::f::and
::fi::for instance
::fo::fortunate
::foy::fortunately
::glo::global
::gloy::globally
::ha::have
::hv::however
!8:: trigger_hotstring("fi")
!9:: trigger_hotstring("ha")
trigger_hotstring(hotstring){
Loop, Read, %A_ScriptFullPath%
{
If InStr(A_LoopReadLine, "::"hotstring "::")
{
SendInput, % StrSplit(A_LoopReadLine,"::"hotstring "::").2
break
}
}
}
If you use AutoHotkey v1.1.06+ you can use #InputLevel
::fe::for example
::f::and
; and so on
#InputLevel, 1 ; sending space will trigger hotstrings above this line
!F9::Send {space}
Edit:
I now see you want to omit the end char which needs a bit of extra work
One way would be to duplicate the hotstrings with additional options:
::fe::for example
:*O:fe_::for example ; duplicate the hotstrings like so
::f::and
::fi::for instance
::fo::fortunate
; and so on
#InputLevel, 1
!9::Send _
Another way would be to remove the endchar
::fe::for example
::f::and
::fi::for instance
::fo::fortunate
; and so on
#InputLevel, 1
!9::
Send {space}
Sleep 100 ; give it time to expand the hotstring, experiment with timing
Send {bs} ; now remove trailing space
Return
I generally use TAB as a trigger for all my hotstrings. like
:*:#pm ::mail1#protonmail.com
:*:#G ::mail2#gmail.com
:*:Btw ::By the way,{Left}
Note that the space that you see here is a tab and not a space. you can do this instead of doing Alt+9 to trigger your macro hotstring.
you can even use more than one Tab so you can be sure that you only trigger it when you really intend to.

Autohotkey send key, let it trigger other hotkeys (#InputLevel confusion)

I want to create a hotkey that sends some key, and then another hotkey for that very just sent key, that in turn sends a third key.
That seems to be possible, using #InputLevel:
#InputLevel 1
a::b
#InputLevel 0
b::c
The above works as intended: By pressing a I get c.
However, I want not only to remap the first key: I want to do more before sending the key. So I thought I could just rewrite the above a little bit:
#InputLevel 1
Hotkey *a, foo
#InputLevel 0
b::c
foo:
; Do something more here …
SendInput {Blind}b
return
The above however does not work as intended: By pressing a I get b (not c).
Update: #Robert Ilbrink reminded me that you can execute more than one command, without using the Hotkey command:
#InputLevel 1
*a::
; Do something here …
SendEvent {Blind}b
return
#InputLevel 0
b::c
The above does give the intended effect: Pressing a results in c. However, I have to rephrase my problem. I guess the problem is: I need to set the hotkeys dynamically, which means I have to use the Hotkey command with a label (as far as I know). (Also notice that I use SendEvent above. Using SendInput produces a b. Odd.)
(End of update.)
I know there is a companion command to #InputLevel—SendLevel—which might be relevant. I've tried putting it many places but it has never made any difference.
So, that was the reduced, theoretical example. Remapping a to b to c is of course useless in reality (and the net result could of course be achieved by a::c). On to my use case. Just keep in mind that if it turns out that the "real" solution means doing what I'm trying to do some other way, I'm still interested in knowing more about #InputLevel and SendLevel, and why my example does not work as intended.
I'm working on implementing dual-role modifier keys. For example, send ) when pressing RShift alone, but RShift+key when pressed together with some other key. Basically, RShift on keydown, and RShift up and ) on keyup. However, that has one flaw: Even when combining RShift with some other key, ) is still sent. So the script needs to know when there has been a combination. My solution is to add hotkeys to all letter keys, the arrow keys and some other keys, like this:
for comboKey in filteredComboKeys {
Hotkey % "*" comboKey, Dual_comboKey
}
; Later in script:
Dual_comboKey:
; The following function lets the dual-role modifier keys know that they have
; been combined with another key (and a few other things, which I don't think
; are important for the issue.)
Dual.combo() ;
key := Dual.cleanKey(A_ThisHotkey)
SendInput {Blind}%key%
return
The above solution works very well for my purpose—except that the break all remappings and other hotkeys the user might have made: These simply never occur.
Why not:
a::
; Do something
Send, b
Return
As far as I can gather, #InputLevel doesn't bite on the Hotkey command. However, I stumbled on a solution for one of the snippets I originally posted:
Hotkey *a, foo
b::c
foo:
; Do something more here …
SendLevel 1
SetKeyDelay 0 ; Optional; Only affects this hotkey.
SendEvent {Blind}b
return
Note that SendEvent must be used. SendInput produces b. SendPlay produces nothing at all. I don't know why.
However, this technique won't work if you want to send the hotkey itself. Then you end up in an infinite loop. Using the keyboard hook does not help, since SendLevel overrides it.
So, again I have an answer the solves one of the initial examples, but does not help me in reality. I need to send the hotkey itself. I guess I have to let the user remap their keys using my script. Sigh.
Update:
I've published my dual-role modifiers script now, in case anyone is interested in more details, and how I deal with the problems.
Update:
I've updated my dual-role modifiers script. I now stay away from the Hotkey command. It's easier when dealing with this kind of thing, I think.
By now (starting Autohotkey 1.1.01), this can be achieved quite easily like so:
~Shift up::
IfInString, A_PriorKey, Shift
{
Send )
}
return