-
Notifications
You must be signed in to change notification settings - Fork 22
/
Anti-AFK.ahk
398 lines (332 loc) · 13 KB
/
Anti-AFK.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
; /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$$ /$$ /$$
; /$$__ $$ | $$ |__/ /$$__ $$| $$_____/| $$ /$$/
; | $$ \ $$ /$$$$$$$ /$$$$$$ /$$ | $$ \ $$| $$ | $$ /$$/
; | $$$$$$$$| $$__ $$|_ $$_/ | $$ /$$$$$$| $$$$$$$$| $$$$$ | $$$$$/
; | $$__ $$| $$ \ $$ | $$ | $$|______/| $$__ $$| $$__/ | $$ $$
; | $$ | $$| $$ | $$ | $$ /$$| $$ | $$ | $$| $$ | $$\ $$
; | $$ | $$| $$ | $$ | $$$$/| $$ | $$ | $$| $$ | $$ \ $$
; |__/ |__/|__/ |__/ \___/ |__/ |__/ |__/|__/ |__/ \__/
;
; ------------------------------------------------------------------------------
; Configuration
; ------------------------------------------------------------------------------
; POLL_INTERVAL (Seconds):
; This is the interval which Anti-AFK checks for new windows and calculates
; how much time is left before exisiting windows become inactve.
POLL_INTERVAL := 5
; WINDOW_TIMEOUT (Minutes):
; This is the amount of time before a window is considered inactive. After
; a window has timed out, Anti-AFK will start resetting any AFK timers.
WINDOW_TIMEOUT := 10
; TASK (Function):
; This is a function that will be ran by the script in order to reset any
; AFK timers. The target window will have focus while it is being executed.
; You can customise this function freely - just make sure it resets the timer.
TASK := () => (
Send("{Space Down}")
Sleep(50)
Send("{Space Up}")
)
; TASK_INTERVAL (Minutes):
; This is the amount of time the script will wait after calling the TASK function
; before calling it again.
TASK_INTERVAL := 15
; BLOCK_INPUT (Boolean):
; This tells the script whether you want to block input whilst it shuffles
; windows and sends input. This requires administrator permissions and is
; therefore disabled by default. If input is not blocked, keystrokes from the
; user may 'leak' into the window while Anti-AFK moves it into focus.
BLOCK_INPUT := False
; PROCESS_LIST (Array):
; This is a list of processes that Anti-AFK will montior. Any windows that do
; not belong to any of these processes will be ignored.
PROCESS_LIST := ["notepad.exe", "wordpad.exe"]
; PROCESS_OVERRIDES (Associative Array):
; This allows you to specify specific values of WINDOW_TIMEOUT, TASK_INTERVAL,
; TASK and BLOCK_INPUT for specific processes. This is helpful if different
; games consider you AFK at wildly different times, or if the function to
; reset the AFK timer does not work as well across different applications.
PROCESS_OVERRIDES := Map(
"wordpad.exe", Map(
"WINDOW_TIMEOUT", 5,
"TASK_INTERVAL", 5,
"BLOCK_INPUT", False,
"TASK", () => (
Send("w")
)
)
)
; ------------------------------------------------------------------------------
; Script
; ------------------------------------------------------------------------------
#SingleInstance
InstallKeybdHook()
InstallMouseHook()
windowList := Map()
for _, program in PROCESS_LIST
windowList[program] := Map()
; Check if the script is running as admin and if keystrokes need to be blocked. If it does not have admin
; privileges the user is prompted to elevate it's permissions. Should they deny, the ability to block input
; is disabled and the script continues as normal.
if (!A_IsAdmin)
{
requireAdmin := BLOCK_INPUT
for program, override in PROCESS_OVERRIDES
if (override.Has("BLOCK_INPUT") && override["BLOCK_INPUT"])
requireAdmin := True
if (requireAdmin)
{
try
{
if A_IsCompiled
RunWait('*RunAs "' A_ScriptFullPath '" /restart')
else
RunWait('*RunAs "' A_AhkPath '" /restart "' A_ScriptFullPath '"')
}
MsgBox(
"This requires Anti-AFK to be run as Admin`nIt has been temporarily disabled",
"Cannot Block Keystrokes", "OK Icon!"
)
}
}
; Reset the AFK timer for a particular window, blocking input if required.
; Input is sent directly to the target window if it's active; If there is no active window the target
; window is made active.
; If another window is active, its handle is stored while the target is made transparent and activated.
; Any AFK timers are reset and the target is sent to the back before being made opaque again. Focus is then
; restored to the original window.
resetTimer(windowID, resetAction, DenyInput)
{
activeInfo := getWindowInfo("A")
targetInfo := getWindowInfo("ahk_id " windowID)
targetWindow := "ahk_id " targetInfo["ID"]
; Activates the target window if there is no active window or the Desktop is focused.
; Bringing the Desktop window to the front can cause some scaling issues, so we ignore it.
; The Desktop's window has a class of "WorkerW" or "Progman".
if (!activeInfo.Count || (activeInfo["CLS"] = "WorkerW" || activeInfo["CLS"] = "Progman"))
activateWindow(targetWindow)
; Send input directly if the target window is already active.
if (WinActive(targetWindow))
{
resetAction()
return
}
if (DenyInput && A_IsAdmin)
BlockInput("On")
WinSetTransparent(0, targetWindow)
activateWindow(targetWindow)
resetAction()
WinMoveBottom(targetWindow)
WinSetTransparent("OFF", targetWindow)
oldActiveWindow := getWindow(
activeInfo["ID"],
activeInfo["PID"],
activeInfo["EXE"],
targetWindow
)
activateWindow(oldActiveWindow)
if (DenyInput && A_IsAdmin)
BlockInput("Off")
}
; Fetch the window which best matches the given criteria.
; Some windows are ephemeral and will be closed after user input. In this case we try
; increasingly vague identifiers until we find a related window. If a window is still
; not found a fallback is used instead.
getWindow(window_ID, process_ID, process_name, fallback)
{
if (WinExist("ahk_id " window_ID))
return "ahk_id " window_ID
if (WinExist("ahk_pid " process_ID))
return "ahk_pid " process_ID
if (WinExist("ahk_exe " process_name))
return "ahk_exe " process_name
return fallback
}
; Get information about a window so that it can be found and reactivated later.
getWindowInfo(target)
{
windowInfo := Map()
if (!WinExist(target))
return windowInfo
windowInfo["ID"] := WinGetID(target)
windowInfo["CLS"] := WinGetClass(target)
windowInfo["PID"] := WinGetPID(target)
windowInfo["EXE"] := WinGetProcessName(target)
return windowInfo
}
; Activate a window and yield until it does so.
activateWindow(target)
{
if (!WinExist(target))
return False
WinActivate(target)
WinWaitActive(target)
return True
}
; Calculate the number of polls it will take for the time (in seconds) to pass.
getLoops(value)
{
return Max(1, Round(value*60 / POLL_INTERVAL))
}
; Find and return a specific attribute for a program, prioritising values in PROCESS_OVERRIDES.
; If an override has not been setup for that process, the default value for all programs will be used instead.
getValue(value, program)
{
if (PROCESS_OVERRIDES.Has(program) && PROCESS_OVERRIDES[program].Has(value))
return PROCESS_OVERRIDES[program][value]
return %value%
}
; Create and return an updated copy of the old window list. A new list is made from scratch and
; populated with the current windows. Timings for these windows are then copied from the old list
; if they are present, otherwise the default timeout is assigned.
updateWindowList(oldWindowList, processList)
{
newWindowList := Map()
for _, program in processList
{
newList := Map()
for _, handle in WinGetList("ahk_exe" program)
{
if (oldWindowList[program].Has(handle))
newList[handle] := oldWindowList[program][handle]
else
newList[handle] := Map(
"value", getLoops(getValue("WINDOW_TIMEOUT", program)),
"type", "Timeout"
)
}
newWindowList[program] := newList
}
return newWindowList
}
; Dynamically update the System Tray icon and tooltip text, taking into consideration the number
; of windows that the script has found and the number of windows it is managing.
updateSysTray(windowList)
{
; Count how many windows are actively managed and how many
; are being monitored so we can guage the script's activity.
managed := Map()
monitor := Map()
for program, windows in windowList
{
managed[program] := 0
monitor[program] := 0
for _, waitInfo in windows
{
if (waitInfo["type"] = "Timeout")
monitor[program] += 1
else if (waitInfo["type"] = "Interval")
managed[program] += 1
}
if (managed[program] = 0)
managed.Delete(program)
if (monitor[program] = 0)
monitor.Delete(program)
}
; If windows are being managed that means the script is periodically
; sending input. We update the SysTray to with the number of windows
; that are being managed.
if (managed.Count > 0)
{
TraySetIcon(A_AhkPath, 2)
if (monitor.Count > 0)
{
newTip := "Managing:`n"
for program, windows in managed
newTip := newTip program " - " windows "`n"
newTip := newTip "`nMonitoring:`n"
for program, windows in monitor
newTip := newTip program " - " windows "`n"
newTip := RTrim(newTip, "`n")
A_IconTip := newTip
}
else
{
newTip := "Managing:`n"
for program, windows in managed
newTip := newTip program " - " windows "`n"
newTip := RTrim(newTip, "`n")
A_IconTip := newTip
}
return
}
; If we are not managing any windows but the script is still monitoring
; them in case they go inactive, the SysTray is updated with the number
; of windows that we are watching.
if (monitor.Count > 0)
{
TraySetIcon(A_AhkPath, 3)
newTip := "Monitoring:`n"
for program, windows in monitor
newTip := newTip program " - " windows "`n"
newTip := RTrim(newTip, "`n")
A_IconTip := newTip
return
}
; If we get to this point the script is not managing or watching any windows.
; Essensially the script isn't doing anything and we make sure the icon conveys
; this if it hasn't already.
TraySetIcon(A_AhkPath, 5)
A_IconTip := "No windows found"
}
; Go through each window in the list and decrement it's timer.
; If the timer reaches zero the TASK function is ran and the timer is set back to it's starting value.
tickWindowList(windowList)
{
for program, windows in windowList
{
for handle, timeLeft in windows
{
if (WinActive("ahk_id" handle))
{
; If the program is active and has not timed out, we set it's timeout back to
; the limit. The user will need to interact with it to send it to the back and
; we use A_TimeIdlePhysical rather then our own timeout if it's in the foreground.
if (A_TimeIdlePhysical < getValue("WINDOW_TIMEOUT", program) * 60000)
{
timeLeft := Map(
"type", "Timeout",
"value", getLoops(getValue("WINDOW_TIMEOUT", program))
)
windowList[program][handle] := timeLeft
continue
}
; If the program has timed out we need to update the WindowList to reflect that.
; We can achieve this by setting the time left to one. It will be decremented immediately
; afterwards and the script will activate as it sees the time left has reached zero.
if (timeLeft["type"] = "Timeout")
timeLeft["value"] = 1
}
; Decrement the time left, if it reaches zero reset the AFK timer. Then reset the time
; left and repeat.
timeLeft["value"] -= 1
if (timeLeft["value"] = 0)
{
timeLeft := Map(
"type", "Interval",
"value", getLoops(getValue("TASK_INTERVAL", program))
)
resetTimer(
handle,
getValue("TASK", program),
getValue("BLOCK_INPUT", program)
)
}
windowList[program][handle] := timeLeft
}
}
return windowList
}
updateScript()
{
global windowList
global BLOCK_INPUT
global PROCESS_LIST
global PROCESS_OVERRIDES
windowList := updateWindowList(windowList, PROCESS_LIST)
windowList := tickWindowList(windowList)
updateSysTray(windowList)
}
; Start Anti-AFK
updateScript()
SetTimer(updateScript, POLL_INTERVAL*1000)