Maximize GUI app window and fix input forwarding

- After window discovery, activate, move to (0,0), and resize to
  fill the 1280x1024 Xvfb display so the app is fullscreen
- Add windowactivate --sync before click events so the window
  receives focus in X11 before mouse input
- Add windowfocus --sync before key events so keyboard input
  goes to the correct window
- Make canvas fill the entire app panel area (width/height 100%)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-15 01:09:58 -05:00
parent 1467d07bc4
commit 12c10d69f0
2 changed files with 31 additions and 5 deletions

View file

@ -187,6 +187,8 @@ class ManagedGuiApp:
pass pass
return return
# Maximize the window to fill the Xvfb display and activate it
await self._maximize_window()
log.info("Capture loop started for app %s window %d", self.app_id, self.window_id) log.info("Capture loop started for app %s window %d", self.app_id, self.window_id)
while self.alive: while self.alive:
@ -357,6 +359,25 @@ class ManagedGuiApp:
grandchildren.extend(ManagedGuiApp._get_descendant_pids(cpid)) grandchildren.extend(ManagedGuiApp._get_descendant_pids(cpid))
return children + grandchildren return children + grandchildren
async def _maximize_window(self):
"""Activate the window, resize it to fill the Xvfb display, and move to origin."""
if not self.window_id or not XDOTOOL_BIN:
return
env = self._display_env()
wid = str(self.window_id)
try:
# Activate (focus) the window
await self._xdotool("windowactivate", "--sync", wid, env=env)
# Move to origin
await self._xdotool("windowmove", "--sync", wid, "0", "0", env=env)
# Resize to fill display (Xvfb is 1280x1024)
await self._xdotool("windowsize", "--sync", wid, "1280", "1024", env=env)
# Some apps need a moment to redraw after resize
await asyncio.sleep(0.3)
log.debug("Maximized window %s to 1280x1024 for %s", wid, self.app_id)
except Exception as e:
log.debug("Failed to maximize window %s: %s", wid, e)
async def _xdotool_search(self, *args, env=None) -> Optional[int]: async def _xdotool_search(self, *args, env=None) -> Optional[int]:
"""Run xdotool search and return first window ID, or None.""" """Run xdotool search and return first window ID, or None."""
try: try:
@ -399,6 +420,7 @@ class ManagedGuiApp:
if action == "click": if action == "click":
btn = str(msg.get("button", 1)) btn = str(msg.get("button", 1))
await self._xdotool( await self._xdotool(
"windowactivate", "--sync", wid,
"mousemove", "--window", wid, x, y, "mousemove", "--window", wid, x, y,
"click", "--window", wid, btn, "click", "--window", wid, btn,
env=env, env=env,
@ -406,6 +428,7 @@ class ManagedGuiApp:
elif action == "dblclick": elif action == "dblclick":
btn = str(msg.get("button", 1)) btn = str(msg.get("button", 1))
await self._xdotool( await self._xdotool(
"windowactivate", "--sync", wid,
"mousemove", "--window", wid, x, y, "mousemove", "--window", wid, x, y,
"click", "--window", wid, "--repeat", "2", btn, "click", "--window", wid, "--repeat", "2", btn,
env=env, env=env,
@ -449,7 +472,11 @@ class ManagedGuiApp:
wid = str(self.window_id) wid = str(self.window_id)
if action == "press": if action == "press":
await self._xdotool("key", "--window", wid, xkey, env=env) await self._xdotool(
"windowfocus", "--sync", wid,
"key", "--window", wid, xkey,
env=env,
)
# release events handled implicitly by xdotool key # release events handled implicitly by xdotool key
async def _xdotool(self, *args, env=None): async def _xdotool(self, *args, env=None):

View file

@ -9,18 +9,17 @@
.gui-canvas-wrap { .gui-canvas-wrap {
flex: 1; flex: 1;
display: flex; display: flex;
align-items: center;
justify-content: center;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
.gui-canvas { .gui-canvas {
max-width: 100%; width: 100%;
max-height: 100%; height: 100%;
cursor: default; cursor: default;
outline: none; outline: none;
image-rendering: auto; image-rendering: auto;
object-fit: contain;
} }
/* Status overlays */ /* Status overlays */