feat: sensor-triggered window switcher
This commit is contained in:
112
window_switcher.py
Normal file
112
window_switcher.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import win32gui
|
||||
import win32con
|
||||
import win32com.client
|
||||
import time
|
||||
import sys
|
||||
|
||||
# --- Config ---
|
||||
COM_PORT = "COM3" # change to your Arduino's COM port
|
||||
BAUD_RATE = 9600
|
||||
TRIGGER_WORD = "DETECTED"
|
||||
COOLDOWN_SEC = 1.5 # minimum seconds between switches
|
||||
IGNORE_TITLES = {"", "Program Manager", "Windows Input Experience"}
|
||||
|
||||
|
||||
def list_ports():
|
||||
ports = serial.tools.list_ports.comports()
|
||||
if not ports:
|
||||
print("No serial ports found.")
|
||||
else:
|
||||
print("Available ports:")
|
||||
for p in ports:
|
||||
print(f" {p.device} — {p.description}")
|
||||
|
||||
|
||||
def get_windows():
|
||||
"""Return list of (hwnd, title) for all visible, non-minimized top-level windows."""
|
||||
results = []
|
||||
|
||||
def callback(hwnd, _):
|
||||
if not win32gui.IsWindowVisible(hwnd):
|
||||
return
|
||||
title = win32gui.GetWindowText(hwnd)
|
||||
if title and title not in IGNORE_TITLES:
|
||||
results.append((hwnd, title))
|
||||
|
||||
win32gui.EnumWindows(callback, None)
|
||||
return results
|
||||
|
||||
|
||||
def force_focus(hwnd):
|
||||
"""Bring window to foreground, bypassing Windows focus-steal protection."""
|
||||
shell = win32com.client.Dispatch("WScript.Shell")
|
||||
shell.SendKeys('%') # simulate Alt — tricks Windows into allowing focus change
|
||||
if win32gui.IsIconic(hwnd):
|
||||
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
|
||||
win32gui.SetForegroundWindow(hwnd)
|
||||
|
||||
|
||||
class WindowSwitcher:
|
||||
def __init__(self):
|
||||
self.index = 0
|
||||
self.last_switch = 0
|
||||
|
||||
def switch(self):
|
||||
now = time.time()
|
||||
if now - self.last_switch < COOLDOWN_SEC:
|
||||
return
|
||||
|
||||
windows = get_windows()
|
||||
if not windows:
|
||||
print("No windows found.")
|
||||
return
|
||||
|
||||
self.index = (self.index + 1) % len(windows)
|
||||
hwnd, title = windows[self.index]
|
||||
|
||||
try:
|
||||
force_focus(hwnd)
|
||||
print(f"[{time.strftime('%H:%M:%S')}] → {title}")
|
||||
self.last_switch = now
|
||||
except Exception as e:
|
||||
print(f"Could not switch to '{title}': {e}")
|
||||
|
||||
|
||||
def main():
|
||||
if "--list-ports" in sys.argv:
|
||||
list_ports()
|
||||
return
|
||||
|
||||
if "--test" in sys.argv:
|
||||
# Test window switching without Arduino
|
||||
print("Test mode — switching window in 2 seconds...")
|
||||
time.sleep(2)
|
||||
WindowSwitcher().switch()
|
||||
return
|
||||
|
||||
print(f"Connecting to {COM_PORT} at {BAUD_RATE} baud...")
|
||||
print("Press Ctrl+C to stop.\n")
|
||||
|
||||
switcher = WindowSwitcher()
|
||||
|
||||
try:
|
||||
ser = serial.Serial(COM_PORT, BAUD_RATE, timeout=1)
|
||||
print(f"Connected. Waiting for '{TRIGGER_WORD}' signal...\n")
|
||||
|
||||
while True:
|
||||
line = ser.readline().decode("utf-8", errors="ignore").strip()
|
||||
if line == TRIGGER_WORD:
|
||||
switcher.switch()
|
||||
|
||||
except serial.SerialException as e:
|
||||
print(f"Serial error: {e}")
|
||||
print("\nAvailable ports:")
|
||||
list_ports()
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopped.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user