traytip when window becomes active? - autohotkey

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.

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 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.

How can I wait for only one of three events (mouse input, keyboard input, or WinWaitClose) in 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"
}

Auto Hot Key: Strange behavior on ControlClick after Gui Submit

I have the following script that sends ControlClick + ControlSend to a window in background (while I'm working in another window). It works ok as expected.
SetTitleMatchMode 2
$F1::
sleep 1000
ControlClick, x400 y470, Notepad
ControlSend,, text, Notepad
return
The problem is: when I add a Gui on the script, the ControlClick behavior becomes odd once I close the Gui (with Submit or Destroy): If I'm working in a window A (Chrome for example) the ControlClick don't work in background anymore: it activates the window B (Notepad) like in the WinActivate command instead.
Here's the script with the issue (same as the previous one but with a simple Gui):
SetTitleMatchMode 2
Gui, Add, Text,, box
Gui, Add, Button, default, OK
Gui, Show, W300 H300
return
GuiClose:
ButtonOK:
Gui, Submit
return
$F1::
sleep 1000
ControlClick, x400 y470, Notepad
ControlSend,, text, Notepad
return
I'm on Windows Vista 32 bits, Autohotkey v1.1.25.01
I can't understand why the Gui Submit is changing the ControlClick bahavior. How can I fix this and let the ControlClick run in background just like it was without the Gui Submit/Destroy?
ps: both windows were maximized.
ControlClick is known to not be very reliable in certain cases. Take a look at the Reliability section in the ControlClick documentation, there are some things you can try:
To improve reliability -- especially during times when the user is
physically moving the mouse during the ControlClick -- one or both of
the following may help:
Use SetControlDelay -1 prior to ControlClick. This avoids holding
the mouse button down during the click, which in turn reduces
interference from the user's physical movement of the mouse.
Specify the string NA anywhere in the sixth parameter (Options) as
shown below:
SetControlDelay -1
ControlClick, Toolbar321, WinTitle,,,, NA
NA avoids marking the target window as active and avoids
merging its input processing with that of the script, which may
prevent physical movement of the mouse from interfering (but usually
only when the target window is not active). However, this method might
not work for all types of windows and controls.