"""Local PC/Steam/Proton control backend (xdotool/scrot wrappers).""" import os import subprocess import time import cv2 from ba_auto import config PROBE_SHOT_PATH = os.path.join(config.SCRATCHPAD_DIR, "probe.png") def run_command(args, **kwargs): kwargs.setdefault("env", config.ENV) kwargs.setdefault("check", True) return subprocess.run(args, **kwargs) def focus_game(): result = run_command( ["xdotool", "search", "--name", config.WINDOW_NAME], check=False, capture_output=True, text=True, ) window_ids = result.stdout.split() if not window_ids: raise RuntimeError("Blue Archive window not found. Is the game running?") run_command(["xdotool", "windowactivate", window_ids[0]]) wait(0.5) def click(x, y): # A combined "mousemove X Y click 1" invocation is unreliable here -- # empirically (see plan.md's click-flakiness writeups) the game's input # handler sometimes misses clicks fired before it's processed the move. # Splitting into separate commands with a short pause between fixes it. run_command(["xdotool", "mousemove", str(x), str(y)]) wait(0.2) run_command(["xdotool", "click", "1"]) wait(0.5) def move_mouse(x, y): run_command(["xdotool", "mousemove", str(x), str(y)]) def scroll(x, y, direction, clicks=1): # xdotool button 4/5 = scroll wheel up/down. A drag (mousedown/move/mouseup) # does not register as a list-scroll gesture in this Proton client; the # wheel does. button = "4" if direction == "up" else "5" run_command(["xdotool", "mousemove", str(x), str(y)]) wait(0.2) for _ in range(clicks): run_command(["xdotool", "click", button]) wait(0.15) wait(0.3) def keypress(key): run_command(["xdotool", "key", key]) wait(0.5) def screenshot(path): run_command(["scrot", "-a", "0,0,1920,1200", "-o", path]) def color_at(x, y): screenshot(PROBE_SHOT_PATH) image = cv2.imread(PROBE_SHOT_PATH) b, g, r = image[y, x] return int(r), int(g), int(b) def wait(seconds): time.sleep(seconds)