Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 88 additions & 82 deletions Anti-AFK.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
; | $$ | $$| $$ | $$ | $$ /$$| $$ | $$ | $$| $$ | $$\ $$
; | $$ | $$| $$ | $$ | $$$$/| $$ | $$ | $$| $$ | $$ \ $$
; |__/ |__/|__/ |__/ \___/ |__/ |__/ |__/|__/ |__/ \__/
;
;
#Requires AutoHotkey v2.0

; ------------------------------------------------------------------------------
; Configuration
; ------------------------------------------------------------------------------
Expand Down Expand Up @@ -77,15 +79,13 @@ for _, program in PROCESS_LIST
; 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)
{
if (!A_IsAdmin) {
requireAdmin := BLOCK_INPUT
for program, override in PROCESS_OVERRIDES
if (override.Has("BLOCK_INPUT") && override["BLOCK_INPUT"])
requireAdmin := True

if (requireAdmin)
{
if (requireAdmin) {
try
{
if A_IsCompiled
Expand All @@ -107,59 +107,88 @@ if (!A_IsAdmin)
; 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)
{
resetTimer(windowID, resetAction, DenyInput) {
activeInfo := getWindowInfo("A")
targetInfo := getWindowInfo("ahk_id " windowID)

if (!targetInfo.Count)
return False

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)
if (!activeInfo.Count || (activeInfo["CLS"] = "WorkerW" || activeInfo["CLS"] = "Progman")) {
if (activateWindow(targetWindow)) {
resetAction()
return True
}

return False
}

; Send input directly if the target window is already active.
if (WinActive(targetWindow))
{
if (WinActive(targetWindow)) {
resetAction()
return
return True
}

if (DenyInput && A_IsAdmin)
BlockInput("On")
inputWasBlocked := False
transparencyApplied := False

WinSetTransparent(0, targetWindow)
activateWindow(targetWindow)
try
{
if (DenyInput && A_IsAdmin) {
BlockInput("On")
inputWasBlocked := True
}

resetAction()
try
{
WinSetTransparent(0, targetWindow)
transparencyApplied := True
}

WinMoveBottom(targetWindow)
WinSetTransparent("OFF", targetWindow)
if (!activateWindow(targetWindow))
return False

oldActiveWindow := getWindow(
activeInfo["ID"],
activeInfo["PID"],
activeInfo["EXE"],
targetWindow
)
resetAction()

activateWindow(oldActiveWindow)
WinMoveBottom(targetWindow)

if (transparencyApplied) {
try WinSetTransparent("OFF", targetWindow)
transparencyApplied := False
}

if (DenyInput && A_IsAdmin)
BlockInput("Off")
oldActiveWindow := getWindow(
activeInfo["ID"],
activeInfo["PID"],
activeInfo["EXE"],
targetWindow
)

activateWindow(oldActiveWindow)
return True
}
finally {
if (transparencyApplied)
try WinSetTransparent("OFF", targetWindow)

if (inputWasBlocked)
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)
{
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

Expand All @@ -170,8 +199,7 @@ getWindow(window_ID, process_ID, process_name, fallback)
}

; Get information about a window so that it can be found and reactivated later.
getWindowInfo(target)
{
getWindowInfo(target) {
windowInfo := Map()

if (!WinExist(target))
Expand All @@ -186,44 +214,36 @@ getWindowInfo(target)
}

; Activate a window and yield until it does so.
activateWindow(target)
{
activateWindow(target, timeoutSeconds := 2) {
if (!WinExist(target))
return False

WinActivate(target)
WinWaitActive(target)

return True
return WinWaitActive(target, , timeoutSeconds) != 0
}

; 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))
; Calculate the number of polls it will take for the time (in minutes) 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)
{
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)
{
updateWindowList(oldWindowList, processList) {
newWindowList := Map()
for _, program in processList
{
for _, program in processList {
newList := Map()
for _, handle in WinGetList("ahk_exe" program)
{
for _, handle in WinGetList("ahk_exe " program) {
if (oldWindowList[program].Has(handle))
newList[handle] := oldWindowList[program][handle]
else
Expand All @@ -241,19 +261,16 @@ updateWindowList(oldWindowList, processList)

; 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)
{
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
{
for program, windows in windowList {
managed[program] := 0
monitor[program] := 0

for _, waitInfo in windows
{
for _, waitInfo in windows {
if (waitInfo["type"] = "Timeout")
monitor[program] += 1
else if (waitInfo["type"] = "Interval")
Expand All @@ -270,12 +287,10 @@ updateSysTray(windowList)
; 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)
{
if (managed.Count > 0) {
TraySetIcon(A_AhkPath, 2)

if (monitor.Count > 0)
{
if (monitor.Count > 0) {
newTip := "Managing:`n"
for program, windows in managed
newTip := newTip program " - " windows "`n"
Expand All @@ -287,8 +302,7 @@ updateSysTray(windowList)
newTip := RTrim(newTip, "`n")
A_IconTip := newTip
}
else
{
else {
newTip := "Managing:`n"
for program, windows in managed
newTip := newTip program " - " windows "`n"
Expand All @@ -303,8 +317,7 @@ updateSysTray(windowList)
; 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)
{
if (monitor.Count > 0) {
TraySetIcon(A_AhkPath, 3)

newTip := "Monitoring:`n"
Expand All @@ -326,19 +339,14 @@ updateSysTray(windowList)

; 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))
{
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)
{
if (A_TimeIdlePhysical < getValue("WINDOW_TIMEOUT", program) * 60000) {
timeLeft := Map(
"type", "Timeout",
"value", getLoops(getValue("WINDOW_TIMEOUT", program))
Expand All @@ -352,15 +360,14 @@ tickWindowList(windowList)
; 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
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)
{

if (timeLeft["value"] = 0) {
timeLeft := Map(
"type", "Interval",
"value", getLoops(getValue("TASK_INTERVAL", program))
Expand All @@ -380,8 +387,7 @@ tickWindowList(windowList)
return windowList
}

updateScript()
{
updateScript() {
global windowList
global BLOCK_INPUT
global PROCESS_LIST
Expand All @@ -395,4 +401,4 @@ updateScript()

; Start Anti-AFK
updateScript()
SetTimer(updateScript, POLL_INTERVAL*1000)
SetTimer(updateScript, POLL_INTERVAL * 1000)