mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 06:46:09 +00:00
101 lines
4.1 KiB
TypeScript
101 lines
4.1 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import type { SelectedSource, WindowBounds } from "./types";
|
|
import { getScreen, parseWindowId } from "./utils";
|
|
import { convertPhysicalBoundsToDip } from "./windowsCaptureSelection";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
export async function bringWindowsWindowForward(windowId: number): Promise<void> {
|
|
const script = [
|
|
'Add-Type -TypeDefinition @"',
|
|
"using System; using System.Runtime.InteropServices;",
|
|
"public static class RecordlyForegroundWindow {",
|
|
' [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);',
|
|
' [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr hWnd);',
|
|
' [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);',
|
|
"}",
|
|
'"@',
|
|
"$handle = [IntPtr][Int64]$env:RECORDLY_WINDOW_ID",
|
|
"if ([RecordlyForegroundWindow]::IsIconic($handle)) { [RecordlyForegroundWindow]::ShowWindowAsync($handle, 9) | Out-Null }",
|
|
"[RecordlyForegroundWindow]::SetForegroundWindow($handle) | Out-Null",
|
|
].join("\n");
|
|
|
|
await execFileAsync("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
timeout: 5000,
|
|
env: { ...process.env, RECORDLY_WINDOW_ID: String(windowId) },
|
|
});
|
|
}
|
|
|
|
export async function resolveWindowsWindowBounds(
|
|
source: SelectedSource,
|
|
): Promise<WindowBounds | null> {
|
|
const windowId = parseWindowId(source.id);
|
|
const windowTitle =
|
|
typeof source.windowTitle === "string" ? source.windowTitle.trim() : source.name.trim();
|
|
|
|
if (!windowId && !windowTitle) return null;
|
|
|
|
const script = [
|
|
"$windowId = $env:RECORDLY_WINDOW_ID",
|
|
"$windowTitle = $env:RECORDLY_WINDOW_TITLE",
|
|
'Add-Type -TypeDefinition @"',
|
|
"using System;",
|
|
"using System.Runtime.InteropServices;",
|
|
"public static class RecordlyWindowBounds {",
|
|
" [StructLayout(LayoutKind.Sequential)]",
|
|
" public struct RECT {",
|
|
" public int Left;",
|
|
" public int Top;",
|
|
" public int Right;",
|
|
" public int Bottom;",
|
|
" }",
|
|
' [DllImport("user32.dll")]',
|
|
" [return: MarshalAs(UnmanagedType.Bool)]",
|
|
" public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);",
|
|
' [DllImport("dwmapi.dll")]',
|
|
" public static extern int DwmGetWindowAttribute(IntPtr hWnd, int attribute, out RECT rect, int size);",
|
|
' [DllImport("user32.dll")]',
|
|
" public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);",
|
|
"}",
|
|
'"@',
|
|
"$handle = [Int64]0",
|
|
"if ($windowId) { $handle = [Int64]$windowId }",
|
|
"$escapedWindowTitle = if ($windowTitle) { [WildcardPattern]::Escape($windowTitle) } else { $null }",
|
|
"if ($handle -le 0 -and $windowTitle) {",
|
|
' $matchingProcess = Get-Process | Where-Object { $_.MainWindowTitle -eq $windowTitle -or ($escapedWindowTitle -and $_.MainWindowTitle -like "*$escapedWindowTitle*") } | Select-Object -First 1',
|
|
" if ($matchingProcess) { $handle = $matchingProcess.MainWindowHandle.ToInt64() }",
|
|
"}",
|
|
"if ($handle -le 0) { exit 1 }",
|
|
"$rect = New-Object RecordlyWindowBounds+RECT",
|
|
"[RecordlyWindowBounds]::SetThreadDpiAwarenessContext([IntPtr](-4)) | Out-Null",
|
|
"$dwmResult = [RecordlyWindowBounds]::DwmGetWindowAttribute([IntPtr]$handle, 9, [ref]$rect, [Runtime.InteropServices.Marshal]::SizeOf($rect))",
|
|
"if ($dwmResult -ne 0 -and -not [RecordlyWindowBounds]::GetWindowRect([IntPtr]$handle, [ref]$rect)) { exit 1 }",
|
|
"@{ x = $rect.Left; y = $rect.Top; width = $rect.Right - $rect.Left; height = $rect.Bottom - $rect.Top } | ConvertTo-Json -Compress",
|
|
].join("\n");
|
|
|
|
try {
|
|
const { stdout } = await execFileAsync(
|
|
"powershell.exe",
|
|
["-NoProfile", "-Command", script],
|
|
{
|
|
timeout: 5000,
|
|
env: {
|
|
...process.env,
|
|
RECORDLY_WINDOW_ID: String(windowId ?? ""),
|
|
RECORDLY_WINDOW_TITLE: windowTitle,
|
|
},
|
|
},
|
|
);
|
|
const bounds = JSON.parse(stdout) as WindowBounds;
|
|
if (!bounds || bounds.width <= 0 || bounds.height <= 0) return null;
|
|
|
|
const electronScreen = getScreen();
|
|
return typeof electronScreen.screenToDipPoint === "function"
|
|
? convertPhysicalBoundsToDip(bounds, (point) => electronScreen.screenToDipPoint(point))
|
|
: bounds;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|