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>
114 lines
4.1 KiB
Python
Executable File
114 lines
4.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""stay_active.py — keep Microsoft Teams (and any OS-idle-based presence) showing 'Available'.
|
|
|
|
Teams marks you 'Away' when the OS reports no keyboard/mouse activity for ~5 minutes.
|
|
This tool resets the X11 idle timer periodically by tapping a harmless key (F15 by
|
|
default — it has no visible effect and is not bound to anything on a normal desktop).
|
|
|
|
No third-party packages required: it calls the X11 XTest extension directly via ctypes.
|
|
X11 sessions only (XDG_SESSION_TYPE=x11). Press Ctrl+C to stop.
|
|
"""
|
|
|
|
import argparse
|
|
import ctypes
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
# --- X11 / XTest bindings via ctypes -----------------------------------------
|
|
|
|
try:
|
|
_x11 = ctypes.CDLL("libX11.so.6")
|
|
_xtst = ctypes.CDLL("libXtst.so.6")
|
|
except OSError as e:
|
|
sys.exit(f"Could not load X11 libraries: {e}\nThis tool requires an X11 session.")
|
|
|
|
_x11.XOpenDisplay.restype = ctypes.c_void_p
|
|
_x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
|
_x11.XKeysymToKeycode.restype = ctypes.c_uint
|
|
_x11.XKeysymToKeycode.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
|
|
_x11.XFlush.argtypes = [ctypes.c_void_p]
|
|
_xtst.XTestFakeKeyEvent.argtypes = [
|
|
ctypes.c_void_p, ctypes.c_uint, ctypes.c_int, ctypes.c_ulong
|
|
]
|
|
_xtst.XTestFakeRelativeMotionEvent.argtypes = [
|
|
ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_ulong
|
|
]
|
|
|
|
# X keysyms for a few no-op keys. F15 is the safest default.
|
|
KEYSYMS = {
|
|
"f13": 0xFFCA,
|
|
"f14": 0xFFCB,
|
|
"f15": 0xFFCC,
|
|
"shift": 0xFFE1, # Shift_L — no character, but modifies if held with another key
|
|
}
|
|
|
|
|
|
def open_display():
|
|
disp = _x11.XOpenDisplay(None)
|
|
if not disp:
|
|
display = os.environ.get("DISPLAY", "(unset)")
|
|
sys.exit(f"Could not open X display (DISPLAY={display}). Run inside your desktop session.")
|
|
return disp
|
|
|
|
|
|
def tap_key(disp, keycode):
|
|
_xtst.XTestFakeKeyEvent(disp, keycode, True, 0) # press
|
|
_xtst.XTestFakeKeyEvent(disp, keycode, False, 0) # release
|
|
_x11.XFlush(disp)
|
|
|
|
|
|
def nudge_mouse(disp):
|
|
# Move 1px, then back — resets the idle timer without the cursor visibly moving.
|
|
_xtst.XTestFakeRelativeMotionEvent(disp, 1, 0, 0)
|
|
_xtst.XTestFakeRelativeMotionEvent(disp, -1, 0, 0)
|
|
_x11.XFlush(disp)
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description="Keep Teams presence 'Available' by resetting the idle timer.")
|
|
p.add_argument("-i", "--interval", type=float, default=120,
|
|
help="seconds between key taps (default: 120; keep under Teams' ~300s Away threshold)")
|
|
p.add_argument("-m", "--method", choices=["mouse", "key"], default="mouse",
|
|
help="how to signal activity (default: mouse — a 1px nudge that returns)")
|
|
p.add_argument("-k", "--key", choices=KEYSYMS.keys(), default="shift",
|
|
help="key to tap when --method key is used (default: shift — always mapped)")
|
|
p.add_argument("-q", "--quiet", action="store_true", help="don't print heartbeat messages")
|
|
args = p.parse_args()
|
|
|
|
if os.environ.get("XDG_SESSION_TYPE", "").lower() not in ("x11", ""):
|
|
print(f"Warning: session type is '{os.environ.get('XDG_SESSION_TYPE')}', not x11. "
|
|
"XTest may not reset the idle timer under Wayland.", file=sys.stderr)
|
|
|
|
disp = open_display()
|
|
|
|
if args.method == "key":
|
|
keycode = _x11.XKeysymToKeycode(disp, KEYSYMS[args.key])
|
|
if keycode == 0:
|
|
sys.exit(f"Key '{args.key}' has no keycode on this layout. Try --method mouse.")
|
|
action = lambda: tap_key(disp, keycode)
|
|
label = f"tapping '{args.key}'"
|
|
else:
|
|
action = lambda: nudge_mouse(disp)
|
|
label = "nudging mouse 1px"
|
|
|
|
if not args.quiet:
|
|
print(f"stay_active: {label} every {args.interval:g}s. 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.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|