How to exit a loop depending on how long I keep a key pressed? - autohotkey

How can I end this loop depending on how long I keep X pressed? If I hold X for five seconds it should send B, S, C, Y, and U at one second intervals.
When I hold X for one second it should only send B, if I hold X for two seconds it should send B and S, etc. Currently it simply sends B, S, C, Y, and U.
*$x::
While(GetKeyState("x", "P"))
{
send b
Sleep 1000
send s
Sleep 1000
send c
Sleep 1000
send y
Sleep 1000
send u
Sleep 1000
}
Return
Solution does not have to be a loop.

x::
index:="i"
letters:="l"
output:="o"
l:="bscyu"
i:=(i<o0?++i:1)
StringSplit,o,l
send % o%i%
sleep,1000
return
x Up::i:=0

I can see a few possible approaches.
1) The first and perhaps worst is to have an if... to test the keystate, and an exit, after every single send. This is extremely repetitive, but not too terrible if you stick it in a function, and just call that, something like:
*$x::
while (GetKeyState("x", "P")) {
sendStuff('b')
sendStuff('s')
sendStuff('c')
...
}
sendStuff(str) {
Send str
if (!GetKeyState("x", "P")) {
exit
}
sleep 1000
}
2) The next is to continue operating as you are, with a chained series of commands, and have an external, separate script kill that series of commands while running, then re-Run() your script when you wish to restart. Killing external scripts is covered in the FAQ, here: http://www.autohotkey.com/docs/FAQ.htm#close
There are three relevant sections there which are a little long to paste here, but in summary, you may be interested in the following commands:
; Allow script's hidden main window to be detected.
DetectHiddenWindows On
; Avoid need to specify full path of file.
SetTitleMatchMode 2
; Kill another script: Update params to the script's case sensitive name.
WinClose Script Filemame.ahk - AutoHotkey
; Suspend other script.
PostMessage, 0x111, 65305,,, Script Filename.ahk - AutoHotkey
; Pause other script.
PostMessage, 0x111, 65306,,, Script Filename.ahk - AutoHotkey
; Pause/resume current script on Ctrl+Alt+P
^!p::Pause
3) There's also a longer code sample just below that, which describes canceling a loop, and it's my belief that this is the correct approach to this problem, as while it might be fiddlier initially, it is considerably more scalable, reduces code duplication, and permits you to handle multiple loop entry/exit conditions in a single place.
It is, in short, the best in terms of Programming Style.
Place the entries (b, s, c, y, u) that you wish to sent into a string, array or other iterable datastructure, and iterate over it, so that your while loop sends only a single item and a pause each iteration. Then the next time you hit your hotkey, you can either resume the loop from where you left off, or start over, depending on whether you maintain a pointer to your current position in the structure.
For example, using the above sendStuff function, a loop will give you the very powerful and trivially extensible:
*$x::
{
letters = b,s,c,y,u
Loop, parse, letters, `,
sendStuff(%A_LoopField%)
return
}

This post is so old, but I just can't help it.
sendChars := "bscyu"
$x::
i := 1 ; (!i ? 1 : i) ; permits starting where left off.
While (getKeyState("x", "P")) {
Send % subStr(sendChars, i, 1)
i := ((i==strLen(sendChars)) ? 1 : (i+1))
if (getKeyState("x", "P")) { ; why wait if you don't have to.
Sleep 1000
}
}
The docs point out that a While loop is only evaluated at the beginning of each iteration, so listing all the keys in a loop will just send all the keys, then break out of loop once at that top again and sees that "x" is no longer being pressed.

Related

kdb \t timer not stopping operation

I have a client side function that queries tables on 3 remote servers and pulls in data for each alphanumeric character from each server.
At the end of the function a timer checks the running dictionary on each server, and if each character has ran (all dictionary values set to 1b) it should kill the timer and close the connection handle.
However I've discovered that the timer isn't being stopped and after the last character has ran (dictionary values all=1b) the cycle will try to start again, which causes the system to hang.
How can I rewrite my timer function so that it effectively closes the connection handle once the value of all running is 1b?
The last 3 lines of my function look like the following...
f:{
...do stuff here...;
if[hA"all running"; system "t 0";hclose hA;];
if[hB"all running"; system "t 0";hclose hB;];
if[hC"all running"; system "t 0";hclose hC;];
}
.z.ts:{f params}
\t 5000
hA, hB, and hC are my connecton handles.
.z.ts runs the function every 5 seconds.
params is grabbing a character from the command line, which is set when the script is started.
I'd like to stop the function from running once the value of all running is 1b.
As per #terrylynch's comment of "stopping the timer after all handles have finished processing" - I joined the if statements (that stop the timer) into a singular if statement and moved it into the .z.ts function
.z.ts:{f params;if[(hA"all running")and(hB"all running")and(hC"all running");system "t 0";hclose hA; hclose hB; hclose hC]}
which upon initial testing seems to work.

How can I detect the simultaneous pressing of two keys in autohotkey?

I am struggling to find a solution that detects the simultaneous pressing of f and j so that if only f or j is pressed, the current application also receives an f or an j but when I press f and (within a short time period) j simultaneously that the current application receives neither key.
I understand that I somehow have to wait for the short time period (of possibly 0.2 seconds) after an f or j is detected in order to determine if I should use send to give the pressed f or j to the current application or if I have to "swallow" both and to go on with my macro.
Yet, how I should to that, I have no idea.
Relatively simple solution is to detect the combo directly without blocking the input for these two keys. Once it detects that both keys are pressed, delete the input results by sending backspace key 2 times, and then run the desired command. This has the benefit that you can still type text without any issues.
OTOH it will work correctly only for the case where current focus is on the text input application/widget and where backspace means delete last char. In other cases you might get some surprises, e.g. activating random commands.
Here is an example, I used x and c keys here. The place where the command fires is the line send {O}.
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
dt := 30 ; milliseconds sleep
k1name := "x" ; key 1
k2name := "c" ; key 2
loop
{
sleep %dt% ; sleep to reduce CPU usage
k1 := getkeystate(k1name, "P") ; get key 1 state
k2 := getkeystate(k2name, "P") ; get key 2 state
if ( ! ( getkeystate("Ctrl", "P") || getkeystate("Alt", "P") || getkeystate("Shift", "P") ) ) {
if ( k1 && k2 && !trig ) {
send {backspace 2}
send {O} ; send some command
trig := 1 ; set flag to avoid repetition
}
if ( !k1 || !k2 ) {
trig := 0
}
}
}
There can be also solutions with overriding the behavior of those keys, but that will be more complicated and has the issues already mentioned in the comments. I personally would tend to stick to the above solution because of its simplicity.

QBasic: how to run a program from within another program?

I have made two different programs in QBasic and they both are saved in different .bas files, i.e one is 1.bas and the other 2.bas.
How to open program 1.bas while I am in program 2.bas, without closing it?
Program 1 should run inside program 2 for some time, and when it ends I should again be in program 2. Is there any way to do that?
I would like to know if there is a syntax for this that works in QBasic and/or QB64.
In Qbasic you can use CHAIN command to pass control to another .BAS file and when it is finished it will return to the first .BAS file. You can combine it with COMMON to also share variables between the two programs.
You could also use RUN but in QBasic you can't pass variables (not sure but I think the control will not return). And in QB64 it is possible to pass variables using RUN
See the standard COM1_EX.BAS and COM2_EX.BAS as an example, contents of COM1_EX.BAS:
' == COM1_EX.BAS - COMMON statement programming example ==
DIM Values(1 TO 50)
COMMON Values(), NumValues
PRINT "Enter values one per line. Type 'END' to quit."
NumValues = 0
DO
INPUT "-> ", N$
IF I >= 50 OR UCASE$(N$) = "END" THEN EXIT DO
NumValues = NumValues + 1
Values(NumValues) = VAL(N$)
LOOP
PRINT "Leaving COM1_EX.BAS to chain to COM2_EX.BAS"
PRINT "Press any key to chain... "
DO WHILE INKEY$ = ""
LOOP
CHAIN "com2_ex"
contents of COM2_EX.BAS:
' == COM2_EX.BAS - COMMON statement programming example ==
' Notice that the variables Values() and NumValues from COM1_EX
' will be called X() and N here in COM2_EX
DIM X(1 TO 50)
COMMON X(), N
PRINT
PRINT "Now executing file com2_ex.bas, reached through a CHAIN command"
IF N > 0 THEN
Sum = 0
FOR I = 1 TO N
Sum = Sum + X(I)
NEXT I
PRINT "The average of the values is"; Sum / N
END IF
I get zero for average sum. It does not pass the array values
Formerly, I used the Qbasic "Join" command successfully for my lengthy Structural programs but it never returned to the original program. In Structural Designing, you have to create a Member Matrix,Property Matrix,Do some Matrix Multiplications, and Load Matrix. Using another program to Inverse for the resulting Matrix. The "Chain" command in any Basic software does not work at all. There ought to be a command to run one program and go to execute another program and return the values/Text it created. As I am running Win 10, 64 bit and I'm not a Comp. expert,

Autohotkey clipboard variable holding values forever?

I have the below simple code, which sends keystrokes for text in the clipboard with a 15ms delay in between characters (I use this to traverse huge lists of treeview elements).
Issue: If I have copied 'text1' to clipboard, followed by 'text2', this script outputs 'text1text2' instead of 'text2' alone.
If I reload the script, then it prints 'text2'.
Is there a mistake in the below code, or is it a bug in implementing %clipboard% in Autohotkey 1.1.14.03 ?
#v::
textToType=" "
textToType=%clipboard%
LoopCount:=StrLen(textToType)
;StringLen, LoopCount, textToType
Array%LoopCount%:=textToType
loop %LoopCount%
{
theChar:=Array%A_Index%
Send %theChar%
sleep 15
}
return
Update: Thanks for pointing out smarter ways of doing this, but I would still like to figure out what is wrong in the above piece of code.
Update 2:
The mistake was in my understanding of the AHK syntax. Array%LoopCount%:=textToType assigns the whole string value in textToType to the (LoopCount)th STRING element of the STRING array named 'Array'.
Update 3:
(Thanks #John Y for clarifying)
Actually, there's no "declared" array at all, in a traditional sense. You just have a bunch of individual variables, dynamically created as needed, that happen to have names with numbers at the end. Array1 and Array2 are not elements in some Array object. They are just two completely independent variables. AutoHotkey provides a way to glue numbers onto the ends of names, so you can use them like an array.
The reason your script doesn't work properly is because you're using a pseudo-array to store different words from your clipboard.
I've commented up your code to explain what it does:
#v::
textToType := "" ; Empty variable
textToType := Clipboard ; Move clipboard into variable
; Get lenght of the word
; Used as array index / loop count
LoopCount := StrLen(textToType)
; Put the clipboard in an array at index 'LoopCount'
Array%LoopCount% := textToType
; Loop through the array as many times
; as the string is long
Loop % LoopCount
{
; Retrieve the word at this index in the array
theChar := Array%A_Index%
; Send the whole word
Send, % theChar
sleep 15
}
return
Instead of sending each character at a time, you're sending whole words from specific indexes in your Array array.
Say you copy the word Dragon, that word is 6 letters long. So you'd put that in Array6, then you'd loop through your array 6 times using the same variable. At which point the loop would take each index at a time and move it into theChar. On your 6th lap in the loop you'd put Array6 into theChar and print the whole word at once.
Then you copy the word Stackoverflow. That's going to go into Array13, and we're going to loop 13 times. On the 6th lap we're going to print out Dragon which is in Array6, and then keep going until we reach 13 where we'll print Stackoverflow since that is in Array13.
So that's why your script isn't doing what you want it to. Hopefully this helps a little.
See the code sample alpha bravo posted, that's the correct way of achieving what you want to do.
keep it simple
#v::
loop Parse, Clipboard
{
Send %A_LoopField%
sleep 15
}
return
There must be a bug in implementation of clipboard assignment in AHK. With the below code, the behaviour of AHK is that everytime the value of dir is accessed, AHK fetches the latest value from clipboard, instead of fetching the value of dir at the time the script was activated.
; Remove all CR+LF's from the clipboard contents:
dir = %clipboard%
sleep 100
dir := StrReplace(dir, "`r`n")
EDIT:
To fix this, I added 1 second sleep before clipboard assignment code:
sleep 1000
; Remove all CR+LF's from the clipboard contents:
dir = %clipboard%
dir := StrReplace(dir, "`r`n")
100 millisecond sleep didn't seem to work.
Accessing value of dir now only gives value of last clipboard assignment at activation.

Displaying List of AutoHotkey Hotkeys

I’ve written script that contains numerous hotkeys (general structure is as below). I would like to create another one that when pressed displays a list of all of the hotkeys and their corresponding descriptions that the script contains in a nice, formatted table.
The formatting and display are tenuous since AutoHotkey’s output is limited to message-boxes, but possible. More problematic is getting the hotkeys and corresponding descriptions.
The hotkeys all call the same function with different arguments. I considered adding a variable to the function so that depending on the value, the function either performs the normal function when triggered by the normal hotkeys, or builds a string or something when triggered from the special display hotkey.
I cannot figure out a way to programmatically access the script’s hotkeys at all. I checked the docs and there don’t seem to be any A_ variables that can be used for this purpose, nor does the Hotkey command lend itself well (it can be used to test if a hotkey exists, but looping through the innumerable combinations is, at best, tedious).
Failed attempts:
I tried using Elliot’s suggestion of parsing the script itself (replacing the path with %A_ScriptFullPath%, and while it does work for a raw script, it does not when the script is compiled
I tried assigning the entire hotkey section of the script to a variable as a continuation section and then parsing the variable and creating hotkeys using the Hotkey command. This worked well right up until the last part because the Hotkey command cannot take arbitrary commands as the destination and requires existing labels.
The ListHotkeys command is not applicable because it only displays the hotkeys as plain text in the control window.
Does anyone know how I can display a list of the hotkeys and either their corresponding arguments or comments?
Example script:
SomeFunc(foobar)
{
MsgBox %foobar%
}
!^#A::SomeFunc("a") ; blah
^+NumpadMult::SomeFunc("c") ; blivet
^+!#`::SomeFunc("b") ; baz
^#Space::SomeFunc("d") ; ermahgerd
…
Example desired “outputs”:
C+A+ W+ A a | C+ S+ NumpadMult b
------------------+----------------------
C+A+S+W+ ` c | C+ W+ Space d
    or
Ctrl Alt Shift Win Key Action
-----------------------------------------
× × × A blah
× × NumpadMult baz
× × × × ` blivet
× × Space ermahgerd
etc.
The only thing I can think of is to read each line of your script individually and parse it. This code reads your script (script.ahk) one line at a time and parses it. This should get you started. Additionally, you could parse the line to check for the modifiers as well.
Loop
{
FileReadLine, line, C:\script.ahk, %A_Index%
if ErrorLevel
break
If Instr(line, "::")
{
StringSplit, linearray, line, ::,
key := linearray1
StringSplit, commandarray, linearray3, `;
action := commandarray2
hotkeyline := "key: " . key . "`tAction: " . action
final .= hotkeyline . "`r"
}
}
msgbox % final
return
I found a solution. It is not perfect (or ideal), and hopefully a proper, built-in method will become available in the future, but it works well (enough) and for raw and compiled scripts.
What I did was to use the FileInstall command which tells the compiler to add a file to the executable (and extract it when run).
Sadly, the FileInstall command will not allow the use of variables for the source file, so I cannot simply include the script itself (FileInstall, %A_ScriptFullPath%, %A_Temp%\%A_ScriptName%, 1).
As a work-around, I ended up extracting all of the desired hotkeys to a second file which I then parse as Elliot suggested, then delete, and #Include at the end of my script (it must be at the end since hotkeys will terminate the autoexecute section).
;;;;; Test.ahk ;;;;;
; Add hotkey file to executable and extract to Temp directory at runtime
FileInstall, Hotkeys.ahk, %A_Temp%\Hotkeys.ahk, 1
Loop
{
;Read a line from the extracted hotkey script and quit if error
FileReadLine, line, %A_Temp%\Hotkeys.ahk, %A_Index%
if ErrorLevel
break
;Trim whitespace
line=%line%
; Parse the line as in Elliot’s answer, but with tweaks as necessary
ParseHotkey(line)
…
}
FileDelete, %A_Temp%\Hotkeys.ahk ; Delete the extracted script
DisplayHotkeys() ; I ended up bulding and using a GUI instead
#Include, Hotkeys.ahk ; It is included at compile-time, so no A_Temp
;;;;; Hotkeys.ahk ;;;;;
z::MsgBox foo
y::MsgBox bar