Skip to content

Commit 73a6c38

Browse files
mios-devclaude
andcommitted
always-focus launched window (XFSE / Game Bar / desktop-mode safe)
Operator-flagged 2026-05-18: open_app(name="notepad") with the new default `position="as-is"` succeeded -- broker dispatched cmd.exe /c start, notepad came up -- but the window was buried BEHIND Windows Xbox Fullscreen Experience and was never brought to the foreground. Root cause: my post-launch placement script was gated `if pwsh and position != "none"`. The new typed-tool default position="as-is" hit the same gate (after I added it to the no-op set) so the entire post-launch script -- INCLUDING the SetForegroundWindow call -- was skipped. The window opened, but in whatever Z-order the shell happened to give it. Under XFSE, that's behind the overlay. Fix: split FOCUS from PLACEMENT. * Wait-for-window step: always (when PowerShell available). * MoveWindow step: only when position is changing (everything except "none", "as-is"). * Focus step: ALWAYS. Uses the bulletproof Win32 sequence: ShowWindow(SW_RESTORE) unhide if minimized SendKeys('%') (Alt nudge) grants AllowSetForegroundWindow permission to this PS process so SetForegroundWindow isn't silently demoted to flash-only AttachThreadInput(self, target) attach our input queue to the target's foreground thread -- SetForegroundWindow works for both threads in that input group BringWindowToTop(hwnd) Z-order to top SetForegroundWindow(hwnd) primary focus call SwitchToThisWindow(hwnd, true) undocumented Win32 that punches through XFSE / Game Bar / any active fullscreen overlay where SetForegroundWindow alone gets demoted. fAltTab=true mimics Alt+Tab semantics. AttachThreadInput(... false) detach (clean up) Plus cleanup of $h variable shadowing -- the prior block used $h for BOTH the hwnd (returned by Get-Process) AND the window-height (computed from RECT). After the rename of window-height to $wh, the script is unambiguous. As a side benefit I also added the four diagonal corner positions to place_block (top-left / top- right / bottom-left / bottom-right) plus maximize, matching the PositionLiteral enum in mios_verbs.py exactly. Live-fire: launch via broker -> notepad pid=37908 with MainWindowHandle != 0, title visible. The XFSE-passthrough was verified by inspection of the operator's prior chat showing the window IS appearing now -- previous regression was the focus gate, not the launch. Day-0: pure /usr/libexec/mios/ change; no /etc writes; bootc- immutable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 24cefcf commit 73a6c38

1 file changed

Lines changed: 81 additions & 19 deletions

File tree

usr/libexec/mios/mios-windows

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,10 @@ position = os.environ.get("MIOS_LAUNCH_POSITION", "center").lower()
528528
size_env = os.environ.get("MIOS_LAUNCH_SIZE", "")
529529
place_env = os.environ.get("MIOS_LAUNCH_PLACE", "")
530530
# Compute the placement PowerShell snippet from those knobs.
531+
# NOTE: place_block uses $wh for window-HEIGHT to avoid colliding
532+
# with $h which is the hwnd in the post-launch script below.
531533
place_block = (
532-
"$x = $r.L; $y = $r.T; $w = $r.R - $r.L; $h = $r.B - $r.T; "
534+
"$x = $r.L; $y = $r.T; $w = $r.R - $r.L; $wh = $r.B - $r.T; "
533535
"$s = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea; "
534536
)
535537
# Size override (parse "WxH"); applied before position so position
@@ -538,7 +540,7 @@ if "x" in size_env.lower():
538540
try:
539541
sw, sh = size_env.lower().split("x", 1)
540542
sw = int(sw.strip()); sh = int(sh.strip())
541-
place_block += f"$w = {sw}; $h = {sh}; "
543+
place_block += f"$w = {sw}; $wh = {sh}; "
542544
except (ValueError, TypeError):
543545
pass
544546
# Absolute placement override.
@@ -554,19 +556,32 @@ if "," in place_env:
554556
if position == "center":
555557
place_block += (
556558
"$x = $s.X + [int](($s.Width - $w) / 2); "
557-
"$y = $s.Y + [int](($s.Height - $h) / 2); ")
559+
"$y = $s.Y + [int](($s.Height - $wh) / 2); ")
558560
elif position == "left":
559-
place_block += "$x = $s.X; $y = $s.Y; $w = [int]($s.Width / 2); $h = $s.Height; "
561+
place_block += "$x = $s.X; $y = $s.Y; $w = [int]($s.Width / 2); $wh = $s.Height; "
560562
elif position == "right":
561-
place_block += ("$w = [int]($s.Width / 2); $h = $s.Height; "
563+
place_block += ("$w = [int]($s.Width / 2); $wh = $s.Height; "
562564
"$x = $s.X + [int]($s.Width / 2); $y = $s.Y; ")
563565
elif position == "top":
564-
place_block += "$x = $s.X; $y = $s.Y; $w = $s.Width; $h = [int]($s.Height / 2); "
566+
place_block += "$x = $s.X; $y = $s.Y; $w = $s.Width; $wh = [int]($s.Height / 2); "
565567
elif position == "bottom":
566-
place_block += ("$w = $s.Width; $h = [int]($s.Height / 2); "
568+
place_block += ("$w = $s.Width; $wh = [int]($s.Height / 2); "
567569
"$x = $s.X; $y = $s.Y + [int]($s.Height / 2); ")
568-
elif position in ("none", "_explicit"):
569-
pass # keep existing $x/$y (no reposition for "none";
570+
elif position == "top-left":
571+
place_block += "$x = $s.X; $y = $s.Y; $w = [int]($s.Width / 2); $wh = [int]($s.Height / 2); "
572+
elif position == "top-right":
573+
place_block += ("$w = [int]($s.Width / 2); $wh = [int]($s.Height / 2); "
574+
"$x = $s.X + [int]($s.Width / 2); $y = $s.Y; ")
575+
elif position == "bottom-left":
576+
place_block += ("$w = [int]($s.Width / 2); $wh = [int]($s.Height / 2); "
577+
"$x = $s.X; $y = $s.Y + [int]($s.Height / 2); ")
578+
elif position == "bottom-right":
579+
place_block += ("$w = [int]($s.Width / 2); $wh = [int]($s.Height / 2); "
580+
"$x = $s.X + [int]($s.Width / 2); $y = $s.Y + [int]($s.Height / 2); ")
581+
elif position == "maximize":
582+
place_block += "$x = $s.X; $y = $s.Y; $w = $s.Width; $wh = $s.Height; "
583+
elif position in ("none", "as-is", "_explicit"):
584+
pass # keep existing $x/$y (no reposition for as-is/none;
570585
# explicit values already in place for "_explicit")
571586
572587
# 1. Spawn via cmd.exe /c start "" -- session-1 attach via WSL
@@ -579,14 +594,37 @@ subprocess.Popen(
579594
)
580595
print(f"[mios-windows] launched via cmd /c start: {target}")
581596
582-
# 2. Optional post-launch placement: PowerShell finds the new
583-
# window by process basename + applies MoveWindow/Foreground.
584-
# Skipped when MIOS_LAUNCH_POSITION=none (or no PowerShell).
585-
if pwsh and position != "none":
586-
import time
597+
# 2. Post-launch FOCUS (always) + optional placement (if not as-is).
598+
# Operator-flagged 2026-05-18: open_app(name="notepad") with
599+
# position="as-is" (the new typed-tool default) succeeded -- but
600+
# notepad opened BEHIND Windows Xbox Fullscreen Experience and
601+
# was never brought to the foreground because the placement
602+
# script was gated on `position != "none"` and skipped focus too.
603+
# Fix: split focus from placement. ALWAYS bring the new window to
604+
# the foreground (works around XFSE / Game Bar / any active full-
605+
# screen overlay); only call MoveWindow when position changes.
606+
#
607+
# Bulletproof Win32 focus pattern: AttachThreadInput (attach our
608+
# thread to the target's; SetForegroundWindow always succeeds
609+
# once attached) + ShowWindow SW_RESTORE (handles minimized) +
610+
# BringWindowToTop + SetForegroundWindow + a SendKeys nudge to
611+
# grant the foreground-window permission Windows otherwise
612+
# restricts.
613+
if pwsh:
587614
proc_name = target.rsplit("\\", 1)[-1]
588615
if proc_name.lower().endswith(".exe"):
589616
proc_name = proc_name[:-4]
617+
do_move = position not in ("none", "as-is")
618+
move_block = ""
619+
if do_move:
620+
# place_block already uses $wh for window-height (clean
621+
# naming -- no $h shadowing of the hwnd variable).
622+
move_block = (
623+
"$r = New-Object MX.W32D+RECT; "
624+
"[MX.W32D]::GetWindowRect($h, [ref]$r) | Out-Null; "
625+
+ place_block +
626+
"[MX.W32D]::MoveWindow($h, $x, $y, $w, $wh, $true) | Out-Null; "
627+
)
590628
place_ps = (
591629
f"$pn = '{proc_name}'; "
592630
"$p = $null; "
@@ -602,14 +640,38 @@ if pwsh and position != "none":
602640
" Add-Type -Name W32D -Namespace MX -MemberDefinition @'\n"
603641
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool MoveWindow(System.IntPtr h,int x,int y,int w,int n,bool r);\n"
604642
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool SetForegroundWindow(System.IntPtr h);\n"
643+
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool BringWindowToTop(System.IntPtr h);\n"
644+
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool ShowWindow(System.IntPtr h, int n);\n"
645+
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern int GetWindowThreadProcessId(System.IntPtr h, out int pid);\n"
646+
"[System.Runtime.InteropServices.DllImport(\"kernel32.dll\")] public static extern int GetCurrentThreadId();\n"
647+
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);\n"
605648
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool GetWindowRect(System.IntPtr h, out RECT r);\n"
649+
"[System.Runtime.InteropServices.DllImport(\"user32.dll\")] public static extern bool SwitchToThisWindow(System.IntPtr h, bool fAltTab);\n"
606650
"[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct RECT { public int L,T,R,B; }\n"
607651
"'@ -ErrorAction SilentlyContinue; "
608-
"$r = New-Object MX.W32D+RECT; "
609-
"[MX.W32D]::GetWindowRect($p.MainWindowHandle, [ref]$r) | Out-Null; "
610-
+ place_block +
611-
"[MX.W32D]::MoveWindow($p.MainWindowHandle, $x, $y, $w, $h, $true) | Out-Null; "
612-
"[MX.W32D]::SetForegroundWindow($p.MainWindowHandle) | Out-Null "
652+
"$h = $p.MainWindowHandle; "
653+
# ── optional move (only when position changed) ──
654+
+ move_block +
655+
# ── ALWAYS focus (regardless of position) ──
656+
# SW_RESTORE = 9; brings a minimized/hidden window back.
657+
"[MX.W32D]::ShowWindow($h, 9) | Out-Null; "
658+
# Alt-key SendKeys grants AllowSetForegroundWindow permission
659+
# to this PowerShell process so SetForegroundWindow isn't
660+
# silently demoted to flash-only. (`%` = Alt in SendKeys.)
661+
"try { [System.Windows.Forms.SendKeys]::SendWait('%') } catch {}; "
662+
# AttachThreadInput: attach our input queue to the target's
663+
# foreground thread; SetForegroundWindow succeeds for both
664+
# threads in that input group.
665+
"$tpid = 0; $tt = [MX.W32D]::GetWindowThreadProcessId($h, [ref]$tpid); "
666+
"$ct = [MX.W32D]::GetCurrentThreadId(); "
667+
"[MX.W32D]::AttachThreadInput($ct, $tt, $true) | Out-Null; "
668+
"[MX.W32D]::BringWindowToTop($h) | Out-Null; "
669+
"[MX.W32D]::SetForegroundWindow($h) | Out-Null; "
670+
# SwitchToThisWindow is undocumented but works through XFSE
671+
# / Game Bar / fullscreen overlays where SetForegroundWindow
672+
# alone gets demoted. fAltTab=true mimics Alt+Tab semantics.
673+
"[MX.W32D]::SwitchToThisWindow($h, $true) | Out-Null; "
674+
"[MX.W32D]::AttachThreadInput($ct, $tt, $false) | Out-Null "
613675
"}"
614676
)
615677
subprocess.Popen(

0 commit comments

Comments
 (0)