import { DesktopPendingSnapShot, DesktopSnapShot as DesktopSnapShotSchema, DesktopSnapShotAnimationDestination, DesktopSnapShotId, DesktopSnapShotShortcutAvailability, DesktopSnapShotState, DesktopSnapShotSetupAction, SnapShotShortcut, DesktopCaptureConfigRequest, DesktopCaptureConfigPreview, DesktopCaptureConfigApplied,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import type * as Electron from "electron"; import * as ElectronWindow from "../../electron/ElectronWindow.ts";import * as ElectronDialog from "../../electron/ElectronDialog.ts";import * as DesktopSnapShot from "../../snapShot/DesktopSnapShot.ts";import * as IpcChannels from "../channels.ts";import * as DesktopIpc from "../DesktopIpc.ts"; class SnapShotIpcUnauthorizedSenderError extends Schema.TaggedError<SnapShotIpcUnauthorizedSenderError>()( "SnapShotIpcUnauthorizedSenderError", {},) { override get message(): string { return "Snapshot request was rejected."; }} const ensureTrustedSnapShotSender = Effect.fn("desktop.ipc.snapShot.ensureTrustedSender")( function* (event: DesktopIpc.DesktopIpcInvokeEvent | undefined) { const main = yield* (yield* ElectronWindow.ElectronWindow).main; if ( event === undefined || Option.isNone(main) || main.value.webContents.id !== event.sender.id ) { return yield* new SnapShotIpcUnauthorizedSenderError(); } return main.value; },); export function snapShotScreenFrame( viewportFrame: DesktopSnapShotAnimationDestination["viewportFrame"], contentBounds: Electron.Rectangle, zoomFactor: number,): Electron.Rectangle { return { x: contentBounds.x + viewportFrame.x * zoomFactor, y: contentBounds.y + viewportFrame.y * zoomFactor, width: viewportFrame.width * zoomFactor, height: viewportFrame.height * zoomFactor, };} export function snapShotRelativeFrame( frame: DesktopSnapShotAnimationDestination["viewportFrame"], bounds: Electron.Rectangle, zoom: number,): Electron.Rectangle | undefined { if (bounds.width <= 0 || bounds.height <= 0) return undefined; return { x: (frame.x * zoom) / bounds.width, y: (frame.y * zoom) / bounds.height, width: (frame.width * zoom) / bounds.width, height: (frame.height * zoom) / bounds.height, };} export const getSnapShotState = DesktopIpc.makeIpcMethod({ channel: IpcChannels.GET_SNAP_SHOT_STATE_CHANNEL, payload: Schema.Void, result: DesktopSnapShotState, handler: Effect.fn("desktop.ipc.snapShot.getState")(function* () { return yield* (yield* DesktopSnapShot.DesktopSnapShot).state; }),}); export const requestSnapShotPermissions = DesktopIpc.makeIpcMethod({ channel: IpcChannels.REQUEST_SNAP_SHOT_PERMISSIONS_CHANNEL, payload: Schema.Boolean, result: Schema.Void, handler: Effect.fn("desktop.ipc.snapShot.requestPermissions")( function* (includeAccessibility, event) { yield* ensureTrustedSnapShotSender(event); yield* (yield* DesktopSnapShot.DesktopSnapShot).requestPermissions(includeAccessibility); }, ),}); export const setupSnapShot = DesktopIpc.makeIpcMethod({ channel: IpcChannels.SETUP_SNAP_SHOT_CHANNEL, payload: DesktopSnapShotSetupAction, result: Schema.Void, handler: Effect.fn("desktop.ipc.snapShot.setup")(function* (action, event) { yield* ensureTrustedSnapShotSender(event); yield* (yield* DesktopSnapShot.DesktopSnapShot).setup(action); }),}); export const previewSnapShotConfig = DesktopIpc.makeIpcMethod({ channel: IpcChannels.PREVIEW_SNAP_SHOT_CONFIG_CHANNEL, payload: DesktopCaptureConfigRequest, result: Schema.NullOr(DesktopCaptureConfigPreview), handler: Effect.fn("desktop.ipc.snapShot.previewConfig")(function* (request, event) { const window = yield* ensureTrustedSnapShotSender(event); const capture = yield* DesktopSnapShot.DesktopSnapShot; let selectedPath: string | undefined; if (request.chooseFile) { const state = yield* capture.state; const paths = yield* (yield* ElectronDialog.ElectronDialog).pickFiles({ owner: Option.some(window), defaultPath: Option.fromUndefinedOr(state.shortcutConfigPath), filters: [ { name: "Desktop config", extensions: state.linuxBackend === "niri" ? ["kdl"] : ["conf", "lua"], }, ], multiple: false, }); selectedPath = paths[0]; if (!selectedPath) return null; } return yield* capture.previewConfig(request, selectedPath); }),}); export const applySnapShotConfig = DesktopIpc.makeIpcMethod({ channel: IpcChannels.APPLY_SNAP_SHOT_CONFIG_CHANNEL, payload: DesktopSnapShotId, result: DesktopCaptureConfigApplied, handler: Effect.fn("desktop.ipc.snapShot.applyConfig")(function* (id, event) { yield* ensureTrustedSnapShotSender(event); return yield* (yield* DesktopSnapShot.DesktopSnapShot).applyConfig(id); }),}); export const checkSnapShotShortcut = DesktopIpc.makeIpcMethod({ channel: IpcChannels.CHECK_SNAP_SHOT_SHORTCUT_CHANNEL, payload: SnapShotShortcut, result: DesktopSnapShotShortcutAvailability, handler: Effect.fn("desktop.ipc.snapShot.checkShortcut")(function* (shortcut, event) { yield* ensureTrustedSnapShotSender(event); return yield* (yield* DesktopSnapShot.DesktopSnapShot).checkShortcut(shortcut); }),}); export const setSnapShotShortcutSuppressed = DesktopIpc.makeIpcMethod({ channel: IpcChannels.SET_SNAP_SHOT_SHORTCUT_SUPPRESSED_CHANNEL, payload: Schema.Boolean, result: Schema.Void, handler: Effect.fn("desktop.ipc.snapShot.setShortcutSuppressed")(function* (suppressed, event) { yield* ensureTrustedSnapShotSender(event); yield* (yield* DesktopSnapShot.DesktopSnapShot).setShortcutSuppressed(suppressed); }),}); export const listPendingSnapShots = DesktopIpc.makeIpcMethod({ channel: IpcChannels.LIST_PENDING_SNAP_SHOTS_CHANNEL, payload: Schema.Void, result: Schema.Array(DesktopPendingSnapShot), handler: Effect.fn("desktop.ipc.snapShot.listPending")(function* (_, event) { yield* ensureTrustedSnapShotSender(event); return yield* (yield* DesktopSnapShot.DesktopSnapShot).listPending; }),}); export const readSnapShot = DesktopIpc.makeIpcMethod({ channel: IpcChannels.READ_SNAP_SHOT_CHANNEL, payload: DesktopSnapShotId, result: DesktopSnapShotSchema, handler: Effect.fn("desktop.ipc.snapShot.read")(function* (id, event) { yield* ensureTrustedSnapShotSender(event); return yield* (yield* DesktopSnapShot.DesktopSnapShot).read(id); }),}); export const setSnapShotAnimationDestination = DesktopIpc.makeIpcMethod({ channel: IpcChannels.SET_SNAP_SHOT_ANIMATION_DESTINATION_CHANNEL, payload: DesktopSnapShotAnimationDestination, result: Schema.Void, handler: Effect.fn("desktop.ipc.snapShot.setAnimationDestination")( function* (destination, event) { const window = yield* ensureTrustedSnapShotSender(event); if ( destination.viewportFrame.width <= 0 || destination.viewportFrame.height <= 0 || destination.borderWidth < 0 || destination.cornerRadius < 0 ) { return; } yield* (yield* DesktopSnapShot.DesktopSnapShot).setAnimationDestination(destination.id, { relativeFrame: snapShotRelativeFrame( destination.viewportFrame, window.getContentBounds(), window.webContents.getZoomFactor(), ), frame: snapShotScreenFrame( destination.viewportFrame, window.getContentBounds(), window.webContents.getZoomFactor(), ), backgroundColor: destination.backgroundColor, borderColor: destination.borderColor, borderWidth: destination.borderWidth * window.webContents.getZoomFactor(), cornerRadius: destination.cornerRadius * window.webContents.getZoomFactor(), scaleFactor: window.webContents.getZoomFactor(), details: destination.details, }); }, ),}); export const dismissSnapShotAnimation = DesktopIpc.makeIpcMethod({ channel: IpcChannels.DISMISS_SNAP_SHOT_ANIMATION_CHANNEL, payload: DesktopSnapShotId, result: Schema.Void, handler: Effect.fn("desktop.ipc.snapShot.dismissAnimation")(function* (id, event) { yield* ensureTrustedSnapShotSender(event); yield* (yield* DesktopSnapShot.DesktopSnapShot).dismissAnimation(id); }),}); export const acknowledgeSnapShot = DesktopIpc.makeIpcMethod({ channel: IpcChannels.ACKNOWLEDGE_SNAP_SHOT_CHANNEL, payload: DesktopSnapShotId, result: Schema.Void, handler: Effect.fn("desktop.ipc.snapShot.acknowledge")(function* (id, event) { yield* ensureTrustedSnapShotSender(event); yield* (yield* DesktopSnapShot.DesktopSnapShot).acknowledge(id); }),});
import {
DesktopPendingSnapShot,
DesktopSnapShot as DesktopSnapShotSchema,
DesktopSnapShotAnimationDestination,
DesktopSnapShotId,
DesktopSnapShotShortcutAvailability,
DesktopSnapShotState,
DesktopSnapShotSetupAction,
SnapShotShortcut,
DesktopCaptureConfigRequest,
DesktopCaptureConfigPreview,
DesktopCaptureConfigApplied,
} from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import type * as Electron from "electron";
import * as ElectronWindow from "../../electron/ElectronWindow.ts";
import * as ElectronDialog from "../../electron/ElectronDialog.ts";
import * as DesktopSnapShot from "../../snapShot/DesktopSnapShot.ts";
import * as IpcChannels from "../channels.ts";
import * as DesktopIpc from "../DesktopIpc.ts";
class SnapShotIpcUnauthorizedSenderError extends Schema.TaggedError<SnapShotIpcUnauthorizedSenderError>()(
"SnapShotIpcUnauthorizedSenderError",
{},
) {
override get message(): string {
return "Snapshot request was rejected.";
}
}
const ensureTrustedSnapShotSender = Effect.fn("desktop.ipc.snapShot.ensureTrustedSender")(
function* (event: DesktopIpc.DesktopIpcInvokeEvent | undefined) {
const main = yield* (yield* ElectronWindow.ElectronWindow).main;
if (
event === undefined ||
Option.isNone(main) ||
main.value.webContents.id !== event.sender.id
) {
return yield* new SnapShotIpcUnauthorizedSenderError();
}
return main.value;
},
);
export function snapShotScreenFrame(
viewportFrame: DesktopSnapShotAnimationDestination["viewportFrame"],
contentBounds: Electron.Rectangle,
zoomFactor: number,
): Electron.Rectangle {
return {
x: contentBounds.x + viewportFrame.x * zoomFactor,
y: contentBounds.y + viewportFrame.y * zoomFactor,
width: viewportFrame.width * zoomFactor,
height: viewportFrame.height * zoomFactor,
};
}
export function snapShotRelativeFrame(
frame: DesktopSnapShotAnimationDestination["viewportFrame"],
bounds: Electron.Rectangle,
zoom: number,
): Electron.Rectangle | undefined {
if (bounds.width <= 0 || bounds.height <= 0) return undefined;
return {
x: (frame.x * zoom) / bounds.width,
y: (frame.y * zoom) / bounds.height,
width: (frame.width * zoom) / bounds.width,
height: (frame.height * zoom) / bounds.height,
};
}
export const getSnapShotState = DesktopIpc.makeIpcMethod({
channel: IpcChannels.GET_SNAP_SHOT_STATE_CHANNEL,
payload: Schema.Void,
result: DesktopSnapShotState,
handler: Effect.fn("desktop.ipc.snapShot.getState")(function* () {
return yield* (yield* DesktopSnapShot.DesktopSnapShot).state;
}),
});
export const requestSnapShotPermissions = DesktopIpc.makeIpcMethod({
channel: IpcChannels.REQUEST_SNAP_SHOT_PERMISSIONS_CHANNEL,
payload: Schema.Boolean,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.snapShot.requestPermissions")(
function* (includeAccessibility, event) {
yield* ensureTrustedSnapShotSender(event);
yield* (yield* DesktopSnapShot.DesktopSnapShot).requestPermissions(includeAccessibility);
},
),
});
export const setupSnapShot = DesktopIpc.makeIpcMethod({
channel: IpcChannels.SETUP_SNAP_SHOT_CHANNEL,
payload: DesktopSnapShotSetupAction,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.snapShot.setup")(function* (action, event) {
yield* ensureTrustedSnapShotSender(event);
yield* (yield* DesktopSnapShot.DesktopSnapShot).setup(action);
}),
});
export const previewSnapShotConfig = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_SNAP_SHOT_CONFIG_CHANNEL,
payload: DesktopCaptureConfigRequest,
result: Schema.NullOr(DesktopCaptureConfigPreview),
handler: Effect.fn("desktop.ipc.snapShot.previewConfig")(function* (request, event) {
const window = yield* ensureTrustedSnapShotSender(event);
const capture = yield* DesktopSnapShot.DesktopSnapShot;
let selectedPath: string | undefined;
if (request.chooseFile) {
const state = yield* capture.state;
const paths = yield* (yield* ElectronDialog.ElectronDialog).pickFiles({
owner: Option.some(window),
defaultPath: Option.fromUndefinedOr(state.shortcutConfigPath),
filters: [
{
name: "Desktop config",
extensions: state.linuxBackend === "niri" ? ["kdl"] : ["conf", "lua"],
},
],
multiple: false,
});
selectedPath = paths[0];
if (!selectedPath) return null;
}
return yield* capture.previewConfig(request, selectedPath);
}),
});
export const applySnapShotConfig = DesktopIpc.makeIpcMethod({
channel: IpcChannels.APPLY_SNAP_SHOT_CONFIG_CHANNEL,
payload: DesktopSnapShotId,
result: DesktopCaptureConfigApplied,
handler: Effect.fn("desktop.ipc.snapShot.applyConfig")(function* (id, event) {
yield* ensureTrustedSnapShotSender(event);
return yield* (yield* DesktopSnapShot.DesktopSnapShot).applyConfig(id);
}),
});
export const checkSnapShotShortcut = DesktopIpc.makeIpcMethod({
channel: IpcChannels.CHECK_SNAP_SHOT_SHORTCUT_CHANNEL,
payload: SnapShotShortcut,
result: DesktopSnapShotShortcutAvailability,
handler: Effect.fn("desktop.ipc.snapShot.checkShortcut")(function* (shortcut, event) {
yield* ensureTrustedSnapShotSender(event);
return yield* (yield* DesktopSnapShot.DesktopSnapShot).checkShortcut(shortcut);
}),
});
export const setSnapShotShortcutSuppressed = DesktopIpc.makeIpcMethod({
channel: IpcChannels.SET_SNAP_SHOT_SHORTCUT_SUPPRESSED_CHANNEL,
payload: Schema.Boolean,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.snapShot.setShortcutSuppressed")(function* (suppressed, event) {
yield* ensureTrustedSnapShotSender(event);
yield* (yield* DesktopSnapShot.DesktopSnapShot).setShortcutSuppressed(suppressed);
}),
});
export const listPendingSnapShots = DesktopIpc.makeIpcMethod({
channel: IpcChannels.LIST_PENDING_SNAP_SHOTS_CHANNEL,
payload: Schema.Void,
result: Schema.Array(DesktopPendingSnapShot),
handler: Effect.fn("desktop.ipc.snapShot.listPending")(function* (_, event) {
yield* ensureTrustedSnapShotSender(event);
return yield* (yield* DesktopSnapShot.DesktopSnapShot).listPending;
}),
});
export const readSnapShot = DesktopIpc.makeIpcMethod({
channel: IpcChannels.READ_SNAP_SHOT_CHANNEL,
payload: DesktopSnapShotId,
result: DesktopSnapShotSchema,
handler: Effect.fn("desktop.ipc.snapShot.read")(function* (id, event) {
yield* ensureTrustedSnapShotSender(event);
return yield* (yield* DesktopSnapShot.DesktopSnapShot).read(id);
}),
});
export const setSnapShotAnimationDestination = DesktopIpc.makeIpcMethod({
channel: IpcChannels.SET_SNAP_SHOT_ANIMATION_DESTINATION_CHANNEL,
payload: DesktopSnapShotAnimationDestination,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.snapShot.setAnimationDestination")(
function* (destination, event) {
const window = yield* ensureTrustedSnapShotSender(event);
if (
destination.viewportFrame.width <= 0 ||
destination.viewportFrame.height <= 0 ||
destination.borderWidth < 0 ||
destination.cornerRadius < 0
) {
return;
}
yield* (yield* DesktopSnapShot.DesktopSnapShot).setAnimationDestination(destination.id, {
relativeFrame: snapShotRelativeFrame(
destination.viewportFrame,
window.getContentBounds(),
window.webContents.getZoomFactor(),
),
frame: snapShotScreenFrame(
destination.viewportFrame,
window.getContentBounds(),
window.webContents.getZoomFactor(),
),
backgroundColor: destination.backgroundColor,
borderColor: destination.borderColor,
borderWidth: destination.borderWidth * window.webContents.getZoomFactor(),
cornerRadius: destination.cornerRadius * window.webContents.getZoomFactor(),
scaleFactor: window.webContents.getZoomFactor(),
details: destination.details,
});
},
),
});
export const dismissSnapShotAnimation = DesktopIpc.makeIpcMethod({
channel: IpcChannels.DISMISS_SNAP_SHOT_ANIMATION_CHANNEL,
payload: DesktopSnapShotId,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.snapShot.dismissAnimation")(function* (id, event) {
yield* ensureTrustedSnapShotSender(event);
yield* (yield* DesktopSnapShot.DesktopSnapShot).dismissAnimation(id);
}),
});
export const acknowledgeSnapShot = DesktopIpc.makeIpcMethod({
channel: IpcChannels.ACKNOWLEDGE_SNAP_SHOT_CHANNEL,
payload: DesktopSnapShotId,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.snapShot.acknowledge")(function* (id, event) {
yield* ensureTrustedSnapShotSender(event);
yield* (yield* DesktopSnapShot.DesktopSnapShot).acknowledge(id);
}),
});