Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1efb945ab1 |
Binary file not shown.
@@ -0,0 +1 @@
|
||||
039BE7EB2B5BC2EE61B7B8557B40A193B1B37514B06A6FE3E1CD3994B02A2C35 Openscreen-1.4.11.msi
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
65D04238E0DCA946CCDCBC610CD4B51070EA4708C748BA1321EE0B966F7808E1 Openscreen-Setup-1.4.11.exe
|
||||
@@ -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>
|
||||
|
||||
<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
@@ -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",
|
||||
|
||||
Vendored
+8
@@ -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>>;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>;
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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'
|
||||
Generated
+85
-10
@@ -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
@@ -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
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user