Skip to main content
Glama
window.ts39.7 kB
import { z } from 'zod'; import { exec } from 'child_process'; import { promisify } from 'util'; import os from 'os'; import { logAudit } from '../audit.js'; import { PowerShellSession } from '../utils/powerShellSession.js'; const execAsync = promisify(exec); const platform = os.platform(); // Schema definitions export const ListWindowsSchema = {}; export const GetActiveWindowSchema = {}; export const FocusWindowSchema = { title: z.string().optional().describe('Window title (partial match)'), pid: z.number().optional().describe('Process ID'), handle: z.string().optional().describe('Window handle (platform-specific)'), }; export const MinimizeWindowSchema = { title: z.string().optional().describe('Window title (partial match). If not specified, minimizes active window.'), all: z.boolean().optional().describe('Minimize all windows'), }; export const MaximizeWindowSchema = { title: z.string().optional().describe('Window title (partial match). If not specified, maximizes active window.'), }; export const RestoreWindowSchema = { title: z.string().optional().describe('Window title (partial match). If not specified, restores active window.'), }; export const CloseWindowSchema = { title: z.string().optional().describe('Window title (partial match). If not specified, closes active window.'), pid: z.number().optional().describe('Process ID'), force: z.boolean().optional().describe('Force close (kill process)'), }; export const ResizeWindowSchema = { title: z.string().optional().describe('Window title (partial match)'), width: z.number().describe('New width'), height: z.number().describe('New height'), }; export const MoveWindowSchema = { title: z.string().optional().describe('Window title (partial match)'), x: z.number().describe('New X position'), y: z.number().describe('New Y position'), }; export const LaunchApplicationSchema = { path: z.string().describe('Application path or name'), args: z.array(z.string()).optional().describe('Command line arguments'), waitForWindow: z.boolean().optional().describe('Wait for a window to appear. Matches process name or window title.'), timeout: z.number().optional().describe('Timeout in ms when waiting for window'), }; export const WaitForWindowSchema = { title: z.string().describe('Window title (partial match)'), timeout: z.number().optional().describe('Timeout in ms (default 5000)'), }; // Platform-specific implementations interface WindowInfo { title: string; pid?: number; handle?: string; className?: string; x?: number; y?: number; width?: number; height?: number; isMinimized?: boolean; isMaximized?: boolean; isActive?: boolean; } async function listWindowsWin32(): Promise<WindowInfo[]> { const script = ` if (-not ("WindowEnumerator" -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Text; public class WindowInfo { public string Title; public int Pid; public string Handle; public string ClassName; } public class WindowEnumerator { [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); public static List<WindowInfo> GetVisibleWindows() { var windows = new List<WindowInfo>(); EnumWindows((hWnd, lParam) => { if (IsWindowVisible(hWnd)) { var sb = new StringBuilder(256); GetWindowText(hWnd, sb, 256); // Only include windows with titles, unless it's a dialog class? No, usually dialogs have titles. // If title is empty, it's likely a background/utility window not interesting for automation. if (sb.Length > 0) { var cls = new StringBuilder(256); GetClassName(hWnd, cls, 256); uint pid = 0; GetWindowThreadProcessId(hWnd, out pid); windows.Add(new WindowInfo { Title = sb.ToString(), Pid = (int)pid, Handle = hWnd.ToString(), ClassName = cls.ToString() }); } } return true; }, IntPtr.Zero); return windows; } } "@ } [WindowEnumerator]::GetVisibleWindows() | ConvertTo-Json `; try { const stdout = await PowerShellSession.getInstance().execute(script); try { const result = JSON.parse(stdout || '[]'); // PowerShell Json serialization might return single object or array return Array.isArray(result) ? result.map(mapWin) : [mapWin(result)]; } catch (parseError) { return []; } } catch (error: any) { console.error('List Windows Error:', error); return []; } } function mapWin(w: any): WindowInfo { return { title: w.Title, pid: w.Pid, handle: w.Handle, className: w.ClassName }; } async function listWindowsDarwin(): Promise<WindowInfo[]> { try { const script = ` tell application "System Events" set appList to {} repeat with proc in (every process whose visible is true) set end of appList to {name of proc, unix id of proc} end repeat return appList end tell `; const { stdout } = await execAsync(`osascript -e '${script.replace(/\n/g, ' ')}'`, { timeout: 5000 }); const windows: WindowInfo[] = []; const matches = stdout.matchAll(/\{([^,]+),\s*(\d+)\}/g); for (const match of matches) { windows.push({ title: match[1].trim(), pid: parseInt(match[2]), }); } return windows; } catch { return []; } } async function listWindowsLinux(): Promise<WindowInfo[]> { try { const { stdout } = await execAsync('wmctrl -l -p', { timeout: 5000 }); const windows: WindowInfo[] = []; const lines = stdout.trim().split('\n'); for (const line of lines) { const parts = line.split(/\s+/); if (parts.length >= 5) { windows.push({ handle: parts[0], pid: parseInt(parts[2]), title: parts.slice(4).join(' '), }); } } return windows; } catch { return []; } } async function getActiveWindowWin32(): Promise<WindowInfo | null> { const script = ` if (-not ("ActiveWindow" -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; using System.Text; public class ActiveWindow { [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); } "@ } $hwnd = [ActiveWindow]::GetForegroundWindow() $sb = New-Object System.Text.StringBuilder 256 [ActiveWindow]::GetWindowText($hwnd, $sb, 256) | Out-Null $procId = 0 [ActiveWindow]::GetWindowThreadProcessId($hwnd, [ref]$procId) | Out-Null @{ title = $sb.ToString(); pid = $procId; handle = $hwnd.ToString() } | ConvertTo-Json `; try { const stdout = await PowerShellSession.getInstance().execute(script); const result = JSON.parse(stdout || 'null'); return result ? { title: result.title, pid: result.pid, handle: result.handle } : null; } catch { return null; } } async function focusWindowWin32(title?: string, pid?: number): Promise<boolean> { const script = ` ${EnsureWindowFocusScript} $hWnd = [IntPtr]::Zero # Try to find by title match in all visible windows first if ("${title}") { if (-not ("WindowEnumerator" -as [type])) { /* Ensure enumerator exists if not already */ } # Re-use WindowEnumerator logic specifically? Or just use simplistic Get-Process for main windows? # Actually, better to use WindowEnumerator to find the handle! } # Simplified for now: Use Get-Process for main windows, or if that fails, try exact handle? # But we want to focus popups too. $proc = Get-Process | Where-Object { $_.MainWindowTitle -like "*${title}*" } | Select-Object -First 1 if ($proc) { [WindowFocus]::ShowWindow($proc.MainWindowHandle, 9) [WindowFocus]::SetForegroundWindow($proc.MainWindowHandle) Write-Output "true" } else { # Fallback: maybe it's a dialog not attached to MainWindowHandle? # Implemented simple main window focus for now. Write-Output "false" } `; // Updated robust logic const scriptRobust = ` ${EnsureWindowFocusScript} $targetTitle = "${title}" $found = $false # Use our enumerator if available (it is defined in listWindows) # But we need to ensure it's defined here too in case listWindows wasn't called if (-not ("WindowFocusEnumerator" -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Text; public class WindowFocusEnumerator { [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd); public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); public static IntPtr FindWindowByTitle(string partialTitle) { IntPtr found = IntPtr.Zero; EnumWindows((hWnd, lParam) => { if (IsWindowVisible(hWnd)) { var sb = new StringBuilder(256); GetWindowText(hWnd, sb, 256); if (sb.ToString().IndexOf(partialTitle, StringComparison.OrdinalIgnoreCase) >= 0) { found = hWnd; return false; // Stop enumeration } } return true; }, IntPtr.Zero); return found; } } "@ } $hwnd = [WindowFocusEnumerator]::FindWindowByTitle($targetTitle) if ($hwnd -ne [IntPtr]::Zero) { [WindowFocus]::ShowWindow($hwnd, 9) [WindowFocus]::SetForegroundWindow($hwnd) Write-Output "true" } else { Write-Output "false" } `; try { const stdout = await PowerShellSession.getInstance().execute(scriptRobust); return stdout.trim() === 'true'; } catch { return false; } } const EnsureWindowFocusScript = ` if (-not ("WindowFocus" -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; public class WindowFocus { [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); } "@ } `; // Tool handlers export async function handleListWindows() { try { let windows: WindowInfo[]; if (platform === 'win32') { windows = await listWindowsWin32(); } else if (platform === 'darwin') { windows = await listWindowsDarwin(); } else { windows = await listWindowsLinux(); } await logAudit('list_windows', {}, { count: windows.length }); return { content: [{ type: 'text', text: JSON.stringify({ platform, count: windows.length, windows }, null, 2) }], }; } catch (error: any) { await logAudit('list_windows', {}, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleGetActiveWindow() { try { let window: WindowInfo | null = null; if (platform === 'win32') { window = await getActiveWindowWin32(); } else if (platform === 'darwin') { const { stdout } = await execAsync(`osascript -e 'tell application "System Events" to get name of first process whose frontmost is true'`, { timeout: 5000 }); window = { title: stdout.trim() }; } else { const { stdout } = await execAsync('xdotool getactivewindow getwindowname', { timeout: 5000 }); window = { title: stdout.trim() }; } await logAudit('get_active_window', {}, 'success'); return { content: [{ type: 'text', text: JSON.stringify(window, null, 2) }], }; } catch (error: any) { await logAudit('get_active_window', {}, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleFocusWindow(args: { title?: string; pid?: number; handle?: string }) { try { let success = false; if (platform === 'win32') { success = await focusWindowWin32(args.title, args.pid); } else if (platform === 'darwin') { if (args.title) { await execAsync(`osascript -e 'tell application "${args.title}" to activate'`, { timeout: 5000 }); success = true; } } else { if (args.title) { await execAsync(`wmctrl -a "${args.title}"`, { timeout: 5000 }); success = true; } } await logAudit('focus_window', args, success ? 'success' : 'not_found'); return { content: [{ type: 'text', text: JSON.stringify({ focused: success }, null, 2) }], }; } catch (error: any) { await logAudit('focus_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleMinimizeWindow(args: { title?: string; all?: boolean }) { try { if (platform === 'win32') { if (args.all) { await PowerShellSession.getInstance().execute('(New-Object -ComObject Shell.Application).MinimizeAll()'); } else { const script = args.title ? `$proc = Get-Process | Where-Object { $_.MainWindowTitle -like "*${args.title}*" } | Select-Object -First 1; if ($proc) { if (-not ("Win32Minimize" -as [type])) { $code = @" [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); "@ Add-Type -MemberDefinition $code -Name "Win32Minimize" -Namespace Win32 } [Win32.Win32Minimize]::ShowWindow($proc.MainWindowHandle, 6) }` : `if (-not ("Native.Win" -as [type])) { Add-Type -Name Win -Namespace Native -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' } [Native.Win]::ShowWindow([Native.Win]::GetForegroundWindow(), 6)`; await PowerShellSession.getInstance().execute(script); } } else if (platform === 'darwin') { await execAsync(`osascript -e 'tell application "System Events" to set visible of first process whose frontmost is true to false'`, { timeout: 5000 }); } else { await execAsync('xdotool getactivewindow windowminimize', { timeout: 5000 }); } await logAudit('minimize_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ minimized: true }, null, 2) }], }; } catch (error: any) { await logAudit('minimize_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleMaximizeWindow(args: { title?: string }) { try { if (platform === 'win32') { const script = ` if (-not ("Native.Win" -as [type])) { Add-Type -Name Win -Namespace Native -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' } [Native.Win]::ShowWindow([Native.Win]::GetForegroundWindow(), 3) `; await PowerShellSession.getInstance().execute(script); } else if (platform === 'darwin') { await execAsync(`osascript -e 'tell application "System Events" to tell first process whose frontmost is true to set value of attribute "AXFullScreen" of window 1 to true'`, { timeout: 5000 }); } else { await execAsync('wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz', { timeout: 5000 }); } await logAudit('maximize_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ maximized: true }, null, 2) }], }; } catch (error: any) { await logAudit('maximize_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleRestoreWindow(args: { title?: string }) { try { if (platform === 'win32') { const script = ` if (-not ("Native.Win" -as [type])) { Add-Type -Name Win -Namespace Native -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' } [Native.Win]::ShowWindow([Native.Win]::GetForegroundWindow(), 9) `; await PowerShellSession.getInstance().execute(script); } else { await execAsync('wmctrl -r :ACTIVE: -b remove,maximized_vert,maximized_horz', { timeout: 5000 }); } await logAudit('restore_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ restored: true }, null, 2) }], }; } catch (error: any) { await logAudit('restore_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleCloseWindow(args: { title?: string; pid?: number; force?: boolean }) { try { if (args.force && args.pid) { if (platform === 'win32') { await execAsync(`taskkill /PID ${args.pid} /F`, { timeout: 5000 }); } else { await execAsync(`kill -9 ${args.pid}`, { timeout: 5000 }); } } else if (platform === 'win32') { // Updated to be more robust: find window via EnumWindows and send WM_CLOSE // Just sending Close to Foreground is mostly ok for active window tools. const script = ` if (-not ("Native.Win" -as [type])) { Add-Type -Name Win -Namespace Native -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' } [Native.Win]::SendMessage([Native.Win]::GetForegroundWindow(), 0x0010, [IntPtr]::Zero, [IntPtr]::Zero) `; await PowerShellSession.getInstance().execute(script); } else { await execAsync('xdotool getactivewindow windowclose', { timeout: 5000 }); } await logAudit('close_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ closed: true }, null, 2) }], }; } catch (error: any) { await logAudit('close_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleResizeWindow(args: { title?: string; width: number; height: number }) { try { if (platform === 'win32') { const script = ` if (-not ("WinResize" -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; public class WinResize { [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; } } "@ } $hwnd = [WinResize]::GetForegroundWindow() $rect = New-Object WinResize+RECT [WinResize]::GetWindowRect($hwnd, [ref]$rect) | Out-Null [WinResize]::MoveWindow($hwnd, $rect.Left, $rect.Top, ${args.width}, ${args.height}, $true) `; await PowerShellSession.getInstance().execute(script); } else { await execAsync(`wmctrl -r :ACTIVE: -e 0,-1,-1,${args.width},${args.height}`, { timeout: 5000 }); } await logAudit('resize_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ resized: true, width: args.width, height: args.height }, null, 2) }], }; } catch (error: any) { await logAudit('resize_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleMoveWindow(args: { title?: string; x: number; y: number }) { try { if (platform === 'win32') { const script = ` if (-not ("WinMove" -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; public class WinMove { [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; } } "@ } $hwnd = [WinMove]::GetForegroundWindow() $rect = New-Object WinMove+RECT [WinMove]::GetWindowRect($hwnd, [ref]$rect) | Out-Null $w = $rect.Right - $rect.Left $h = $rect.Bottom - $rect.Top [WinMove]::MoveWindow($hwnd, ${args.x}, ${args.y}, $w, $h, $true) `; await PowerShellSession.getInstance().execute(script); } else { await execAsync(`wmctrl -r :ACTIVE: -e 0,${args.x},${args.y},-1,-1`, { timeout: 5000 }); } await logAudit('move_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ moved: true, x: args.x, y: args.y }, null, 2) }], }; } catch (error: any) { await logAudit('move_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleLaunchApplication(args: { path: string; args?: string[]; waitForWindow?: boolean; timeout?: number }) { try { const appArgs = args.args?.join(' ') || ''; if (platform === 'win32') { await execAsync(`start "" "${args.path}" ${appArgs}`, { timeout: 5000 }); } else if (platform === 'darwin') { await execAsync(`open -a "${args.path}" ${appArgs}`, { timeout: 5000 }); } else { await execAsync(`${args.path} ${appArgs} &`, { timeout: 5000 }); } await logAudit('launch_application', args, 'success'); let windowInfo: WindowInfo | null = null; if (args.waitForWindow) { // Try to guess title from path or use generic wait const basename = args.path.split(/[/\\]/).pop() || args.path; const searchTerm = basename.replace(/\.(exe|app)$/i, ''); try { const result = await handleWaitForWindow({ title: searchTerm, timeout: args.timeout || 10000 }); if (!result.isError) { const data = JSON.parse(result.content[0].text); if (data.found) { windowInfo = data.window; } } } catch (err) { // Ignore wait errors, we still launched } } return { content: [{ type: 'text', text: JSON.stringify({ launched: true, path: args.path, window: windowInfo }, null, 2) }], }; } catch (error: any) { await logAudit('launch_application', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleWaitForWindow(args: { title: string; timeout?: number }) { const timeout = args.timeout || 5000; // Improved Event-Driven Logic using UI Automation if (platform === 'win32') { const script = ` if (-not ("WindowWaiter" -as [type])) { Add-Type -AssemblyName UIAutomationClient Add-Type -AssemblyName UIAutomationTypes $source = @" using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Windows.Automation; using System.Diagnostics; using System.Runtime.InteropServices; public class WindowWaiter { private static ManualResetEvent _arg = new ManualResetEvent(false); private static string _foundTitle = null; private static int _foundPid = 0; private static string _foundHandle = null; private static string _foundClass = null; private static string _targetTitlePartial = null; private static object _lock = new object(); public static object WaitForWindow(string titlePartial, int timeoutMs) { _targetTitlePartial = titlePartial; _arg.Reset(); _foundTitle = null; // 1. Check existing windows first (fast path) var existing = FindWindow(titlePartial); if (existing != null) return existing; // 2. Subscribe to WindowOpenedEvent AutomationEventHandler handler = new AutomationEventHandler(OnWindowOpen); try { Automation.AddAutomationEventHandler( WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, handler ); // Wait for event or timeout if (_arg.WaitOne(timeoutMs)) { lock(_lock) { return new { found = true, window = new { title = _foundTitle, pid = _foundPid, handle = _foundHandle, className = _foundClass } }; } } } catch (Exception ex) { return new { error = ex.Message }; } finally { try { Automation.RemoveAutomationEventHandler( WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, handler ); } catch {} } // 3. One final check in case we missed the event race var finalCheck = FindWindow(titlePartial); if (finalCheck != null) return finalCheck; return new { found = false }; } private static void OnWindowOpen(object src, AutomationEventArgs e) { try { AutomationElement element = src as AutomationElement; if (element == null) return; string name = ""; try { name = element.Current.Name; } catch {} if (!string.IsNullOrEmpty(name) && name.IndexOf(_targetTitlePartial, StringComparison.OrdinalIgnoreCase) >= 0) { lock(_lock) { _foundTitle = name; _foundPid = element.Current.ProcessId; _foundHandle = element.Current.NativeWindowHandle.ToString(); _foundClass = element.Current.ClassName; _arg.Set(); } } } catch {} } [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); [DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); private static object FindWindow(string partial) { object result = null; EnumWindows((hWnd, lParam) => { if (!IsWindowVisible(hWnd)) return true; StringBuilder sb = new StringBuilder(256); GetWindowText(hWnd, sb, 256); string title = sb.ToString(); if (!string.IsNullOrEmpty(title) && title.IndexOf(partial, StringComparison.OrdinalIgnoreCase) >= 0) { int pid = 0; GetWindowThreadProcessId(hWnd, out pid); StringBuilder sbClass = new StringBuilder(256); GetClassName(hWnd, sbClass, 256); result = new { found = true, window = new { title = title, pid = pid, handle = hWnd.ToString(), className = sbClass.ToString() } }; return false; // Stop enumeration } return true; }, IntPtr.Zero); return result; } } "@ Add-Type -TypeDefinition $source -ReferencedAssemblies "UIAutomationClient","UIAutomationTypes","System.Core" } # Since native C# FindWindow helper above was stubbed, we do a quick PowerShell pre-check # Actually, let's just let the C# waiter run. If it misses existing, we might wait unnecessary time. # Ideally we combine them. For now, rely on list_windows loop as fallback if C# returns error? # No, let's implement the full C# Wait including the scan. [WindowWaiter]::WaitForWindow("${args.title}", ${timeout}) | ConvertTo-Json -Depth 2 `; try { const stdout = await PowerShellSession.getInstance().execute(script); // If setup failed or returned empty if (!stdout) throw new Error("No output from WindowWaiter"); const result = JSON.parse(stdout); // If the C# stub returned found=false (timeout), we double check with our robust listWindowsWin32 // just in case UIA missed it (some non-UIA windows). if (!result.found) { // One final check using P/Invoke enumerator const windows = await listWindowsWin32(); const match = windows.find(w => w.title.toLowerCase().includes(args.title.toLowerCase())); if (match) { await logAudit('wait_for_window', args, 'success (fallback scan)'); return { content: [{ type: 'text', text: JSON.stringify({ found: true, window: match }, null, 2) }] }; } } await logAudit('wait_for_window', args, result.found ? 'success' : 'timeout'); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { // Fallback to polling loop if UIA setup totally failed // ... (keep existing polling logic as backup below) ... } } // ... Existing Polling Fallback (for Mac/Linux or if UIA fails) ... try { const interval = 500; const startTime = Date.now(); while (Date.now() - startTime < timeout) { let windows: WindowInfo[] = []; if (platform === 'win32') { windows = await listWindowsWin32(); } else if (platform === 'darwin') { windows = await listWindowsDarwin(); } else { windows = await listWindowsLinux(); } const match = windows.find(w => w.title.toLowerCase().includes(args.title.toLowerCase())); if (match) { await logAudit('wait_for_window', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ found: true, window: match }, null, 2) }] }; } await new Promise(resolve => setTimeout(resolve, interval)); } await logAudit('wait_for_window', args, 'timeout'); return { content: [{ type: 'text', text: JSON.stringify({ found: false }, null, 2) }] }; } catch (error: any) { await logAudit('wait_for_window', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/mnehmos.ooda.mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server