// @effect-diagnostics globalTimers:off -- Capture timeouts and Electron overlay animation timers run at native callback boundaries outside Effect fibers. import { DEFAULT_CLIENT_SETTINGS, DesktopPendingSnapShot, isModifierPairShortcut, snapShotModifierPairLabel, snapShotShortcutModifierPair, type DesktopSnapShot as DesktopSnapShotValue, type DesktopSnapShotShortcutAvailability, type DesktopSnapShotState, DesktopSnapShotSetupAction, type DesktopCaptureConfigRequest, type DesktopCaptureConfigPreview, type DesktopCaptureConfigApplied, type ClientSettings, type SnapShotModifier, type SnapShotModifierPairShortcut, type SnapShotShortcut, type DesktopSnapShotEvent, type DesktopSnapShotId,} from "@t3tools/contracts";import * as Clock from "effect/Clock";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as DateTime from "effect/DateTime";import * as Effect from "effect/Effect";import * as Encoding from "effect/Encoding";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Semaphore from "effect/Semaphore"; import * as Electron from "electron"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as DesktopClientSettings from "../settings/DesktopClientSettings.ts";import * as DesktopWindow from "../window/DesktopWindow.ts";import { startGlobalShiftShortcutProcess } from "./GlobalShiftShortcutProcess.ts";import { startMacModifierPairShortcutProcess } from "./MacModifierPairShortcutProcess.ts";import { activeWindow, type ActiveWindow } from "./ActiveWindow.ts";import { captureMacWindowSnapshot, type MacSnapShotSource } from "./MacSnapShot.ts";import type { LinuxCaptureFeedback, LinuxWindowMetadata } from "./LinuxSnapShot.ts";import { niriSocketPath } from "./NiriSnapShot.ts";import { CaptureShortcutConfig, niriCaptureConfigPath } from "./CaptureShortcutConfig.ts";import type { PortalCaptureShortcut } from "./PortalCaptureShortcut.ts";import { isGnomeCaptureSession, niriCaptureBinding, portalShortcutTrigger,} from "./linuxCaptureSession.ts";import { HyprlandCaptureSetup, HYPRLAND_CAPTURE_EXECUTABLE, isHyprlandCaptureSession, hyprlandCaptureShortcut, type HyprlandCapturePaths,} from "./HyprlandSnapShot.ts";import { KdeCaptureSetup, KDE_CAPTURE_EXECUTABLE, isKdeCaptureSession, type KdeCapturePaths,} from "./KdeSnapShot.ts";import { captureRegionWindowSnapshot, makeRegionSnapShotPool, type RegionSnapShotPool, type RegionSnapShotSource,} from "./RegionSnapShot.ts";import { type SnapShotAnimationDestination, SnapShotTransition } from "./SnapShotTransition.ts";import { type AccessibilityProcessPool, makeSnapShotAccessibilityProcessPool,} from "./SnapShotAccessibilityProcess.ts";import { showWindowsCaptureOverlay } from "./WindowsCaptureFeedback.ts"; import { boundedSnapShotString, isWaylandSession, sameSnapShotShortcut, toElectronAccelerator, snapShotShortcutRegistrationFailureMessage, snapShotShortcutSystemConflict,} from "./snapShot.ts"; const MAX_CAPTURE_WIDTH = 2_560;const MAX_CAPTURE_HEIGHT = 1_600;const SHORTCUT_COOLDOWN_NS = 200_000_000n;const WAYLAND_MODIFIER_PAIR_UNAVAILABLE_MESSAGE = "Modifier-pair shortcuts aren't available in this Wayland session. Choose another shortcut or use Take snapshot from the command palette.";const FLASH_ANIMATION_DURATION_MS = 180;const FLASH_STATIC_DURATION_MS = 60;const FLASH_FRAME_INTERVAL_MS = 16;const FLASH_PEAK_OPACITY = 0.08;const MAC_SCREEN_CAPTURE_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";const MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE = "Allow Screen Recording in System Settings, then restart T3 Code.";const MAC_ACCESSIBILITY_PERMISSION_MESSAGE = "Allow Accessibility in System Settings, then restart T3 Code.";const MAC_BOTH_PERMISSIONS_MESSAGE = "Allow Accessibility and Screen Recording in System Settings, then restart T3 Code.";const MAC_PERMISSION_MESSAGES = new Set([ MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE, MAC_ACCESSIBILITY_PERMISSION_MESSAGE, MAC_BOTH_PERMISSIONS_MESSAGE,]); const decodePendingCapture = Schema.decodeUnknownEffect(DesktopPendingSnapShot); const PendingCaptureJson = Schema.fromJsonString(DesktopPendingSnapShot);const decodePendingCaptureJson = Schema.decodeEffect(PendingCaptureJson);const encodePendingCaptureJson = Schema.encodeEffect(PendingCaptureJson);const DesktopSnapShotOperation = Schema.Literals([ "list-pending", "read", "acknowledge", "unsupported", "disabled", "no-window-selected", "window-unavailable", "capture",]); export class DesktopSnapShotError extends Schema.TaggedError<DesktopSnapShotError>()( "DesktopSnapShotError", { operation: DesktopSnapShotOperation, captureId: Schema.optional(Schema.String), cause: Schema.optional(Schema.Defect()), },) { override get message(): string { switch (this.operation) { case "list-pending": return "Could not list pending snapshots."; case "read": return "Could not read the snapshot."; case "acknowledge": return "Could not remove the snapshot."; case "unsupported": return "SnapShots are not supported here."; case "disabled": return "Enable SnapShots in Settings first."; case "no-window-selected": return "No window was selected."; case "window-unavailable": return "The active window is not available for capture."; case "capture": return "Could not capture the active window."; } }} const isDesktopSnapShotError = Schema.is(DesktopSnapShotError); function captureFailure(cause: unknown, captureId?: string): DesktopSnapShotError { return isDesktopSnapShotError(cause) ? cause : new DesktopSnapShotError({ operation: "capture", captureId, cause });} export class DesktopSnapShot extends Context.Service< DesktopSnapShot, { readonly initialize: Effect.Effect<void>; readonly configure: (settings: ClientSettings) => Effect.Effect<void>; readonly requestPermissions: (includeAccessibility: boolean) => Effect.Effect<void>; readonly state: Effect.Effect<DesktopSnapShotState>; readonly setup: ( action: DesktopSnapShotSetupAction, ) => Effect.Effect<void, DesktopSnapShotSetupError>; readonly previewConfig: ( request: DesktopCaptureConfigRequest, selectedPath?: string, ) => Effect.Effect<DesktopCaptureConfigPreview, DesktopSnapShotSetupError>; readonly applyConfig: ( previewId: string, ) => Effect.Effect<DesktopCaptureConfigApplied, DesktopSnapShotSetupError>; readonly checkShortcut: ( shortcut: SnapShotShortcut, ) => Effect.Effect<DesktopSnapShotShortcutAvailability>; readonly setShortcutSuppressed: (suppressed: boolean) => Effect.Effect<void>; /** Capture the foreground window in place, including T3 Code itself. */ readonly capture: Effect.Effect<void, DesktopSnapShotError>; readonly listPending: Effect.Effect< ReadonlyArray<DesktopPendingSnapShot>, DesktopSnapShotError >; readonly read: (id: string) => Effect.Effect<DesktopSnapShotValue, DesktopSnapShotError>; readonly setAnimationDestination: ( id: string, destination: SnapShotAnimationDestination, ) => Effect.Effect<void>; readonly dismissAnimation: (id: string) => Effect.Effect<void>; readonly acknowledge: (id: string) => Effect.Effect<void, DesktopSnapShotError>; }>()("@t3tools/desktop/snapShot/DesktopSnapShot") {} export class DesktopSnapShotSetupError extends Schema.TaggedError<DesktopSnapShotSetupError>()( "DesktopSnapShotSetupError", { action: Schema.Union([ DesktopSnapShotSetupAction, Schema.Literals(["preview-config", "apply-config"]), ]), reason: Schema.Literals(["unsupported-session", "setup-failed", "shortcut-permissions"]), cause: Schema.optional(Schema.Defect()), },) { override get message(): string { if (this.action === "preview-config" || this.action === "apply-config") { if (this.reason === "unsupported-session") return "Config setup requires a Niri or Hyprland session."; return this.action === "preview-config" ? "Couldn't prepare your capture shortcut changes." : "Couldn't save your capture shortcut."; } const kde = this.action === "install-kde-helper" || this.action === "remove-kde-helper"; const hyprland = this.action === "install-hyprland-helper" || this.action === "remove-hyprland-helper"; if (this.reason === "unsupported-session") return hyprland ? "Helper setup requires a Hyprland Wayland session outside a sandbox." : kde ? "Helper setup requires a KDE Plasma Wayland session outside a sandbox." : "Extension setup requires a GNOME Wayland session outside a sandbox."; if (this.reason === "shortcut-permissions") return "Could not open shortcut permissions."; return hyprland ? "Could not set up Hyprland capture." : kde ? "Could not set up KDE capture." : "Could not set up the GNOME extension."; }} type SnapShotSystemAnimationSettings = Pick< ReturnType<typeof Electron.systemPreferences.getAnimationSettings>, "prefersReducedMotion" | "shouldRenderRichAnimation">; function captureMode(platform: NodeJS.Platform): DesktopSnapShotState["mode"] { if (platform === "linux") return isWaylandSession(platform, process.env) ? "portal" : "unavailable"; return platform === "darwin" || platform === "win32" ? "direct" : "unavailable";} export function shouldAnimateSnapShot(settings: SnapShotSystemAnimationSettings): boolean { return settings.shouldRenderRichAnimation && !settings.prefersReducedMotion;} export function snapShotThumbnailSize(active: ActiveWindow | undefined): Electron.Size { if (!active) return { width: 2_560, height: 1_600 }; return { width: Math.min(Math.max(active.bounds.width, 1), MAX_CAPTURE_WIDTH), height: Math.min(Math.max(active.bounds.height, 1), MAX_CAPTURE_HEIGHT), };} export function snapShotIconDataUrl( ...icons: ReadonlyArray<Electron.NativeImage | null | undefined>): string | undefined { const icon = icons.find((candidate): candidate is Electron.NativeImage => Boolean(candidate && !candidate.isEmpty()), ); if (!icon) return undefined; return icon.resize({ width: 64, height: 64, quality: "best" }).toDataURL({ scaleFactor: 2 });} async function appFileIcon( path: string, platform: NodeJS.Platform,): Promise<Electron.NativeImage | undefined> { if (platform === "darwin") { const thumbnail = await Electron.nativeImage .createThumbnailFromPath(path, { width: 64, height: 64 }) .catch(() => undefined); if (thumbnail && !thumbnail.isEmpty()) return thumbnail; } return Electron.app.getFileIcon(path, { size: "normal" }).catch(() => undefined);} export async function iconDataUrl( source: { readonly appIcon?: Electron.NativeImage | null }, active: ActiveWindow | undefined, platform: NodeJS.Platform,): Promise<string | undefined> { try { const fileIcon = active?.owner.path ? await appFileIcon(active.owner.path, platform) : undefined; return snapShotIconDataUrl(fileIcon, source.appIcon); } catch { return undefined; }} function snapShotAppName( active: ActiveWindow | undefined, linuxWindow: LinuxWindowMetadata | undefined, sourceName: string,): string { const appName = active?.owner.name.trim() || linuxWindow?.appName.trim() || sourceName.trim() || "Window"; if (active?.platform !== "windows") return appName; return appName.replace(/\.exe$/i, "").trim() || appName;} async function requestMacScreenCapturePermission(): Promise<string | null> { let status: ReturnType<typeof Electron.systemPreferences.getMediaAccessStatus>; try { status = Electron.systemPreferences.getMediaAccessStatus("screen"); if (status === "granted") return null; if (status === "not-determined") { try { await Electron.desktopCapturer.getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 }, }); } catch {} status = Electron.systemPreferences.getMediaAccessStatus("screen"); if (status === "granted") return null; } } catch {} await Electron.shell.openExternal(MAC_SCREEN_CAPTURE_SETTINGS_URL).catch(() => undefined); return MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE;} function currentMacPermissions(): NonNullable<DesktopSnapShotState["macPermissions"]> { return { screenRecording: Electron.systemPreferences.getMediaAccessStatus("screen") === "granted", accessibility: Electron.systemPreferences.isTrustedAccessibilityClient(false), };} function macPermissionMessage( permissions: NonNullable<DesktopSnapShotState["macPermissions"]>, includeAccessibility: boolean,): string | null { const accessibilityGranted = !includeAccessibility || permissions.accessibility; if (!accessibilityGranted && !permissions.screenRecording) { return MAC_BOTH_PERMISSIONS_MESSAGE; } if (!accessibilityGranted) { return MAC_ACCESSIBILITY_PERMISSION_MESSAGE; } return permissions.screenRecording ? null : MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE;} function currentMacSnapShotPermissionMessage(includeAccessibility: boolean): string | null { return macPermissionMessage( { screenRecording: Electron.systemPreferences.getMediaAccessStatus("screen") === "granted", accessibility: !includeAccessibility || Electron.systemPreferences.isTrustedAccessibilityClient(false), }, includeAccessibility, );} async function requestMacSnapShotPermissions( includeAccessibility: boolean,): Promise<string | null> { const accessibilityGranted = !includeAccessibility || Electron.systemPreferences.isTrustedAccessibilityClient(true); const screenMessage = await requestMacScreenCapturePermission(); if (!accessibilityGranted && screenMessage) { return MAC_BOTH_PERMISSIONS_MESSAGE; } if (!accessibilityGranted) { return MAC_ACCESSIBILITY_PERMISSION_MESSAGE; } return screenMessage;} function snapShotImageSize(png: Buffer, fallback: Electron.Rectangle): Electron.Size { try { const size = Electron.nativeImage.createFromBuffer(png).getSize(); if (size.width > 0 && size.height > 0) return size; } catch {} return { width: Math.max(1, Math.round(fallback.width)), height: Math.max(1, Math.round(fallback.height)), };} async function captureSource({ mode, captureId, platform, settings, flash, transition, imageTempPath, linuxAppId, kdeCapturePaths, hyprlandCapturePaths, accessibilityProcessPool, regionSnapShotPool, prepareReveal, onLinuxFeedback,}: { mode: DesktopSnapShotState["mode"]; captureId: string; platform: NodeJS.Platform; settings: ClientSettings; flash: SnapShotFlash; transition: SnapShotTransition; imageTempPath: string; linuxAppId: string; kdeCapturePaths: KdeCapturePaths; hyprlandCapturePaths: HyprlandCapturePaths; accessibilityProcessPool: AccessibilityProcessPool; regionSnapShotPool: RegionSnapShotPool; prepareReveal: () => Promise<void>; onLinuxFeedback: (feedback: LinuxCaptureFeedback) => void;}) { let active: ActiveWindow | undefined; let linuxWindow: LinuxWindowMetadata | undefined; let linuxFeedback: LinuxCaptureFeedback | undefined; let linuxActivationFailure: { readonly cause: unknown } | undefined; const destinationWindow = Electron.BrowserWindow.getFocusedWindow() ?? Electron.BrowserWindow.getAllWindows().find((window) => !window.isDestroyed()); const destinationWindowBounds = destinationWindow?.getBounds(); { const revealPreparation = platform === "win32" ? prepareReveal().catch(() => undefined) : Promise.resolve(); if (mode === "direct") { active = await activeWindow(platform); } let source: MacSnapShotSource | RegionSnapShotSource | Electron.DesktopCapturerSource; let png: Buffer; let imageTempReady = false; if (platform === "darwin") { if (!active) { throw new DesktopSnapShotError({ operation: "window-unavailable", captureId }); } ({ source, png } = await captureMacWindowSnapshot( active, imageTempPath, snapShotThumbnailSize(active), )); imageTempReady = true; } else if (mode === "direct") { if (!active) { throw new DesktopSnapShotError({ operation: "window-unavailable", captureId }); } ({ source, png } = await captureRegionWindowSnapshot( regionSnapShotPool, active, snapShotFlashBounds(active, platform), snapShotThumbnailSize(active), )); } else { const { captureLinuxWindow } = await import("./LinuxSnapShot.ts"); const snapshot = await captureLinuxWindow( linuxAppId, { flash: settings.snapShotFlash, animate: settings.snapShotAnimations && shouldAnimateSnapShot(Electron.systemPreferences.getAnimationSettings()), }, kdeCapturePaths, hyprlandCapturePaths, ); if (snapshot) { linuxFeedback = snapshot.feedback; if (linuxFeedback) onLinuxFeedback(linuxFeedback); linuxWindow = snapshot.window; source = { name: linuxWindow?.title || "Active window" }; png = snapshot.png; } else { const [selected] = await Electron.desktopCapturer.getSources({ types: ["window", "screen"], thumbnailSize: snapShotThumbnailSize(active), fetchWindowIcons: true, }); if (!selected || selected.thumbnail.isEmpty()) { throw new DesktopSnapShotError({ operation: "no-window-selected", captureId, }); } source = selected; png = selected.thumbnail.toPNG(); } } const accessibleIdentity = active ? { ...active, bounds: snapShotFlashBounds(active, platform) } : linuxWindow?.processId ? { title: linuxWindow.title, bounds: linuxWindow.bounds,Lines 1–500 of 1679. Load more
// @effect-diagnostics globalTimers:off -- Capture timeouts and Electron overlay animation timers run at native callback boundaries outside Effect fibers. import { DEFAULT_CLIENT_SETTINGS, DesktopPendingSnapShot, isModifierPairShortcut, snapShotModifierPairLabel, snapShotShortcutModifierPair, type DesktopSnapShot as DesktopSnapShotValue, type DesktopSnapShotShortcutAvailability, type DesktopSnapShotState, DesktopSnapShotSetupAction, type DesktopCaptureConfigRequest, type DesktopCaptureConfigPreview, type DesktopCaptureConfigApplied, type ClientSettings, type SnapShotModifier, type SnapShotModifierPairShortcut, type SnapShotShortcut, type DesktopSnapShotEvent, type DesktopSnapShotId, } from "@t3tools/contracts"; import * as Clock from "effect/Clock"; import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Encoding from "effect/Encoding"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Semaphore from "effect/Semaphore"; import * as Electron from "electron"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; import * as DesktopClientSettings from "../settings/DesktopClientSettings.ts"; import * as DesktopWindow from "../window/DesktopWindow.ts"; import { startGlobalShiftShortcutProcess } from "./GlobalShiftShortcutProcess.ts"; import { startMacModifierPairShortcutProcess } from "./MacModifierPairShortcutProcess.ts"; import { activeWindow, type ActiveWindow } from "./ActiveWindow.ts"; import { captureMacWindowSnapshot, type MacSnapShotSource } from "./MacSnapShot.ts"; import type { LinuxCaptureFeedback, LinuxWindowMetadata } from "./LinuxSnapShot.ts"; import { niriSocketPath } from "./NiriSnapShot.ts"; import { CaptureShortcutConfig, niriCaptureConfigPath } from "./CaptureShortcutConfig.ts"; import type { PortalCaptureShortcut } from "./PortalCaptureShortcut.ts"; import { isGnomeCaptureSession, niriCaptureBinding, portalShortcutTrigger, } from "./linuxCaptureSession.ts"; import { HyprlandCaptureSetup, HYPRLAND_CAPTURE_EXECUTABLE, isHyprlandCaptureSession, hyprlandCaptureShortcut, type HyprlandCapturePaths, } from "./HyprlandSnapShot.ts"; import { KdeCaptureSetup, KDE_CAPTURE_EXECUTABLE, isKdeCaptureSession, type KdeCapturePaths, } from "./KdeSnapShot.ts"; import { captureRegionWindowSnapshot, makeRegionSnapShotPool, type RegionSnapShotPool, type RegionSnapShotSource, } from "./RegionSnapShot.ts"; import { type SnapShotAnimationDestination, SnapShotTransition } from "./SnapShotTransition.ts"; import { type AccessibilityProcessPool, makeSnapShotAccessibilityProcessPool, } from "./SnapShotAccessibilityProcess.ts"; import { showWindowsCaptureOverlay } from "./WindowsCaptureFeedback.ts"; import { boundedSnapShotString, isWaylandSession, sameSnapShotShortcut, toElectronAccelerator, snapShotShortcutRegistrationFailureMessage, snapShotShortcutSystemConflict, } from "./snapShot.ts"; const MAX_CAPTURE_WIDTH = 2_560; const MAX_CAPTURE_HEIGHT = 1_600; const SHORTCUT_COOLDOWN_NS = 200_000_000n; const WAYLAND_MODIFIER_PAIR_UNAVAILABLE_MESSAGE = "Modifier-pair shortcuts aren't available in this Wayland session. Choose another shortcut or use Take snapshot from the command palette."; const FLASH_ANIMATION_DURATION_MS = 180; const FLASH_STATIC_DURATION_MS = 60; const FLASH_FRAME_INTERVAL_MS = 16; const FLASH_PEAK_OPACITY = 0.08; const MAC_SCREEN_CAPTURE_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"; const MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE = "Allow Screen Recording in System Settings, then restart T3 Code."; const MAC_ACCESSIBILITY_PERMISSION_MESSAGE = "Allow Accessibility in System Settings, then restart T3 Code."; const MAC_BOTH_PERMISSIONS_MESSAGE = "Allow Accessibility and Screen Recording in System Settings, then restart T3 Code."; const MAC_PERMISSION_MESSAGES = new Set([ MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE, MAC_ACCESSIBILITY_PERMISSION_MESSAGE, MAC_BOTH_PERMISSIONS_MESSAGE, ]); const decodePendingCapture = Schema.decodeUnknownEffect(DesktopPendingSnapShot); const PendingCaptureJson = Schema.fromJsonString(DesktopPendingSnapShot); const decodePendingCaptureJson = Schema.decodeEffect(PendingCaptureJson); const encodePendingCaptureJson = Schema.encodeEffect(PendingCaptureJson); const DesktopSnapShotOperation = Schema.Literals([ "list-pending", "read", "acknowledge", "unsupported", "disabled", "no-window-selected", "window-unavailable", "capture", ]); export class DesktopSnapShotError extends Schema.TaggedError<DesktopSnapShotError>()( "DesktopSnapShotError", { operation: DesktopSnapShotOperation, captureId: Schema.optional(Schema.String), cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { switch (this.operation) { case "list-pending": return "Could not list pending snapshots."; case "read": return "Could not read the snapshot."; case "acknowledge": return "Could not remove the snapshot."; case "unsupported": return "SnapShots are not supported here."; case "disabled": return "Enable SnapShots in Settings first."; case "no-window-selected": return "No window was selected."; case "window-unavailable": return "The active window is not available for capture."; case "capture": return "Could not capture the active window."; } } } const isDesktopSnapShotError = Schema.is(DesktopSnapShotError); function captureFailure(cause: unknown, captureId?: string): DesktopSnapShotError { return isDesktopSnapShotError(cause) ? cause : new DesktopSnapShotError({ operation: "capture", captureId, cause }); } export class DesktopSnapShot extends Context.Service< DesktopSnapShot, { readonly initialize: Effect.Effect<void>; readonly configure: (settings: ClientSettings) => Effect.Effect<void>; readonly requestPermissions: (includeAccessibility: boolean) => Effect.Effect<void>; readonly state: Effect.Effect<DesktopSnapShotState>; readonly setup: ( action: DesktopSnapShotSetupAction, ) => Effect.Effect<void, DesktopSnapShotSetupError>; readonly previewConfig: ( request: DesktopCaptureConfigRequest, selectedPath?: string, ) => Effect.Effect<DesktopCaptureConfigPreview, DesktopSnapShotSetupError>; readonly applyConfig: ( previewId: string, ) => Effect.Effect<DesktopCaptureConfigApplied, DesktopSnapShotSetupError>; readonly checkShortcut: ( shortcut: SnapShotShortcut, ) => Effect.Effect<DesktopSnapShotShortcutAvailability>; readonly setShortcutSuppressed: (suppressed: boolean) => Effect.Effect<void>; /** Capture the foreground window in place, including T3 Code itself. */ readonly capture: Effect.Effect<void, DesktopSnapShotError>; readonly listPending: Effect.Effect< ReadonlyArray<DesktopPendingSnapShot>, DesktopSnapShotError >; readonly read: (id: string) => Effect.Effect<DesktopSnapShotValue, DesktopSnapShotError>; readonly setAnimationDestination: ( id: string, destination: SnapShotAnimationDestination, ) => Effect.Effect<void>; readonly dismissAnimation: (id: string) => Effect.Effect<void>; readonly acknowledge: (id: string) => Effect.Effect<void, DesktopSnapShotError>; } >()("@t3tools/desktop/snapShot/DesktopSnapShot") {} export class DesktopSnapShotSetupError extends Schema.TaggedError<DesktopSnapShotSetupError>()( "DesktopSnapShotSetupError", { action: Schema.Union([ DesktopSnapShotSetupAction, Schema.Literals(["preview-config", "apply-config"]), ]), reason: Schema.Literals(["unsupported-session", "setup-failed", "shortcut-permissions"]), cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { if (this.action === "preview-config" || this.action === "apply-config") { if (this.reason === "unsupported-session") return "Config setup requires a Niri or Hyprland session."; return this.action === "preview-config" ? "Couldn't prepare your capture shortcut changes." : "Couldn't save your capture shortcut."; } const kde = this.action === "install-kde-helper" || this.action === "remove-kde-helper"; const hyprland = this.action === "install-hyprland-helper" || this.action === "remove-hyprland-helper"; if (this.reason === "unsupported-session") return hyprland ? "Helper setup requires a Hyprland Wayland session outside a sandbox." : kde ? "Helper setup requires a KDE Plasma Wayland session outside a sandbox." : "Extension setup requires a GNOME Wayland session outside a sandbox."; if (this.reason === "shortcut-permissions") return "Could not open shortcut permissions."; return hyprland ? "Could not set up Hyprland capture." : kde ? "Could not set up KDE capture." : "Could not set up the GNOME extension."; } } type SnapShotSystemAnimationSettings = Pick< ReturnType<typeof Electron.systemPreferences.getAnimationSettings>, "prefersReducedMotion" | "shouldRenderRichAnimation" >; function captureMode(platform: NodeJS.Platform): DesktopSnapShotState["mode"] { if (platform === "linux") return isWaylandSession(platform, process.env) ? "portal" : "unavailable"; return platform === "darwin" || platform === "win32" ? "direct" : "unavailable"; } export function shouldAnimateSnapShot(settings: SnapShotSystemAnimationSettings): boolean { return settings.shouldRenderRichAnimation && !settings.prefersReducedMotion; } export function snapShotThumbnailSize(active: ActiveWindow | undefined): Electron.Size { if (!active) return { width: 2_560, height: 1_600 }; return { width: Math.min(Math.max(active.bounds.width, 1), MAX_CAPTURE_WIDTH), height: Math.min(Math.max(active.bounds.height, 1), MAX_CAPTURE_HEIGHT), }; } export function snapShotIconDataUrl( ...icons: ReadonlyArray<Electron.NativeImage | null | undefined> ): string | undefined { const icon = icons.find((candidate): candidate is Electron.NativeImage => Boolean(candidate && !candidate.isEmpty()), ); if (!icon) return undefined; return icon.resize({ width: 64, height: 64, quality: "best" }).toDataURL({ scaleFactor: 2 }); } async function appFileIcon( path: string, platform: NodeJS.Platform, ): Promise<Electron.NativeImage | undefined> { if (platform === "darwin") { const thumbnail = await Electron.nativeImage .createThumbnailFromPath(path, { width: 64, height: 64 }) .catch(() => undefined); if (thumbnail && !thumbnail.isEmpty()) return thumbnail; } return Electron.app.getFileIcon(path, { size: "normal" }).catch(() => undefined); } export async function iconDataUrl( source: { readonly appIcon?: Electron.NativeImage | null }, active: ActiveWindow | undefined, platform: NodeJS.Platform, ): Promise<string | undefined> { try { const fileIcon = active?.owner.path ? await appFileIcon(active.owner.path, platform) : undefined; return snapShotIconDataUrl(fileIcon, source.appIcon); } catch { return undefined; } } function snapShotAppName( active: ActiveWindow | undefined, linuxWindow: LinuxWindowMetadata | undefined, sourceName: string, ): string { const appName = active?.owner.name.trim() || linuxWindow?.appName.trim() || sourceName.trim() || "Window"; if (active?.platform !== "windows") return appName; return appName.replace(/\.exe$/i, "").trim() || appName; } async function requestMacScreenCapturePermission(): Promise<string | null> { let status: ReturnType<typeof Electron.systemPreferences.getMediaAccessStatus>; try { status = Electron.systemPreferences.getMediaAccessStatus("screen"); if (status === "granted") return null; if (status === "not-determined") { try { await Electron.desktopCapturer.getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 }, }); } catch {} status = Electron.systemPreferences.getMediaAccessStatus("screen"); if (status === "granted") return null; } } catch {} await Electron.shell.openExternal(MAC_SCREEN_CAPTURE_SETTINGS_URL).catch(() => undefined); return MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE; } function currentMacPermissions(): NonNullable<DesktopSnapShotState["macPermissions"]> { return { screenRecording: Electron.systemPreferences.getMediaAccessStatus("screen") === "granted", accessibility: Electron.systemPreferences.isTrustedAccessibilityClient(false), }; } function macPermissionMessage( permissions: NonNullable<DesktopSnapShotState["macPermissions"]>, includeAccessibility: boolean, ): string | null { const accessibilityGranted = !includeAccessibility || permissions.accessibility; if (!accessibilityGranted && !permissions.screenRecording) { return MAC_BOTH_PERMISSIONS_MESSAGE; } if (!accessibilityGranted) { return MAC_ACCESSIBILITY_PERMISSION_MESSAGE; } return permissions.screenRecording ? null : MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE; } function currentMacSnapShotPermissionMessage(includeAccessibility: boolean): string | null { return macPermissionMessage( { screenRecording: Electron.systemPreferences.getMediaAccessStatus("screen") === "granted", accessibility: !includeAccessibility || Electron.systemPreferences.isTrustedAccessibilityClient(false), }, includeAccessibility, ); } async function requestMacSnapShotPermissions( includeAccessibility: boolean, ): Promise<string | null> { const accessibilityGranted = !includeAccessibility || Electron.systemPreferences.isTrustedAccessibilityClient(true); const screenMessage = await requestMacScreenCapturePermission(); if (!accessibilityGranted && screenMessage) { return MAC_BOTH_PERMISSIONS_MESSAGE; } if (!accessibilityGranted) { return MAC_ACCESSIBILITY_PERMISSION_MESSAGE; } return screenMessage; } function snapShotImageSize(png: Buffer, fallback: Electron.Rectangle): Electron.Size { try { const size = Electron.nativeImage.createFromBuffer(png).getSize(); if (size.width > 0 && size.height > 0) return size; } catch {} return { width: Math.max(1, Math.round(fallback.width)), height: Math.max(1, Math.round(fallback.height)), }; } async function captureSource({ mode, captureId, platform, settings, flash, transition, imageTempPath, linuxAppId, kdeCapturePaths, hyprlandCapturePaths, accessibilityProcessPool, regionSnapShotPool, prepareReveal, onLinuxFeedback, }: { mode: DesktopSnapShotState["mode"]; captureId: string; platform: NodeJS.Platform; settings: ClientSettings; flash: SnapShotFlash; transition: SnapShotTransition; imageTempPath: string; linuxAppId: string; kdeCapturePaths: KdeCapturePaths; hyprlandCapturePaths: HyprlandCapturePaths; accessibilityProcessPool: AccessibilityProcessPool; regionSnapShotPool: RegionSnapShotPool; prepareReveal: () => Promise<void>; onLinuxFeedback: (feedback: LinuxCaptureFeedback) => void; }) { let active: ActiveWindow | undefined; let linuxWindow: LinuxWindowMetadata | undefined; let linuxFeedback: LinuxCaptureFeedback | undefined; let linuxActivationFailure: { readonly cause: unknown } | undefined; const destinationWindow = Electron.BrowserWindow.getFocusedWindow() ?? Electron.BrowserWindow.getAllWindows().find((window) => !window.isDestroyed()); const destinationWindowBounds = destinationWindow?.getBounds(); { const revealPreparation = platform === "win32" ? prepareReveal().catch(() => undefined) : Promise.resolve(); if (mode === "direct") { active = await activeWindow(platform); } let source: MacSnapShotSource | RegionSnapShotSource | Electron.DesktopCapturerSource; let png: Buffer; let imageTempReady = false; if (platform === "darwin") { if (!active) { throw new DesktopSnapShotError({ operation: "window-unavailable", captureId }); } ({ source, png } = await captureMacWindowSnapshot( active, imageTempPath, snapShotThumbnailSize(active), )); imageTempReady = true; } else if (mode === "direct") { if (!active) { throw new DesktopSnapShotError({ operation: "window-unavailable", captureId }); } ({ source, png } = await captureRegionWindowSnapshot( regionSnapShotPool, active, snapShotFlashBounds(active, platform), snapShotThumbnailSize(active), )); } else { const { captureLinuxWindow } = await import("./LinuxSnapShot.ts"); const snapshot = await captureLinuxWindow( linuxAppId, { flash: settings.snapShotFlash, animate: settings.snapShotAnimations && shouldAnimateSnapShot(Electron.systemPreferences.getAnimationSettings()), }, kdeCapturePaths, hyprlandCapturePaths, ); if (snapshot) { linuxFeedback = snapshot.feedback; if (linuxFeedback) onLinuxFeedback(linuxFeedback); linuxWindow = snapshot.window; source = { name: linuxWindow?.title || "Active window" }; png = snapshot.png; } else { const [selected] = await Electron.desktopCapturer.getSources({ types: ["window", "screen"], thumbnailSize: snapShotThumbnailSize(active), fetchWindowIcons: true, }); if (!selected || selected.thumbnail.isEmpty()) { throw new DesktopSnapShotError({ operation: "no-window-selected", captureId, }); } source = selected; png = selected.thumbnail.toPNG(); } } const accessibleIdentity = active ? { ...active, bounds: snapShotFlashBounds(active, platform) } : linuxWindow?.processId ? { title: linuxWindow.title, bounds: linuxWindow.bounds,