I am trying to create a simple InputBox pop-up that asks for an hour:minute combo, and then starts a countdown timer. When the timer reaches zero, it activates a window and then sends input into that window. I have followed the documentation and am still met with the same error over and over Call to nonexistent function. My script used to be far more complicated, but in an effort to troubleshoot, it has gotten progressively simpler and simpler ... and now it basically mirrors the documentation -- and yet, I still get this error. Fresh eyes on this would be greatly appreciated!
:*:obstimer::
;; Show the input box
USER_INPUT := InputBox("This is the prompt","This is the title",W200 H300 T30,"").value
;; Split the input time into hours and minutes
TimeArray := StrSplit(USER_INPUT, ":")
hours := TimeArray[1] ; Get the hours part
minutes := TimeArray[2] ; Get the minutes part
;; Convert the hours and minutes to seconds
loop_timer = (hours * 3600) + (minutes * 60)
;; Set a timer that will trigger the press_backtick function every 1000 milliseconds (1 second)
SetTimer, press_backtick, 1000
return
press_backtick:
loop_timer-- ; Decrement the loop timer by 1
if (loop_timer <= 0) { ; If the loop timer is less than or equal to 0
WinActivate, ahk_exe obs64.exe ; Activate the obs64.exe window
ControlSend, , {U+0060}, ahk_exe obs64.exe ; Send the backtick character ` to the obs64.exe window
SetTimer, press_backtick, off ; Turn off the timer
}
return
Your syntax is so far from being valid AHKv2 syntax, that the AHK script launcher deduces your script as being AHKv1, and therefore you get the error for a non existent function. (A function InputBox doesn't exist in AHKv1, it's a legacy command there)
Here's the script in AHKv2 syntax, there are loads of changes, and you also had some errors which wouldn't have worked even in v1.
I'd recommend you to go through v2 changes before trying to write v2 code (assuming you have a v1 background). Or if this is your first time writing AHK, be sure to look at v2 examples and documentations.
:*:obstimer::
{
global loop_timer
USER_INPUT := InputBox("This is the prompt", "This is the title", "W200 H300 T30", "").value
TimeArray := StrSplit(USER_INPUT, ":")
hours := TimeArray[1]
minutes := TimeArray[2]
loop_timer := (hours * 3600) + (minutes * 60)
SetTimer(press_backtick, 1000)
}
press_backtick()
{
global loop_timer
loop_timer-- ; Decrement the loop timer by 1
if (loop_timer <= 0) {
WinActivate("ahk_exe obs64.exe")
ControlSend("{U+0060}", , "ahk_exe obs64.exe")
SetTimer(press_backtick, 0)
}
}
Related
I want to write a script in autohotkey so that every time I open my dictionary application on PC, keys Windows+LeftArrow being pressed at the same time and as the result, it snaps the windows on the left side of monitor.
I tried this:
#IfWinActive Oxford Advanced Learner's Dictionary
Send, #{Left}
return
Also this one:
#IfWinActive Oxford Advanced Learner's Dictionary
Send, {LWinDown}{Left}{LWinup}
return
But for either of them noting happened when I opened the application.
EDIT:
As suggested by #Charlie Armstrong the real question is: How do I make a block of code run every time I start a certain program? So #IfWinActive might not be useful for.
One way is periodically check if new process/window is created and to check if that is a process/window we want to interact with.
This first example is based on COM notifications when a process has been created/destroyed.
; help for question: https://stackoverflow.com/q/66394326/883015
; by joedf (16:04 2021/02/28)
MyWatchedWindowTitle := "Oxford Advanced Learner's Dictionary"
NewProcess_CheckInterval := 1 ; in seconds
SetTitleMatchMode, 2 ;this might not be needed, makes the check for "contains" instead of "same" winTitle
hWnds := []
gosub, initialize_NewProcessNotification
return
; Called when a new process is detected
On_NewProcess(proc) {
global hWnds
global MyWatchedWindowTitle
; get the window handle, if possible
if (hwnd:=WinExist("ahk_pid " proc.ProcessID)) {
WinGetTitle, wTitle, ahk_id %hwnd%
; check if there is a visible window
if (wTitle)
{
; if so, check if it's a window we want to interact with
if (InStr(wTitle,MyWatchedWindowTitle))
{
; check if we've interacted with this specific window before
if (!ArrayContains(hWnds, hwnd)) {
; we havent, so we do something with it
hWnds.push(hwnd) ; keep in memory that we have interacted with this window ID before.
DoSomething(hwnd) ; the keys we want to send to it
}
}
}
}
}
DoSomething(hwnd) {
; size and move window to the left
SysGet, MonitorWorkArea, MonitorWorkArea
posY := 0
posX := 0
width := A_ScreenWidth // 2
height := MonitorWorkAreaBottom
WinMove, ahk_id %hwnd% ,,%posX%,%posY%,%width%,%height%
; multi-montitor support, more examples, and more complete snapping functions can be found here:
; https://gist.github.com/AWMooreCO/1ef708055a11862ca9dc
}
ArrayContains(haystack, needle) {
for k, v in haystack
{
if (v == needle)
return true
}
return false
}
initialize_NewProcessNotification:
;////////////////////////////// New Process notificaton ////////////////////////
; from Lexikos' example
; https://autohotkey.com/board/topic/56984-new-process-notifier/#entry358038
; Get WMI service object.
winmgmts := ComObjGet("winmgmts:")
; Create sink objects for receiving event noficiations.
ComObjConnect(createSink := ComObjCreate("WbemScripting.SWbemSink"), "ProcessCreate_")
ComObjConnect(deleteSink := ComObjCreate("WbemScripting.SWbemSink"), "ProcessDelete_")
; Set event polling interval, in seconds.
interval := NewProcess_CheckInterval
; Register for process creation notifications:
winmgmts.ExecNotificationQueryAsync(createSink
, "Select * from __InstanceCreationEvent"
. " within " interval
. " where TargetInstance isa 'Win32_Process'")
; Register for process deletion notifications:
winmgmts.ExecNotificationQueryAsync(deleteSink
, "Select * from __InstanceDeletionEvent"
. " within " interval
. " where TargetInstance isa 'Win32_Process'")
; Don't exit automatically.
#Persistent
return
; Called when a new process is detected:
ProcessCreate_OnObjectReady(obj) {
proc := obj.TargetInstance
/*
TrayTip New Process Detected, % "
(LTrim
ID:`t" proc.ProcessID "
Parent:`t" proc.ParentProcessID "
Name:`t" proc.Name "
Path:`t" proc.ExecutablePath "
Command line (requires XP or later):
" proc.CommandLine
)
*/
On_NewProcess(proc)
}
; Called when a process terminates:
ProcessDelete_OnObjectReady(prm) {
/*
obj := COM_DispGetParam(prm, 0, 9)
proc := COM_Invoke(obj, "TargetInstance")
COM_Release(obj)
TrayTip Process Terminated, % "
(LTrim
ID:`t" COM_Invoke(proc, "Handle") "
Name:`t" COM_Invoke(proc, "Name")
)
COM_Release(proc)
*/
}
This second example, which is perhaps a bit simpler, checks periodically for new windows that match the searched WinTitle.
; help for question: https://stackoverflow.com/q/66394326/883015
; by joedf (16:17 2021/02/28)
#Persistent
MyWatchedWindowTitle := "Oxford Advanced Learner's Dictionary"
SetTitleMatchMode, 2 ;this might not be needed, makes the check for "contains" instead of "same" winTitle
SetTimer, checkForNewWindow, 1000 ;ms
hWnds := []
return
checkForNewWindow() {
global hWnds
global MyWatchedWindowTitle
; first check if there is at least one window that matches our winTitle
if (hwnd:=WinExist(MyWatchedWindowTitle)) {
; get all window matches
WinGet, wArray, List , %MyWatchedWindowTitle%
; loop through all windows that matched
loop % wArray
{
hWnd := wArray%A_Index%
; check if we've interacted with this specific window before
if (!ArrayContains(hWnds, hwnd)) {
; we havent, so we do something with it
hWnds.push(hwnd) ; keep in memory that we have interacted with this window ID before.
DoSomething(hwnd) ; the keys we want to send to it
}
}
}
}
DoSomething(hwnd) {
; size and move window to the left
SysGet, MonitorWorkArea, MonitorWorkArea
posY := 0
posX := 0
width := A_ScreenWidth // 2
height := MonitorWorkAreaBottom
WinMove, ahk_id %hwnd% ,,%posX%,%posY%,%width%,%height%
; multi-montitor support, more examples, and more complete snapping functions can be found here:
; https://gist.github.com/AWMooreCO/1ef708055a11862ca9dc
}
ArrayContains(haystack, needle) {
for k, v in haystack
{
if (v == needle)
return true
}
return false
}
I think your biggest issue is that AHK doesn't seem to work well for snapping windows (according to my quick research and testing). What does work well, though, is WinMove.
I assume you're launching the program from a shortcut icon, but I would suggest using a keyboard shortcut that launches the program and then positions the window from the script. Here is some sample code that opens Notepad2.exe, waits for 200 milliseconds, and then moves the window and resizes it:
^+!n:: ; Control+Shift+Alt+N to Open Notepad
Run C:\Program Files\Notepad2\Notepad2.exe
sleep, 200
WinMove, Notepad2,, 10, 20, 800, 600
return
Goals:
Run ahk script so that a window stays active. When the user clicks off the window it immediately becomes active again.
This is so an overlay (considered its own window) can be used in a game and that if the overlay is clicked on by accident the game window will become the active window again.
I would also like this to be able to be turned on and off during game play so that the user can alt+tab if necessary.
Problems:
I'm testing my code implementation, so far i have it set up to make a blank notepad file become the active window and stay active.
The problem is the toggle (ctrl+alt+J). I can toggle the code it off just fine but when i toggle it on the window doesn't become active again.
Code:
stop := 0
; 0 = off, 1 = on
while (stop = 0)
{
IfWinNotActive, Untitled - Notepad
{
WinActivate, Untitled - Notepad
}
}
return
^!j::
stop := !stop
if (stop = 0){
MsgBox, stop is off.
}
else{
MsgBox, stop is on.
}
return
The reason it doesn't work after you toggle it off is that While only runs until is evaluates to false. Even if what it would evaluate later becomes true again, it won't restart.
Here's what you can do to make your current code work:
stop := 0
; 0 = off, 1 = on
labelWinCheck: ; label for GoSub to restart while-loop
while (stop = 0)
{
IfWinNotActive, Untitled - Notepad
{
WinActivate, Untitled - Notepad
}
Sleep , 250 ; added sleep (250 ms) so CPU isn't running full blast
}
return
^!j::
stop := !stop
if (stop = 0){
MsgBox, stop is off.
} else {
MsgBox, stop is on.
}
GoSub , labelWinCheck ; restarts while-loop
return
There are a couple of different ways that I would look at to achieve your goal.
Easy: use SetTimer instead of While.
stop := 0
SetTimer , labelWinCheck , 250 ; Repeats every 250 ms
labelWinCheck:
If !WinActive( "Untitled - Notepad" )
WinActivate , Untitled - Notepad
Return
^!j::
SetTimer , labelWinCheck , % ( stop := !stop ) ? "off" : "on"
MsgBox , % "stop is " . ( stop ? "on" : "off" )
Return
Advanced: us OnMessage() to monitor WinActivate events. I don't have a working example of this as that would take a bit of research for me, but here is a link for a solution I made to monitor keyboard events, Log multiple Keys with AutoHotkey. The links at the bottom may especially prove useful.
In autohotkey im trying to make it so that when I press the left mouse button 3 times with a delay of +/- 10 ms it becomes a volume mute
LButton::
if (?)
{
Send, Volume_Mute
}
else
{
Send, LButton
}
Return
Use A_TickCount to read current time in milliseconds and then calculate the delay between clicks. See Date and Time
ms := A_TickCount
N := 3 ; number of clicks
T := 500 ; max delay between clicks, ms
clicks := 0
~lbutton::
msx := A_TickCount ; get current time
d := msx - ms ; get time past
ms := msx ; remember current time
if (d < T)
clicks += 1
else
clicks := 1
if (clicks >= N)
{
; tooltip %N%-click detected
send {Volume_Mute}
clicks := 0
}
return
Each Autohotkey Script (example.Ahk) that you will run in a loop (running in the background), these loops will repeating in a count off frequence ?...ms (milliseconds)
If you want to use a delay from +- 10ms you will need to change the Timer. (Default = +-250ms)
With the Autohotkey Command (SetTimer) you can change that.
(ps- +-10 ms is very fast i recommend to use a lower Time frequence)
In the Line (SetTimer, CountClicks, 100) you can change(optimize) the number 100. (so that it works fine on your system.)
Note: you can remove the line (msgbox) this is only to show visual how many times you did click.
Try this code:
#NoEnv
#SingleInstance force
;#NoTrayIcon
a1 := -1
b1 := 0
esc::exitapp ;You can click the (esc) key to stop the script.
;if you use ~ it will also use the default function Left-Button-Click.
;and if you Click the Left Mouse Button 3x times, it will Execute Ahk Code Part 3
~LButton::
if(a1 = -1)
{
a1 := 4
#Persistent
SetTimer, CountClicks, 100
}
else
{
a1 := 3
}
return
CountClicks:
if(a1 = 3)
{
b1 := b1 + 1
}
if(a1 = 0)
{
msgbox you did Click <LButton> Key > %b1%x times
if (b1=1)
{
;if Click 1x - Then Execute Ahk Code Part 1
;Here you can put any code for Part 1
}
if (b1=2)
{
;if Click 2x - Then Execute Ahk Code Part 2
;Here you can put any code for Part 2
}
if (b1=3)
{
;if Click 3x - Then Execute Ahk Code Part 3
;Here you can put any code for Part 3
Send {Volume_Mute} ;Send, Volume_Mute
}
if (b1=4)
{
;if Click 4x - Then Execute Ahk Code Part 4
;Here you can put any code for Part 4
}
b1 := 0
SetTimer, CountClicks , off
reload ; restart script
}
a1 := a1 - 1
return
I did test it out on a Windows 10 System and it works.
I am using AutoHotKey's latest release of v1.1
I pass 3 arguments to the program from the commandline:
key, duration, and window saved in %1%, %2%, and %3% respectively.
When printing key duration and window using MsgBox, %1% %2% %3%
I get correct values of lets say in this case a 5 Untitled
duration := %2%
new_duration := (duration * 1000)
MsgBox, %new_duration%
while (A_TickCount - start <= new_duration)
{
ControlSend,,{Blind}{%1% down}{Blind}{%1% up},%3%
sleep 50
}
When the above code is executed it prints nothing not allowing my loop to run.
Why?
I read the documentation a bit more carefully and found A_Args[index]
Completed code:
key := A_Args[1]
duration := (A_Args[2] * 1000)
window := A_Args[3]
while (A_TickCount - start <= duration)
{
ControlSend,,{Blind}{%key% down}{Blind}{%key% up},%window%
sleep 50
}
How to pause the main loop and press buff keys every min? But main loop autohotkey script seems overriding the timer script at both are running in the same time? How can I make the main loop pause and make priority of timer to run press keys every min?
home::
SetTimer, skillbuffs, 60000 ;1min
loop
{
;attack loop and pick items main script
}
skillbuffs:
send{f5} ;press self skill every1min
SetTimer, skillbuffs, On
return
SetTimer runs its tasks in parallel with the rest of the script, which is why you're not seeing the loop waiting.
You could include the loop inside the timer, which will force it to wait for the loop to break before completing its tasks, such as the below code:
DoTheLoop()
{
StartTime := A_TickCount ; Set current tick time (uptime of PC) to compare against.
loop
{
ElapsedTime := A_TickCount - StartTime ; Difference between start of loop and now.
SendInput, %ElapsedTime%{Enter}
Sleep, 1000
if ( ElapsedTime >= 10000 ) ; 10.0 sec.
{
break
}
}}
Home::
SetTimer, skillbuffs
skillbuffs:
DoTheLoop()
SendInput, Skillbuffs timer has fired.{Enter}
SetTimer, skillbuffs, On
return
But this is a kludge at best, and thus isn't 100% reliable. That aside, it seems you're over-engineering the problem if you have clearly defined tasks and a timeframe with which to do it.
Instead, I would recommend going simpler:
MainLoop()
{
StartTime := A_TickCount ; Set current tick time (uptime of PC) to compare against.
loop
{
ElapsedTime := A_TickCount - StartTime ; Difference between start of loop and now.
; Do stuff in the loop.
if ( ElapsedTime >= 60000 ) ; 60.0 sec.
{
SendInput, {F5} ; Press self skill every 60 sec.
StartTime := A_TickCount ; Reset StartTime variable.
}
}}
Home::
MainLoop()
return
Add a Sleep, 10 (Or 100, or whatever duration you feel you can afford) somewhere in your main loop. This will pause the main loop for however many milliseconds, allowing other processes to get run time. This may be especially important if the code is marked Critical.