Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions electron/cli/cliMain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// `openscreen mcp` sends the GUI's anonymous usage ping: an install driven only by an agent
// counts as active. runCli runs for real here; only Electron and the heavy services around
// the MCP server are stubbed.

import { mkdtempSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { sendUsagePing, setUsagePingEnabled } from "../usage-ping";
import { runCli } from "./cliMain";

const mocks = vi.hoisted(() => ({ userData: "", serve: vi.fn(async () => undefined) }));

vi.mock("electron", () => ({
app: {
getPath: () => mocks.userData,
getVersion: () => "2.1.0",
exit: vi.fn(),
on: vi.fn(),
whenReady: () => Promise.resolve(),
commandLine: { appendSwitch: vi.fn() },
},
BrowserWindow: class {},
ipcMain: { handle: vi.fn() },
Menu: { setApplicationMenu: vi.fn(), buildFromTemplate: vi.fn() },
session: {
defaultSession: {
setPermissionCheckHandler: vi.fn(),
setPermissionRequestHandler: vi.fn(),
setDisplayMediaRequestHandler: vi.fn(),
},
},
systemPreferences: {},
}));
vi.mock("../install-channel", () => ({ getInstallChannel: () => "nsis" }));
vi.mock("../ipc/handlers", () => ({
getSelectedDesktopSource: () => null,
registerIpcHandlers: () => ({ agentServices: {} }),
}));
vi.mock("../mcp/hidden-video", () => ({ openVideoInHiddenWindow: vi.fn() }));
vi.mock("../mcp/stdio-server", () => ({ serveMcpOverStdio: mocks.serve }));
vi.mock("../stt", () => ({ registerSttIpc: vi.fn(), shutdownStt: vi.fn() }));
vi.mock("../windows", () => ({ createRunnerWindow: vi.fn() }));
vi.mock("./runnerHost", () => ({ createRunnerHost: () => ({}) }));
vi.mock("./sessionData", () => ({ isolateCliSessionData: vi.fn() }));

const fetchMock = vi.fn(async (_url: string, _init: RequestInit) => ({ ok: true }));
const savedConsole = { ...console };
let stdoutWrite: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
mocks.userData = mkdtempSync(path.join(os.tmpdir(), "cli-usage-ping-"));
mocks.serve.mockClear();
fetchMock.mockClear();
vi.stubGlobal("fetch", fetchMock);
vi.stubEnv("DO_NOT_TRACK", "");
vi.stubEnv("CI", "");
// The darwin branch touches the dock and menu, which this test is not about.
vi.spyOn(process, "platform", "get").mockReturnValue("linux");
// runCli's fatal-error handlers would outlive the test and exit the worker.
vi.spyOn(process, "on").mockReturnValue(process);
stdoutWrite = vi.spyOn(process.stdout, "write");
});

afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
vi.restoreAllMocks();
Object.assign(console, savedConsole);
rmSync(mocks.userData, { recursive: true, force: true });
});

/** Runs `openscreen mcp` up to the point its server is listening and the ping went out. */
async function runMcp() {
runCli({ kind: "mcp" });
await vi.waitFor(() => expect(mocks.serve).toHaveBeenCalled());
await new Promise((resolve) => setTimeout(resolve, 0));
}

describe("openscreen mcp usage ping", () => {
it("pings once, as the mcp surface, and writes nothing to stdout", async () => {
await runMcp();
expect(fetchMock).toHaveBeenCalledTimes(1);
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
expect(body.properties).toMatchObject({
version: "2.1.0",
channel: "nsis",
surface: "mcp",
});
expect(stdoutWrite).not.toHaveBeenCalled();
});

it("honours the tray toggle and DO_NOT_TRACK", async () => {
setUsagePingEnabled(mocks.userData, false);
await runMcp();
setUsagePingEnabled(mocks.userData, true);
vi.stubEnv("DO_NOT_TRACK", "1");
await runMcp();
expect(fetchMock).not.toHaveBeenCalled();
});

it("counts a day the app already counted only once", async () => {
const facts = { version: "2.1.0", os: "linux", arch: "x64", channel: "nsis" } as const;
await sendUsagePing(mocks.userData, { ...facts, surface: "app" });
fetchMock.mockClear();
await runMcp();
expect(fetchMock).not.toHaveBeenCalled();
});
});
15 changes: 15 additions & 0 deletions electron/cli/cliMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { app, BrowserWindow, ipcMain, Menu, session, systemPreferences } from "e
import type { CliProgressEvent, CliSourcesResult } from "../../src/lib/cliContracts";
import { renameWithRetry } from "../atomic-rename";
import { isDiagnosticModeEnabled } from "../diagnostics/main-log-buffer";
import { getInstallChannel } from "../install-channel";
import { getSelectedDesktopSource, registerIpcHandlers } from "../ipc/handlers";
import { openVideoInHiddenWindow } from "../mcp/hidden-video";
import { serveMcpOverStdio } from "../mcp/stdio-server";
import { registerSttIpc, shutdownStt } from "../stt";
import { sendUsagePing, USAGE_PING_INTERVAL_MS } from "../usage-ping";
import { createRunnerWindow } from "../windows";
import { CLI_USAGE, type CliCommand } from "./args";
import { openGuiFromCli } from "./openGui";
Expand Down Expand Up @@ -409,6 +411,19 @@ export function runCli(command: CliCommand): void {
onClose: () => void shutdownStt().finally(() => app.exit(0)),
});
milestone("mcp server listening on stdio");
// An install driven only by an agent is an active install too. Same ping, state
// file and opt-outs as the GUI's, and it writes nothing: stdout is the protocol.
// The other commands stay silent: they are scripts and CI more often than people.
const pingUsage = () =>
void sendUsagePing(userDataDir, {
version: app.getVersion(),
os: process.platform,
arch: process.arch,
channel: getInstallChannel(),
surface: "mcp",
});
pingUsage();
setInterval(pingUsage, USAGE_PING_INTERVAL_MS).unref();
return;
}

Expand Down
1 change: 1 addition & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ function runUsagePing() {
os: process.platform,
arch: process.arch,
channel: getInstallChannel(),
surface: "app",
});
}

Expand Down
9 changes: 8 additions & 1 deletion electron/usage-ping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ function tmp(): string {
}

const fresh: UsagePingState = { enabled: true, day: null, week: null, month: null };
const facts: UsagePingFacts = { version: "2.0.0", os: "win32", arch: "x64", channel: "store" };
const facts: UsagePingFacts = {
version: "2.0.0",
os: "win32",
arch: "x64",
channel: "store",
surface: "app",
};
// Wednesday 14 October 2026.
const wednesday = new Date("2026-10-14T09:00:00Z");

Expand Down Expand Up @@ -96,6 +102,7 @@ describe("sendUsagePing", () => {
os: "win32",
arch: "x64",
channel: "store",
surface: "app",
first_this_week: true,
first_this_month: true,
});
Expand Down
4 changes: 4 additions & 0 deletions electron/usage-ping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export interface UsagePingFacts {
os: NodeJS.Platform;
arch: string;
channel: InstallChannel;
/** What the install was used through: the app's window, or `openscreen mcp` for an agent.
* Both share one state file, so a day counts once, under whichever pinged first. */
surface: "app" | "mcp";
}

const DEFAULT_STATE: UsagePingState = { enabled: true, day: null, week: null, month: null };
Expand Down Expand Up @@ -139,6 +142,7 @@ export async function sendUsagePing(
os: facts.os,
arch: facts.arch,
channel: facts.channel,
surface: facts.surface,
first_this_week: plan.firstThisWeek,
first_this_month: plan.firstThisMonth,
},
Expand Down
2 changes: 1 addition & 1 deletion website/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OpenScreen is a free, MIT-licensed screen recorder and video editor for Windows,
- **github.com, once per language.** On a machine with a GPU, the first English or French transcription also downloads the model that re-times each word: about 110 MB for English, 350 MB for French.
- **github.com and api.github.com.** Builds that update themselves check for a new release every 24 hours, and when you ask. By default they only tell you one is available.
- **fonts.googleapis.com, only for a font added in 1.x.** A custom annotation font added in an earlier version is still fetched at launch, though 2.x no longer draws it.
- **PostHog, once a day.** An anonymous count of active installs, sent to PostHog's EU servers. It holds the app version, the operating system, the processor architecture, how the app was installed, and whether it is the first count of the week and of the month. That is all: no installation identifier (each count gets a new random ID), no IP address kept, nothing about your recordings. Untick **Send Anonymous Usage Count** in the tray menu to turn it off, or set `DO_NOT_TRACK=1`.
- **PostHog, once a day.** An anonymous count of active installs, sent to PostHog's EU servers. It holds the app version, the operating system, the processor architecture, how the app was installed, whether it was used directly or through its MCP server by an AI agent, and whether it is the first count of the week and of the month. That is all: no installation identifier (each count gets a new random ID), no IP address kept, nothing about your recordings. Untick **Send Anonymous Usage Count** in the tray menu to turn it off, or set `DO_NOT_TRACK=1`.
- **Your AI provider, only if you connect one.** Chat editing sends your messages and the project data it reads, such as the timeline and transcript. Caption translation sends the caption text. Both stay off until you connect a provider. See [AI editing](./ai-editing.md).

## Does OpenScreen collect analytics or crash reports?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OpenScreen ist ein kostenloser Bildschirmrekorder und Videoeditor unter MIT-Lize
- **github.com, einmal pro Sprache.** Auf einem Rechner mit GPU lädt die erste englische oder französische Transkription zusätzlich das Modell herunter, das jedes Wort neu zeitlich ausrichtet: etwa 110 MB für Englisch, 350 MB für Französisch.
- **github.com und api.github.com.** Builds, die sich selbst aktualisieren, suchen alle 24 Stunden und auf deine Anfrage nach einer neuen Version. Standardmäßig melden sie nur, dass eine verfügbar ist.
- **fonts.googleapis.com, nur für eine in 1.x hinzugefügte Schrift.** Eine eigene Schrift für Annotationen, die in einer früheren Version hinzugefügt wurde, wird beim Start weiterhin geladen, obwohl 2.x sie nicht mehr zeichnet.
- **PostHog, einmal täglich.** Eine anonyme Zählung aktiver Installationen, gesendet an die EU-Server von PostHog. Sie enthält die App-Version, das Betriebssystem, die Prozessorarchitektur, die Art der Installation und ob es die erste Zählung der Woche und des Monats ist. Mehr nicht: keine Installationskennung (jede Zählung erhält eine neue Zufalls-ID), keine gespeicherte IP-Adresse, nichts über deine Aufnahmen. Entferne im Menü des Symbols im Infobereich das Häkchen bei **Anonyme Nutzungszählung senden**, um sie abzuschalten, oder setze `DO_NOT_TRACK=1`.
- **PostHog, einmal täglich.** Eine anonyme Zählung aktiver Installationen, gesendet an die EU-Server von PostHog. Sie enthält die App-Version, das Betriebssystem, die Prozessorarchitektur, die Art der Installation, ob die App direkt oder über ihren MCP-Server von einem KI-Agenten genutzt wurde, und ob es die erste Zählung der Woche und des Monats ist. Mehr nicht: keine Installationskennung (jede Zählung erhält eine neue Zufalls-ID), keine gespeicherte IP-Adresse, nichts über deine Aufnahmen. Entferne im Menü des Symbols im Infobereich das Häkchen bei **Anonyme Nutzungszählung senden**, um sie abzuschalten, oder setze `DO_NOT_TRACK=1`.
- **Dein KI-Anbieter, nur wenn du einen verbindest.** Die Chat-Bearbeitung sendet deine Nachrichten und die Projektdaten, die sie liest, etwa Zeitleiste und Transkript. Die Untertitelübersetzung sendet den Untertiteltext. Beides bleibt aus, bis du einen Anbieter verbindest. Siehe [KI-Bearbeitung](./ai-editing.md).

## Sammelt OpenScreen Analysedaten oder Absturzberichte? {#does-openscreen-collect-analytics-or-crash-reports}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "Was die Desktop-App, die Website, der MCP-Server und das Agent-Plu

# Datenschutzerklärung

Zuletzt aktualisiert: 18. Oktober 2026.
Zuletzt aktualisiert: 19. Oktober 2026.

OpenScreen ist ein kostenloser Open-Source-Bildschirmrekorder und Videoeditor. Es hat kein Konto und keine eigenen Server: Deine Aufnahmen, Projekte und Transkripte werden auf deinem Computer gespeichert, und keine Funktion lädt eine Aufnahme hoch. Diese Seite listet alles auf, was deinen Rechner tatsächlich verlässt, von der Desktop-App, dieser Website, dem MCP-Server und dem Agent-Plugin, einschließlich der Projektdaten, die die KI-Funktionen an den Anbieter senden, den du wählst.

Expand All @@ -21,6 +21,7 @@ Höchstens einmal täglich meldet die App an PostHog, auf dessen EU-Servern, das

- die App-Version, das Betriebssystem und die Prozessorarchitektur;
- wie die App installiert wurde (Installer, Microsoft Store, AppImage, Flatpak und so weiter);
- ob die App direkt oder über ihren MCP-Server von einem KI-Agenten genutzt wurde;
- ob es die erste Zählung der Installation in der Woche und im Monat ist.

Sonst enthält sie nichts: keine Installationskennung (jede Zählung erhält eine neue Zufalls-ID), keinen Namen, keine Dateinamen, nichts über deine Aufnahmen. Die Standortbestimmung ist abgeschaltet, und das PostHog-Projekt ist so eingestellt, dass IP-Adressen verworfen werden. Der Zweck ist zu wissen, wie viele Menschen OpenScreen nutzen und auf welchen Systemen; das ist das berechtigte Interesse des Projekts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OpenScreen es un grabador de pantalla y editor de video gratis, con licencia MIT
- **github.com, una vez por idioma.** En un equipo con GPU, la primera transcripción en inglés o en francés también descarga el modelo que vuelve a sincronizar cada palabra: unos 110 MB para el inglés, 350 MB para el francés.
- **github.com y api.github.com.** Las compilaciones que se actualizan solas buscan una nueva versión cada 24 horas, y cuando tú lo pides. De forma predeterminada, solo te avisan de que hay una disponible.
- **fonts.googleapis.com, solo para una fuente añadida en 1.x.** Una fuente personalizada para anotaciones añadida en una versión anterior se sigue descargando al iniciar, aunque la 2.x ya no la dibuja.
- **PostHog, una vez al día.** Un recuento anónimo de instalaciones activas, enviado a los servidores de PostHog en la UE. Contiene la versión de la app, el sistema operativo, la arquitectura del procesador, cómo se instaló la app y si es el primer recuento de la semana y del mes. Nada más: ningún identificador de instalación (cada recuento recibe un ID aleatorio nuevo), ninguna dirección IP conservada, nada sobre tus grabaciones. Desmarca **Enviar recuento de uso anónimo** en el menú de la bandeja para desactivarlo, o define `DO_NOT_TRACK=1`.
- **PostHog, una vez al día.** Un recuento anónimo de instalaciones activas, enviado a los servidores de PostHog en la UE. Contiene la versión de la app, el sistema operativo, la arquitectura del procesador, cómo se instaló la app, si se usó directamente o a través de su servidor MCP por un agente de IA, y si es el primer recuento de la semana y del mes. Nada más: ningún identificador de instalación (cada recuento recibe un ID aleatorio nuevo), ninguna dirección IP conservada, nada sobre tus grabaciones. Desmarca **Enviar recuento de uso anónimo** en el menú de la bandeja para desactivarlo, o define `DO_NOT_TRACK=1`.
- **Tu proveedor de IA, solo si conectas uno.** La edición por chat envía tus mensajes y los datos del proyecto que lee, como la línea de tiempo y la transcripción. La traducción de subtítulos envía el texto de los subtítulos. Ambas permanecen desactivadas hasta que conectas un proveedor. Consulta [Edición con IA](./ai-editing.md).

## ¿OpenScreen recopila analíticas o informes de fallos? {#does-openscreen-collect-analytics-or-crash-reports}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "Qué envían fuera de tu equipo la app de escritorio, el sitio web

# Política de privacidad

Última actualización: 18 de octubre de 2026.
Última actualización: 19 de octubre de 2026.

OpenScreen es un grabador de pantalla y editor de vídeo gratuito y de código abierto. No tiene cuenta ni servidores propios: tus grabaciones, proyectos y transcripciones se guardan en tu equipo, y ninguna función sube una grabación. Esta página enumera todo lo que sí sale de tu equipo, desde la app de escritorio, este sitio web, el servidor MCP y el plugin para agentes, incluidos los datos del proyecto que las funciones de IA envían al proveedor que elijas.

Expand All @@ -21,6 +21,7 @@ Como mucho una vez al día, la app informa a PostHog, en sus servidores de la UE

- la versión de la app, el sistema operativo y la arquitectura del procesador;
- cómo se instaló la app (instalador, Microsoft Store, AppImage, Flatpak, etc.);
- si la app se usó directamente o a través de su servidor MCP por un agente de IA;
- si es el primer recuento de la instalación en la semana y en el mes.

No contiene nada más: ningún identificador de instalación (cada recuento recibe un ID aleatorio nuevo), ningún nombre, ningún nombre de archivo, nada sobre tus grabaciones. La geolocalización está desactivada, y el proyecto de PostHog está configurado para descartar las direcciones IP. La finalidad es saber cuántas personas usan OpenScreen y en qué sistemas, lo que constituye el interés legítimo del proyecto.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OpenScreen est un enregistreur d'écran et éditeur vidéo gratuit, sous licence
- **github.com, une fois par langue.** Sur une machine dotée d'un GPU, la première transcription en anglais ou en français télécharge aussi le modèle qui recale chaque mot dans le temps : environ 110 Mo pour l'anglais, 350 Mo pour le français.
- **github.com et api.github.com.** Les versions qui se mettent à jour d'elles-mêmes vérifient la présence d'une nouvelle version toutes les 24 heures, et quand vous le demandez. Par défaut, elles se contentent de vous signaler qu'une version est disponible.
- **fonts.googleapis.com, seulement pour une police ajoutée en 1.x.** Une police d'annotation personnalisée ajoutée dans une version précédente est encore téléchargée au lancement, bien que la 2.x ne l'affiche plus.
- **PostHog, une fois par jour.** Un comptage anonyme des installations actives, envoyé aux serveurs de PostHog dans l'UE. Il contient la version de l'application, le système d'exploitation, l'architecture du processeur, le mode d'installation de l'application, et s'il s'agit du premier comptage de la semaine et du mois. C'est tout : aucun identifiant d'installation (chaque comptage reçoit un nouvel identifiant aléatoire), aucune adresse IP conservée, rien sur vos enregistrements. Décochez **Envoyer le comptage d'utilisation anonyme** dans le menu de l'icône de la zone de notification pour le désactiver, ou définissez `DO_NOT_TRACK=1`.
- **PostHog, une fois par jour.** Un comptage anonyme des installations actives, envoyé aux serveurs de PostHog dans l'UE. Il contient la version de l'application, le système d'exploitation, l'architecture du processeur, le mode d'installation de l'application, si elle a servi directement ou à un agent IA via son serveur MCP, et s'il s'agit du premier comptage de la semaine et du mois. C'est tout : aucun identifiant d'installation (chaque comptage reçoit un nouvel identifiant aléatoire), aucune adresse IP conservée, rien sur vos enregistrements. Décochez **Envoyer le comptage d'utilisation anonyme** dans le menu de l'icône de la zone de notification pour le désactiver, ou définissez `DO_NOT_TRACK=1`.
- **Votre fournisseur d'IA, seulement si vous en connectez un.** Le montage par chat envoie vos messages et les données du projet qu'il lit, comme la timeline et la transcription. La traduction des sous-titres envoie le texte des sous-titres. Les deux restent désactivés tant que vous n'avez pas connecté de fournisseur. Voir [Montage par IA](./ai-editing.md).

## OpenScreen collecte-t-il des statistiques d'usage ou des rapports de plantage ? {#does-openscreen-collect-analytics-or-crash-reports}
Expand Down
Loading
Loading