using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace WindowsBridge; /// /// Process and window management using Windows APIs. /// Equivalent to macOS AppManagement.swift. /// class AppManagement { // P/Invoke declarations [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern int GetWindowTextLength(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] private static extern bool AllowSetForegroundWindow(int dwProcessId); [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left, Top, Right, Bottom; } private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); private const int SW_RESTORE = 9; private const int SW_SHOW = 5; /// /// List all running GUI applications. /// On Windows, "bundleId" is mapped to the process name (e.g., "chrome", "notepad"). /// public List> ListRunningApps() { var apps = new List>(); var seen = new HashSet(); var foregroundHwnd = GetForegroundWindow(); GetWindowThreadProcessId(foregroundHwnd, out uint foregroundPid); var processes = Process.GetProcesses() .Where(p => !string.IsNullOrEmpty(p.MainWindowTitle) && p.MainWindowHandle != IntPtr.Zero) .OrderBy(p => p.ProcessName); foreach (var proc in processes) { try { if (seen.Contains(proc.Id)) continue; seen.Add(proc.Id); apps.Add(new Dictionary { ["name"] = proc.ProcessName, ["bundleId"] = proc.ProcessName.ToLowerInvariant(), // Windows equivalent ["pid"] = proc.Id, ["isActive"] = proc.Id == (int)foregroundPid, }); } catch { // Process may have exited } } return apps; } /// /// Launch an application by process name or path. /// public Dictionary LaunchApp(string bundleId) { Process process; try { // Try launching as a process name first (e.g., "notepad", "calc") process = Process.Start(new ProcessStartInfo { FileName = bundleId, UseShellExecute = true, }) ?? throw new BridgeException($"Failed to launch: {bundleId}"); } catch { throw new BridgeException($"Could not launch application: {bundleId}"); } // Wait briefly for the process to initialize try { process.WaitForInputIdle(3000); } catch { } return new Dictionary { ["appName"] = process.ProcessName, ["bundleId"] = process.ProcessName.ToLowerInvariant(), ["pid"] = process.Id, }; } /// /// Focus/activate a window by process name. /// public Dictionary FocusApp(string bundleId) { var name = bundleId.ToLowerInvariant().Replace(".exe", ""); var processes = Process.GetProcessesByName(name); if (processes.Length == 0) { // Try exact match with original casing processes = Process.GetProcesses() .Where(p => p.ProcessName.Equals(bundleId, StringComparison.OrdinalIgnoreCase) || p.ProcessName.Equals(name, StringComparison.OrdinalIgnoreCase)) .ToArray(); } if (processes.Length == 0) throw new BridgeException($"No running process found: {bundleId}"); var proc = processes.First(p => p.MainWindowHandle != IntPtr.Zero); var hwnd = proc.MainWindowHandle; if (hwnd == IntPtr.Zero) throw new BridgeException($"Process {bundleId} has no visible window"); ShowWindow(hwnd, SW_RESTORE); AllowSetForegroundWindow(proc.Id); SetForegroundWindow(hwnd); return new Dictionary { ["ok"] = true }; } /// /// List all visible windows with their bounds. /// public List> ListWindows() { var windows = new List>(); EnumWindows((hWnd, _) => { if (!IsWindowVisible(hWnd)) return true; var titleLength = GetWindowTextLength(hWnd); if (titleLength == 0) return true; var titleBuilder = new StringBuilder(titleLength + 1); GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity); var title = titleBuilder.ToString(); GetWindowThreadProcessId(hWnd, out uint pid); GetWindowRect(hWnd, out RECT rect); string appName; try { appName = Process.GetProcessById((int)pid).ProcessName; } catch { appName = "unknown"; } windows.Add(new Dictionary { ["windowId"] = hWnd.ToInt64(), ["appName"] = appName, ["title"] = title, ["pid"] = (int)pid, ["bounds"] = new Dictionary { ["x"] = rect.Left, ["y"] = rect.Top, ["width"] = rect.Right - rect.Left, ["height"] = rect.Bottom - rect.Top, }, }); return true; }, IntPtr.Zero); return windows; } /// /// Get the frontmost (foreground) application. /// public Dictionary FrontmostApp() { var hwnd = GetForegroundWindow(); GetWindowThreadProcessId(hwnd, out uint pid); try { var proc = Process.GetProcessById((int)pid); return new Dictionary { ["name"] = proc.ProcessName, ["bundleId"] = proc.ProcessName.ToLowerInvariant(), ["pid"] = proc.Id, ["isActive"] = true, }; } catch { throw new BridgeException("Could not determine frontmost application"); } } }