How can I wait for only one of three events (mouse input, keyboard input, or WinWaitClose) in AutoHotkey? - autohotkey

In my scenario, either a window will close automatically, or the user will provide an input via mouse or keyboard.
If the window closes automatically, I want to open a PDF. This is easy:
WinWaitClose, ahk_id %cmdHwnd%
Run, "C:\Program Files\SumatraPDF\SumatraPDF.exe" "%path%\cv.pdf"
But if the window doesn't close automatically (i.e. the PDF failed to compile), then the user's going to close that window manually, e.g. hit Enter or click the close button. In this case, I do not want the above WinWaitClose to trigger!
So another way of putting my question is, how do I "cancel" a WinWaitClose listener upon mouse or keyboard input?

I suppose you could rely on the Seconds timer in WinWaitClose, and if that fails, you can rely on the ErrorLevel which will be set to 1.
; Set WinWaitTimer to wait 2 seconds before timing out.
WinWaitClose, ahk_id %cmdHwnd%,, 2
if (ErrorLevel) {
; Do something.
}
else
{
Run, "C:\Program Files\SumatraPDF\SumatraPDF.exe" "%path%\cv.pdf"
}

Related

AHK Click event while Toggled

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

WinActivate does not work as expected. Re-activating focus to the starting window

I am having some serious struggles fully grasping the control on activating windows and forcing their focus and foremost position.
In order to debug a larger script I made a separate script to test the use of WinActivate and again I am observing frustrating behaviour as it either all together ignores the title I have defined or is failing in some other way. In the smaller test script I am simply requesting that the window in which the hotkey was triggered be set as active after another action, specifically an input box
Below is the simple code for testing:
F10::
SetTitleMatchMode, 1
DetectHiddenWindows, Off
WinGetTitle, startTitle, A
msgbox % "Start Title = <" . startTitle . ">"
;WinActivate, startTitle
inputbox, mode, Test box, Testing,,260,160
sleep 500
WinActivate, startTitle
Return
This code does not properly activate the starting window. For example I execute the hotkey in an empty notepad window and upon submitting blank into the input box the focus becomes notepad++ on my second monitor. The second time I press the hotkey from within notepad (or another application) notepad does not lose focus. In a third execution I begin from notepad again and after the input box appears I switch the focus to another window. I again submit blank to the inputbox but that new window remains the focus and notepad is not activated or brought to the foremost position.
Can someone please explain to me what is going on with WinActivate?
I was having similar frustration with unexpected results making a windows script host file and I think I must be missing some fundamental detail in windows.
You are trying to activate a window that start with the literal text "startTitle".
You forgot(?) to either enter expression syntax with % or use the legacy way of referring to a variable %startTitle% (please don't use legacy).
Extra stuff:
You shouldn't specify SetTitleMatchMode and DetectHiddenWindows inside your hotkey statement. There is no need (unless there actually is) to set those every time you hit the hotkey. Just specify them at the top of your script once.
Both of them are useless for you though, below I'll show why. Also DetectHiddenWindows is already off by default.
WinGetTitle is not good to use for this. What you actually want to do is get the hwnd of the window you wish by using e.g. WinExist().
And then refer to the window by its hwnd. Much better than working with window titles, and impossible to match the wrong window as well. To refer to a window by its hwnd, you specify ahk_id followed by the hwnd on a WinTitle parameter.
And lastly, the concatenation operator . is redundant. Of course you may prefer to use it, but in case you didn't know, it can just be left out.
Here's your revised code:
F10::
_HWND := WinExist("A")
MsgBox, % "Start hwnd = <" _HWND ">"
InputBox, mode, Test box, Testing,,260,160
Sleep, 500
WinActivate, % "ahk_id " _HWND
Return

autohotkey type in another window with gui

Autohotkey Example Needed
Need some help, please. I've searched and can't seem to find an example of what I need.
What I want to do is create a ahk dialog box with a button (I can do this part), and when I click on it, it will type some text into another window. Basically, I want want to offload the "shortcut" to a "mouse click". But, without mapping a shortcut.
Something like this:
When user clicks BOX1, "text" is stored. Then, when user clicks elsewhere, vBOX1 is typed into the cursor location of the window activated by that click
I hope I'm explaining this succinctly. Any help would be appreciated.
Here are two possible alternatives:
First alternative expands #scso's suggestion:
~LButton::
sleep, 200 ;give the window below the cursor some time to get activated
Send, %vBOX1%
return
Now this may seem fine but what it actually does is type the text EVERY time you click the mouse in ANY window. Let's put an additional check so if vBOX1 is empty it doesn't type anything.
~LButton::
sleep, 200 ;give the window below the cursor some time to get activated
If (vBOX1 != "")
{
Send, %vBOX1%
vBOX1 := "" ; clears the contents of vBOX1
}
return
Second alternative:
You use the mouse clicks normally and the text gets typed only when you do Control + Click.
So in order to type the text you need to click once to select the window and then control+click to do the actual typing
~^LButton::
Send, %vBOX1%
vBOX1 := "" ; clears the contents of vBOX1
return
You can expand both alternatives by adding commands for detecting the active window and then typing the text or changing the mouse click combination to something else.

traytip when window becomes active?

I want to simply display a tooltip when a window becomes active.
Why doesn't this work? It launches the tooltip as soon as the script is loaded.
#IfWinActive, Untitled - Notepad
{
TrayTip, Notepad Has Focus, test
Tab::
MsgBox Window Found
return
}
Tab detection works as expected, it shows the Message Box only if the window is active.
As per the #If... docs, #IfWinActive creates context-sensitive hotkeys and hotstrings. To be a bit more precise, this is what happens when you use #IfWin...:
Whenever you press a hotkey or type a hotstring, AHK looks up the corresponding #IfWin... definition (if available) and evaluates it (e.g. "Is notepad active?"). If it is true, the hotkey/hotstring label will be executed, otherwise the native key will be sent.
Looking at this procedure, you will recognize that executing arbitrary code below a #IfWin... statement won't work; AHK doesn't fire an event when a specified window becomes active/existent etc, it rather checks the conditions when a corresponding hotkey/hotstring is fired.
Ergo, you will have to write code that waits for notepad, shows a notification and possibly repeats this procedure:
#Persistent
SetTimer, WaitForNotepad, -1
Exit
WaitForNotepad:
WinWaitActive, ahk_class Notepad
TrayTip, Warning, Notepad is active!
WinWaitNotActive
SetTimer, WaitForNotepad, -1
return
Please note that this would also work without SetTimer in some kind of loop. But whenever you're waiting a potentially large amount of time, it is reasonable to use timers, since they virtually allow other threads to run in between.
You also noticed that I used the window class (ahk_class) instead of the window title, since it's usually more reliable.

How to simulate key press on a background window

I need to force Outlook which is in the background, to check for new emails every 2 seconds. So I wrote a following script but it doesn't do it. What's wrong? I don't want the script to disturb what I do and give the focus to the Outlook window. The "ahk_class rctrl_renwnd32" is correct, I checked it with "WinActivate, ahk_class rctrl_renwnd32" and it worked.
Loop
{
ControlSend,, {F9}, ahk_class rctrl_renwnd32
Sleep 2000 ; Wait 2 seconds
}
There is no error in your code. The problem might be in the receiving application. When I test this in Notepad, an F5 (Print time & date) is executed when the Notepad window is open somewhere on my second screen. As soon as I minimize Notepad, it will no longer execute F5, but it still accepts a string like A{Enter}B{Enter}C{Enter} when minimized.
!q::
ControlSend,, {F5}A{Enter}B{Enter}C{Enter}, ahk_class Notepad
Return
Solution, Try if this works when you keep the window on screen somewhere (no need to have the focus).
I have used Outlook in the past and remember that F9 took some time to execute. Running this every 2 seconds looks like overkill.
If getting your e-mail in time is THAT important that you are willing to "kill" the mail server with refresh requests, I would discuss a solution with your IT support.