Cross-platform anti-idle utility that keeps Microsoft Teams showing Available by resetting the OS idle timer with an invisible input signal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
134 lines
4.3 KiB
Python
134 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""stay_active_win.py — keep Microsoft Teams showing 'Available' on Windows.
|
|
|
|
Teams marks you 'Away' after ~5 minutes of no keyboard/mouse input. This tool
|
|
resets Windows' idle timer periodically by sending a harmless input via the Win32
|
|
SendInput API. It also optionally tells Windows not to sleep/turn off the display
|
|
while running.
|
|
|
|
No third-party packages required — pure ctypes against user32/kernel32.
|
|
Windows only. Press Ctrl+C to stop.
|
|
"""
|
|
|
|
import argparse
|
|
import ctypes
|
|
import sys
|
|
import time
|
|
from ctypes import wintypes
|
|
|
|
if not sys.platform.startswith("win"):
|
|
sys.exit("This script is for Windows. On Linux use stay_active.py instead.")
|
|
|
|
user32 = ctypes.WinDLL("user32", use_last_error=True)
|
|
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
|
|
# --- SendInput structures ----------------------------------------------------
|
|
|
|
ULONG_PTR = wintypes.WPARAM
|
|
|
|
INPUT_MOUSE = 0
|
|
INPUT_KEYBOARD = 1
|
|
|
|
MOUSEEVENTF_MOVE = 0x0001
|
|
|
|
KEYEVENTF_KEYUP = 0x0002
|
|
VK_F15 = 0x7E # virtual key; valid even without a physical F15 on the keyboard
|
|
|
|
# SetThreadExecutionState flags (keep system/display awake)
|
|
ES_CONTINUOUS = 0x80000000
|
|
ES_SYSTEM_REQUIRED = 0x00000001
|
|
ES_DISPLAY_REQUIRED = 0x00000002
|
|
|
|
|
|
class MOUSEINPUT(ctypes.Structure):
|
|
_fields_ = [
|
|
("dx", wintypes.LONG),
|
|
("dy", wintypes.LONG),
|
|
("mouseData", wintypes.DWORD),
|
|
("dwFlags", wintypes.DWORD),
|
|
("time", wintypes.DWORD),
|
|
("dwExtraInfo", ULONG_PTR),
|
|
]
|
|
|
|
|
|
class KEYBDINPUT(ctypes.Structure):
|
|
_fields_ = [
|
|
("wVk", wintypes.WORD),
|
|
("wScan", wintypes.WORD),
|
|
("dwFlags", wintypes.DWORD),
|
|
("time", wintypes.DWORD),
|
|
("dwExtraInfo", ULONG_PTR),
|
|
]
|
|
|
|
|
|
class _INPUTunion(ctypes.Union):
|
|
_fields_ = [("mi", MOUSEINPUT), ("ki", KEYBDINPUT)]
|
|
|
|
|
|
class INPUT(ctypes.Structure):
|
|
_fields_ = [("type", wintypes.DWORD), ("u", _INPUTunion)]
|
|
|
|
|
|
def _send(*inputs):
|
|
n = len(inputs)
|
|
arr = (INPUT * n)(*inputs)
|
|
sent = user32.SendInput(n, arr, ctypes.sizeof(INPUT))
|
|
if sent != n:
|
|
raise ctypes.WinError(ctypes.get_last_error())
|
|
|
|
|
|
def nudge_mouse():
|
|
# Relative move +1px then -1px: cursor ends where it started, effectively invisible.
|
|
right = INPUT(type=INPUT_MOUSE, u=_INPUTunion(mi=MOUSEINPUT(1, 0, 0, MOUSEEVENTF_MOVE, 0, 0)))
|
|
left = INPUT(type=INPUT_MOUSE, u=_INPUTunion(mi=MOUSEINPUT(-1, 0, 0, MOUSEEVENTF_MOVE, 0, 0)))
|
|
_send(right, left)
|
|
|
|
|
|
def tap_f15():
|
|
down = INPUT(type=INPUT_KEYBOARD, u=_INPUTunion(ki=KEYBDINPUT(VK_F15, 0, 0, 0, 0)))
|
|
up = INPUT(type=INPUT_KEYBOARD, u=_INPUTunion(ki=KEYBDINPUT(VK_F15, 0, KEYEVENTF_KEYUP, 0, 0)))
|
|
_send(down, up)
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description="Keep Teams presence 'Available' on Windows.")
|
|
p.add_argument("-i", "--interval", type=float, default=120,
|
|
help="seconds between signals (default: 120; keep under Teams' ~300s Away threshold)")
|
|
p.add_argument("-m", "--method", choices=["mouse", "key"], default="key",
|
|
help="how to signal activity (default: key — an invisible F15 tap)")
|
|
p.add_argument("--no-sleep", action="store_true",
|
|
help="also prevent the system and display from sleeping while running")
|
|
p.add_argument("-q", "--quiet", action="store_true", help="don't print heartbeat messages")
|
|
args = p.parse_args()
|
|
|
|
action = tap_f15 if args.method == "key" else nudge_mouse
|
|
label = "tapping F15" if args.method == "key" else "nudging mouse 1px"
|
|
|
|
if args.no_sleep:
|
|
flags = ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
|
|
kernel32.SetThreadExecutionState(flags)
|
|
|
|
if not args.quiet:
|
|
awake = " (sleep inhibited)" if args.no_sleep else ""
|
|
print(f"stay_active: {label} every {args.interval:g}s{awake}. Press Ctrl+C to stop.")
|
|
|
|
taps = 0
|
|
try:
|
|
while True:
|
|
action()
|
|
taps += 1
|
|
if not args.quiet:
|
|
print(f"\r active — {taps} signal(s) sent, last at {time.strftime('%H:%M:%S')}",
|
|
end="", flush=True)
|
|
time.sleep(args.interval)
|
|
except KeyboardInterrupt:
|
|
if not args.quiet:
|
|
print("\nstay_active: stopped.")
|
|
finally:
|
|
if args.no_sleep:
|
|
kernel32.SetThreadExecutionState(ES_CONTINUOUS) # release the keep-awake request
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|