1 Commits

Author SHA1 Message Date
huanld 1efb945ab1 Publish OpenScreen 1.4.11 update assets 2026-06-05 16:32:22 +07:00
15 changed files with 424 additions and 204 deletions
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
039BE7EB2B5BC2EE61B7B8557B40A193B1B37514B06A6FE3E1CD3994B02A2C35 Openscreen-1.4.11.msi
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
65D04238E0DCA946CCDCBC610CD4B51070EA4708C748BA1321EE0B966F7808E1 Openscreen-Setup-1.4.11.exe
+9 -187
View File
@@ -1,190 +1,12 @@
> [!WARNING]
> This started as a side project that took off — it's not production grade and you'll hit bugs, but hopefully it covers what you need.
# OpenScreen 1.4.11 release assets
<p align="center">
<img src="public/openscreen.png" alt="OpenScreen Logo" width="64" />
<br />
<br />
<a href="https://trendshift.io/repositories/17427" target="_blank"><img src="https://trendshift.io/api/badge/repositories/17427" alt="siddharthvaddem%2Fopenscreen | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<br />
<br />
<a href="https://deepwiki.com/siddharthvaddem/openscreen">
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" />
</a>
&nbsp;
<a href="https://discord.gg/yAQQhRaEeg">
<img src="https://dcbadge.limes.pink/api/server/https://discord.gg/yAQQhRaEeg?style=flat" alt="Join Discord" />
</a>
</p>
This branch stores public installer assets for OpenScreen updates.
# <p align="center">OpenScreen</p>
- Openscreen-Setup-1.4.11.exe: NSIS installer used by electron-updater.
- latest.yml: auto-update feed for Windows.
- Openscreen-1.4.11.msi: manual MSI installer.
<p align="center"><strong>OpenScreen is your free, open-source alternative to Screen Studio (sort of).</strong></p>
If you don't want to pay $29/month for Screen Studio but want a much simpler version that does what most people seem to need - quick, polished product demos and walkthroughs you'd post on X, Reddit. OpenScreen does not offer all Screen Studio features, but covers the basics well!
Screen Studio is an awesome product and this is definitely not a 1:1 clone. OpenScreen is a much simpler take, just the basics for folks who want control and don't want to pay. If you need all the fancy features, your best bet is to support Screen Studio (they really do a great job, haha). But if you just want something free (no gotchas) and open, this project does the job!
**100% free** for both **personal** and **commercial** use. Use it, modify it, distribute it — just be cool 😁 and shout out the project if you feel like it.
<p align="center">
<img src="public/preview3.png" alt="OpenScreen App Preview 3" style="height: 0.2467; margin-right: 12px;" />
<img src="public/preview4.png" alt="OpenScreen App Preview 4" style="height: 0.1678; margin-right: 12px;" />
</p>
## Core Features
- Record a specific window, region, or your whole screen.
- Record microphone and system audio.
- Webcam overlay with picture-in-picture, drag-to-position, and shape options.
- Auto or manual zooms with adjustable depth, duration, easing, and pixel-precise position.
- Wallpapers, solid colors, gradients, or a custom background.
- Motion blur for smoother pan and zoom transitions.
- Crop, trim, and per-segment speed control on the timeline.
- Blur effects to hide sensitive parts of the screen.
- Cursor and click highlighting.
- Text, arrow, and image annotations.
- Save and reopen projects without re-recording.
- Export to MP4 or GIF in multiple aspect ratios and resolutions.
- Translated into Arabic, English, Spanish, French, Japanese, Korean, Russian, Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese.
## Installation
Download the latest installer for your platform from the [GitHub Releases](https://github.com/siddharthvaddem/openscreen/releases) page.
### macOS
The easiest way to install on macOS is via [Homebrew](https://brew.sh):
```bash
brew install --cask siddharthvaddem/openscreen/openscreen
```
Brew automatically picks the right build for Apple Silicon or Intel, and verifies the download against a notarized signature so Gatekeeper won't block it.
To update later: `brew upgrade --cask openscreen`
To uninstall: `brew uninstall --cask openscreen` (add `--zap` to also remove app data)
#### Manual install (if you prefer)
If you'd rather grab the `.dmg` directly from the [Releases page](https://github.com/siddharthvaddem/openscreen/releases) and encounter Gatekeeper blocking the app, you can bypass it by running the following command in your terminal after installation:
```bash
xattr -rd com.apple.quarantine /Applications/Openscreen.app
```
Note: Give your terminal Full Disk Access in **System Settings > Privacy & Security** to grant you access and then run the above command.
After running this command, proceed to **System Preferences > Security & Privacy** to grant the necessary permissions for "screen recording" and "accessibility". Once permissions are granted, you can launch the app.
### Windows
Install via [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/):
```bash
winget install SiddharthVaddem.OpenScreen
```
To update later: `winget upgrade SiddharthVaddem.OpenScreen`
To uninstall: `winget uninstall SiddharthVaddem.OpenScreen`
If you'd rather grab the `.exe` installer directly, download it from the [Releases page](https://github.com/siddharthvaddem/openscreen/releases).
### Linux
Three packages are published to the [Releases page](https://github.com/siddharthvaddem/openscreen/releases) for each version. Pick the one that matches your distro:
**Debian / Ubuntu / Pop!_OS (`.deb`)**
```bash
sudo apt install ./Openscreen-Linux-latest.deb
```
**Arch / Manjaro (`.pacman`)**
```bash
sudo pacman -U Openscreen-Linux-latest.pacman
```
**Any distro (`.AppImage`)**
```bash
chmod +x Openscreen-Linux-*.AppImage
./Openscreen-Linux-*.AppImage
```
**NixOS / Nix (flake)**
Try without installing:
```bash
nix run github:siddharthvaddem/openscreen
```
Install into your user profile:
```bash
nix profile install github:siddharthvaddem/openscreen
```
For a NixOS system config (flake):
```nix
{
inputs.openscreen.url = "github:siddharthvaddem/openscreen";
outputs = { nixpkgs, openscreen, ... }: {
nixosConfigurations.<host> = nixpkgs.lib.nixosSystem {
modules = [
openscreen.nixosModules.default
{ programs.openscreen.enable = true; }
];
};
};
}
```
For Home Manager, use `openscreen.homeManagerModules.default` with the same `programs.openscreen.enable = true;`.
You may need to grant screen recording permissions depending on your desktop environment.
**Sandbox error:** If the AppImage fails to launch with a "sandbox" error, run it with `--no-sandbox`:
```bash
./Openscreen-Linux-*.AppImage --no-sandbox
```
### Limitations
System audio capture relies on Electron's [desktopCapturer](https://www.electronjs.org/docs/latest/api/desktop-capturer) and has some platform-specific quirks:
- **macOS**: Requires macOS 13+. On macOS 14.2+ you'll be prompted to grant audio capture permission. macOS 12 and below does not support system audio (mic still works).
- **Windows**: Works out of the box.
- **Linux**: Needs PipeWire (default on Ubuntu 22.04+, Fedora 34+). Older PulseAudio-only setups may not support system audio (mic should still work).
## Built with
- Electron
- React
- TypeScript
- Vite
- PixiJS
- dnd-timeline
---
## Documentation
See the documentation here:
[OpenScreen Docs](https://deepwiki.com/siddharthvaddem/openscreen)
Refresh if outdated.
## Contributing
Contributions are welcome - please **include screenshots or a short video** for any UI change or new user-facing feature. If it touches what users see or do, show it. Skip only when it genuinely doesn't apply. PRs that don't follow this will be closed.
## Star History
<a href="https://www.star-history.com/?repos=siddharthvaddem%2Fopenscreen&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=siddharthvaddem/openscreen&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=siddharthvaddem/openscreen&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=siddharthvaddem/openscreen&type=date&legend=top-left" />
</picture>
</a>
## License
This project is licensed under the [MIT License](./LICENSE). By using this software, you agree that the authors are not liable for any issues, damages, or claims arising from its use.
EXE SHA256: 65D04238E0DCA946CCDCBC610CD4B51070EA4708C748BA1321EE0B966F7808E1 Openscreen-Setup-1.4.11.exe
MSI SHA256: 039BE7EB2B5BC2EE61B7B8557B40A193B1B37514B06A6FE3E1CD3994B02A2C35 Openscreen-1.4.11.msi
Signing: Azure Trusted Signing Private Trust
Source commit: ee69df92222ec53d73b34497e3488beaba25a8f4
+11 -5
View File
@@ -13,11 +13,17 @@
},
"npmRebuild": true,
"buildDependenciesFromSource": true,
"compression": "normal",
"directories": {
"output": "release/${version}"
},
"files": [
"compression": "normal",
"directories": {
"output": "release/${version}"
},
"publish": [
{
"provider": "generic",
"url": "https://gittea.softs.business/huanld/openscreen/raw/branch/release-assets%2Flatest"
}
],
"files": [
"dist",
"dist-electron",
"!*.png",
+8
View File
@@ -24,6 +24,14 @@ declare namespace NodeJS {
// Used in Renderer process, expose in `preload.ts`
interface Window {
electronAPI: {
updates: {
getStatus: () => Promise<import("../src/lib/updateStatus").UpdateStatus>;
check: () => Promise<import("../src/lib/updateStatus").UpdateCheckResult>;
install: () => Promise<import("../src/lib/updateStatus").UpdateCheckResult>;
onStatus: (
callback: (status: import("../src/lib/updateStatus").UpdateStatus) => void,
) => () => void;
};
invokeNativeBridge: <TData = unknown>(
request: import("../src/native/contracts").NativeBridgeRequest,
) => Promise<import("../src/native/contracts").NativeBridgeResponse<TData>>;
+2
View File
@@ -13,6 +13,7 @@ import {
} from "electron";
import { mainT, setMainLocale } from "./i18n";
import { getSelectedDesktopSource, registerIpcHandlers } from "./ipc/handlers";
import { initializeAutoUpdates } from "./updater";
import {
createCountdownOverlayWindow,
createEditorWindow,
@@ -515,6 +516,7 @@ app.whenReady().then(async () => {
createTray();
updateTrayMenu();
setupApplicationMenu();
initializeAutoUpdates();
// Ensure recordings directory exists
await ensureRecordingsDir();
+21
View File
@@ -26,6 +26,27 @@ const assetBaseUrl = assetBaseUrlArg ? assetBaseUrlArg.slice(ASSET_BASE_URL_ARG_
contextBridge.exposeInMainWorld("electronAPI", {
assetBaseUrl,
updates: {
getStatus: () => {
return ipcRenderer.invoke("updates:get-status");
},
check: () => {
return ipcRenderer.invoke("updates:check");
},
install: () => {
return ipcRenderer.invoke("updates:install");
},
onStatus: (callback: (status: import("../src/lib/updateStatus").UpdateStatus) => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
status: import("../src/lib/updateStatus").UpdateStatus,
) => {
callback(status);
};
ipcRenderer.on("updates:status", listener);
return () => ipcRenderer.removeListener("updates:status", listener);
},
},
invokeNativeBridge: <TData>(request: NativeBridgeRequest) => {
return ipcRenderer.invoke(NATIVE_BRIDGE_CHANNEL, request) as Promise<TData>;
},
+177
View File
@@ -0,0 +1,177 @@
import { app, BrowserWindow, ipcMain } from "electron";
import { autoUpdater, type ProgressInfo, type UpdateInfo } from "electron-updater";
import type { UpdateCheckResult, UpdateStatus } from "../src/lib/updateStatus";
const DEFAULT_UPDATE_FEED_URL =
"https://gittea.softs.business/huanld/openscreen/raw/branch/release-assets%2Flatest";
const AUTO_CHECK_DELAY_MS = 10_000;
const AUTO_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000;
let status: UpdateStatus = createStatus("idle");
let handlersRegistered = false;
let initialized = false;
let checkInFlight: Promise<UpdateCheckResult> | null = null;
function createStatus(
phase: UpdateStatus["phase"],
patch: Partial<UpdateStatus> = {},
): UpdateStatus {
return {
phase,
currentVersion: app.getVersion(),
updatedAt: new Date().toISOString(),
...patch,
};
}
function normalizeReleaseNotes(releaseNotes: UpdateInfo["releaseNotes"]): string | undefined {
if (typeof releaseNotes === "string") {
return releaseNotes;
}
if (Array.isArray(releaseNotes)) {
return releaseNotes
.map((note) => note.note)
.filter(Boolean)
.join("\n\n");
}
return undefined;
}
function updateStatus(next: UpdateStatus) {
status = next;
for (const window of BrowserWindow.getAllWindows()) {
if (!window.isDestroyed()) {
window.webContents.send("updates:status", status);
}
}
}
function statusFromInfo(phase: UpdateStatus["phase"], info: UpdateInfo): UpdateStatus {
return createStatus(phase, {
version: info.version,
releaseName: info.releaseName ?? undefined,
releaseNotes: normalizeReleaseNotes(info.releaseNotes),
});
}
async function checkForUpdates(): Promise<UpdateCheckResult> {
if (!initialized) {
updateStatus(
createStatus("unsupported", {
error: "Update service is not initialized.",
}),
);
return { success: false, status, error: status.error };
}
if (!app.isPackaged && process.env.OPENSCREEN_ALLOW_DEV_UPDATE_CHECK !== "1") {
updateStatus(
createStatus("unsupported", {
error: "Update checks only run in packaged builds.",
}),
);
return { success: false, status, error: status.error };
}
if (checkInFlight) {
return checkInFlight;
}
updateStatus(createStatus("checking"));
checkInFlight = autoUpdater
.checkForUpdates()
.then(() => ({ success: true, status }))
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
updateStatus(createStatus("error", { error: message }));
return { success: false, status, error: message };
})
.finally(() => {
checkInFlight = null;
});
return checkInFlight;
}
function registerUpdateIpcHandlers() {
if (handlersRegistered) {
return;
}
handlersRegistered = true;
ipcMain.handle("updates:get-status", () => status);
ipcMain.handle("updates:check", () => checkForUpdates());
ipcMain.handle("updates:install", () => {
if (status.phase !== "downloaded") {
return {
success: false,
status,
error: "No downloaded update is ready to install.",
};
}
setImmediate(() => autoUpdater.quitAndInstall(false, true));
return { success: true, status };
});
}
export function initializeAutoUpdates() {
registerUpdateIpcHandlers();
if (initialized) {
return;
}
initialized = true;
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.logger = console;
const feedUrl = process.env.OPENSCREEN_UPDATE_FEED_URL?.trim() || DEFAULT_UPDATE_FEED_URL;
const updateToken = process.env.OPENSCREEN_UPDATE_TOKEN?.trim();
if (updateToken) {
autoUpdater.requestHeaders = {
Authorization: `token ${updateToken}`,
};
}
autoUpdater.setFeedURL({
provider: "generic",
url: feedUrl,
});
autoUpdater.on("checking-for-update", () => {
updateStatus(createStatus("checking"));
});
autoUpdater.on("update-available", (info) => {
updateStatus(statusFromInfo("available", info));
});
autoUpdater.on("update-not-available", (info) => {
updateStatus(statusFromInfo("not-available", info));
});
autoUpdater.on("download-progress", (progress: ProgressInfo) => {
updateStatus(
createStatus("downloading", {
version: status.version,
releaseName: status.releaseName,
releaseNotes: status.releaseNotes,
percent: progress.percent,
bytesPerSecond: progress.bytesPerSecond,
transferred: progress.transferred,
total: progress.total,
}),
);
});
autoUpdater.on("update-downloaded", (info) => {
updateStatus(statusFromInfo("downloaded", info));
});
autoUpdater.on("error", (error) => {
const message = error instanceof Error ? error.message : String(error);
updateStatus(createStatus("error", { error: message }));
});
setTimeout(() => {
void checkForUpdates();
}, AUTO_CHECK_DELAY_MS);
setInterval(() => {
void checkForUpdates();
}, AUTO_CHECK_INTERVAL_MS).unref();
}
+8
View File
@@ -0,0 +1,8 @@
version: 1.4.11
files:
- url: Openscreen-Setup-1.4.11.exe
sha512: t1/5vpdCV/2EbG5Wz010S/XlJ7UFILxGJ9+t63XdvB9xisgROLS2pRLRC1QAGOetRLCNa2pyv6Q1Kbz9R8uteQ==
size: 417655584
path: Openscreen-Setup-1.4.11.exe
sha512: t1/5vpdCV/2EbG5Wz010S/XlJ7UFILxGJ9+t63XdvB9xisgROLS2pRLRC1QAGOetRLCNa2pyv6Q1Kbz9R8uteQ==
releaseDate: '2026-06-05T09:30:41.101Z'
+85 -10
View File
@@ -1,12 +1,12 @@
{
"name": "openscreen",
"version": "1.4.10",
"version": "1.4.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openscreen",
"version": "1.4.10",
"version": "1.4.11",
"dependencies": {
"@fix-webm-duration/fix": "^1.0.1",
"@pixi/filter-drop-shadow": "^5.2.0",
@@ -29,6 +29,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dnd-timeline": "^2.4.0",
"electron-updater": "^6.8.3",
"emoji-picker-react": "^4.18.0",
"fix-webm-duration": "^1.0.6",
"gif.js": "^0.2.0",
@@ -4625,7 +4626,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
@@ -4959,7 +4959,6 @@
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
@@ -5504,7 +5503,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -6015,6 +6013,69 @@
"dev": true,
"license": "ISC"
},
"node_modules/electron-updater": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
"integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
"license": "MIT",
"dependencies": {
"builder-util-runtime": "9.5.1",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "~7.7.3",
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron-updater/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/electron-updater/node_modules/jsonfile": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-updater/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/electron-updater/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/electron-winstaller": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
@@ -6874,7 +6935,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/gsap": {
@@ -7287,7 +7347,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -7419,7 +7478,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/lilconfig": {
@@ -7586,6 +7644,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
@@ -7991,7 +8062,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/mz": {
@@ -9394,7 +9464,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
@@ -10099,6 +10168,12 @@
"node": ">=12"
}
},
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
"license": "MIT"
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "openscreen",
"private": true,
"version": "1.4.10",
"version": "1.4.11",
"type": "module",
"packageManager": "npm@10.9.4",
"engines": {
@@ -70,6 +70,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dnd-timeline": "^2.4.0",
"electron-updater": "^6.8.3",
"emoji-picker-react": "^4.18.0",
"fix-webm-duration": "^1.0.6",
"gif.js": "^0.2.0",
+70 -1
View File
@@ -1,4 +1,5 @@
import { lazy, Suspense, useEffect, useState } from "react";
import { lazy, Suspense, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { CountdownOverlay } from "./components/launch/CountdownOverlay.tsx";
import { LaunchWindow } from "./components/launch/LaunchWindow";
import { SourceSelector } from "./components/launch/SourceSelector";
@@ -6,6 +7,7 @@ import { Toaster } from "./components/ui/sonner";
import { TooltipProvider } from "./components/ui/tooltip";
import { ShortcutsProvider } from "./contexts/ShortcutsContext";
import { loadAllCustomFonts } from "./lib/customFonts";
import type { UpdateStatus } from "./lib/updateStatus";
const VideoEditor = lazy(() => import("./components/video-editor/VideoEditor"));
const ShortcutsConfigDialog = lazy(() =>
@@ -79,11 +81,78 @@ export default function App() {
return (
<TooltipProvider>
{content}
<UpdateNotifier
enabled={
hasElectronBridge &&
windowType !== "hud-overlay" &&
windowType !== "source-selector" &&
windowType !== "countdown-overlay"
}
/>
<Toaster theme="dark" className="pointer-events-auto" />
</TooltipProvider>
);
}
function UpdateNotifier({ enabled }: { enabled: boolean }) {
const lastPhaseRef = useRef<UpdateStatus["phase"]>("idle");
useEffect(() => {
if (!enabled || !window.electronAPI?.updates) {
return;
}
const applyStatus = (status: UpdateStatus) => {
const version = status.version ? ` ${status.version}` : "";
if (status.phase === "available") {
toast.loading(`Downloading OpenScreen${version} update...`, {
id: "openscreen-update",
duration: Number.POSITIVE_INFINITY,
});
} else if (status.phase === "downloading") {
const percent = typeof status.percent === "number" ? ` ${Math.round(status.percent)}%` : "";
toast.loading(`Downloading OpenScreen${version} update${percent}...`, {
id: "openscreen-update",
duration: Number.POSITIVE_INFINITY,
});
} else if (status.phase === "downloaded") {
toast.success(`OpenScreen${version} is ready to install.`, {
id: "openscreen-update",
duration: Number.POSITIVE_INFINITY,
action: {
label: "Restart",
onClick: () => {
void window.electronAPI.updates.install();
},
},
});
} else if (
status.phase === "error" &&
(lastPhaseRef.current === "available" ||
lastPhaseRef.current === "downloading" ||
lastPhaseRef.current === "downloaded")
) {
toast.error(status.error || "OpenScreen update failed.", {
id: "openscreen-update",
});
} else if (status.phase === "not-available" || status.phase === "unsupported") {
toast.dismiss("openscreen-update");
}
lastPhaseRef.current = status.phase;
};
const unsubscribe = window.electronAPI.updates.onStatus(applyStatus);
void window.electronAPI.updates
.getStatus()
.then(applyStatus)
.catch(() => undefined);
return unsubscribe;
}, [enabled]);
return null;
}
function BrowserDevFallback() {
return (
<div className="flex h-screen w-screen items-center justify-center bg-[#08090b] px-6 text-slate-100">
+29
View File
@@ -0,0 +1,29 @@
export type UpdateStatusPhase =
| "idle"
| "checking"
| "available"
| "not-available"
| "downloading"
| "downloaded"
| "error"
| "unsupported";
export interface UpdateStatus {
phase: UpdateStatusPhase;
currentVersion: string;
version?: string;
releaseName?: string;
releaseNotes?: string;
percent?: number;
bytesPerSecond?: number;
transferred?: number;
total?: number;
error?: string;
updatedAt: string;
}
export interface UpdateCheckResult {
success: boolean;
status: UpdateStatus;
error?: string;
}