sunset windows support
This commit is contained in:
@@ -6,34 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install app dependencies
|
||||
run: npx electron-builder install-app-deps
|
||||
|
||||
- name: Build Windows app
|
||||
run: npm run build:win
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Windows build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-installer
|
||||
path: release/**/*.exe
|
||||
retention-days: 30
|
||||
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
|
||||
@@ -37,7 +37,6 @@ OpenScreen is 100% free for personal and commercial use. Use it, modify it, dist
|
||||
- Motion blur and exponential easing for smoother pan and zoom effects
|
||||
|
||||
**Note:**
|
||||
- OpenScreen is designed to support both Windows and macOS. We did not use any native APIs specific to one OS, so it should work on both. However, it hasn't been fully tested on Windows yet, if you run into issues, please let me know!
|
||||
- After you install the app, you'll need to grant it accessibility and screen recording permissions for it to work properly.
|
||||
|
||||
|
||||
|
||||
+2
-37
@@ -41,21 +41,13 @@ function createHudOverlayWindow() {
|
||||
return win;
|
||||
}
|
||||
function createEditorWindow() {
|
||||
const isMac = process.platform === "darwin";
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
// On macOS, use hiddenInset for native controls; on Windows, frameless
|
||||
...isMac ? {
|
||||
titleBarStyle: "hiddenInset",
|
||||
trafficLightPosition: { x: 12, y: 12 }
|
||||
} : {
|
||||
frame: false,
|
||||
icon: void 0
|
||||
// No app icon on Windows
|
||||
},
|
||||
titleBarStyle: "hiddenInset",
|
||||
trafficLightPosition: { x: 12, y: 12 },
|
||||
transparent: false,
|
||||
resizable: true,
|
||||
alwaysOnTop: false,
|
||||
@@ -369,28 +361,6 @@ function registerIpcHandlers(createEditorWindow2, createSourceSelectorWindow2, g
|
||||
};
|
||||
}
|
||||
});
|
||||
ipcMain.handle("minimize-window", () => {
|
||||
const mainWin = getMainWindow();
|
||||
if (mainWin) {
|
||||
mainWin.minimize();
|
||||
}
|
||||
});
|
||||
ipcMain.handle("maximize-window", () => {
|
||||
const mainWin = getMainWindow();
|
||||
if (mainWin) {
|
||||
if (mainWin.isMaximized()) {
|
||||
mainWin.unmaximize();
|
||||
} else {
|
||||
mainWin.maximize();
|
||||
}
|
||||
}
|
||||
});
|
||||
ipcMain.handle("close-window", () => {
|
||||
const mainWin = getMainWindow();
|
||||
if (mainWin) {
|
||||
mainWin.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const RECORDINGS_DIR = path.join(app.getPath("userData"), "recordings");
|
||||
@@ -469,11 +439,6 @@ function createSourceSelectorWindowWrapper() {
|
||||
return sourceSelectorWindow;
|
||||
}
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
cleanupMouseTracking();
|
||||
app.quit();
|
||||
mainWindow = null;
|
||||
}
|
||||
});
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
|
||||
@@ -47,14 +47,5 @@ electron.contextBridge.exposeInMainWorld("electronAPI", {
|
||||
},
|
||||
saveExportedVideo: (videoData, fileName) => {
|
||||
return electron.ipcRenderer.invoke("save-exported-video", videoData, fileName);
|
||||
},
|
||||
minimizeWindow: () => {
|
||||
return electron.ipcRenderer.invoke("minimize-window");
|
||||
},
|
||||
maximizeWindow: () => {
|
||||
return electron.ipcRenderer.invoke("maximize-window");
|
||||
},
|
||||
closeWindow: () => {
|
||||
return electron.ipcRenderer.invoke("close-window");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,25 +26,6 @@
|
||||
"icon": "icons/icons/mac/icon.icns",
|
||||
"artifactName": "${productName}-Mac-${version}-Installer.${ext}"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "icons/icons/win/icon.ico",
|
||||
"artifactName": "${productName}-Windows-${version}-Setup.${ext}",
|
||||
"requestedExecutionLevel": "asInvoker"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"deleteAppDataOnUninstall": false
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage"
|
||||
|
||||
Vendored
-3
@@ -38,9 +38,6 @@ interface Window {
|
||||
onStopRecordingFromTray: (callback: () => void) => () => void
|
||||
openExternalUrl: (url: string) => Promise<{ success: boolean; error?: string }>
|
||||
saveExportedVideo: (videoData: ArrayBuffer, fileName: string) => Promise<{ success: boolean; path?: string; message?: string }>
|
||||
minimizeWindow: () => Promise<void>
|
||||
maximizeWindow: () => Promise<void>
|
||||
closeWindow: () => Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,30 +178,4 @@ export function registerIpcHandlers(
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Window control handlers for frameless window
|
||||
ipcMain.handle('minimize-window', () => {
|
||||
const mainWin = getMainWindow()
|
||||
if (mainWin) {
|
||||
mainWin.minimize()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('maximize-window', () => {
|
||||
const mainWin = getMainWindow()
|
||||
if (mainWin) {
|
||||
if (mainWin.isMaximized()) {
|
||||
mainWin.unmaximize()
|
||||
} else {
|
||||
mainWin.maximize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('close-window', () => {
|
||||
const mainWin = getMainWindow()
|
||||
if (mainWin) {
|
||||
mainWin.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+2
-7
@@ -109,15 +109,10 @@ function createSourceSelectorWindowWrapper() {
|
||||
return sourceSelectorWindow
|
||||
}
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// On macOS, applications and their menu bar stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
cleanupMouseTracking()
|
||||
app.quit()
|
||||
mainWindow = null
|
||||
}
|
||||
// Keep app running (macOS behavior)
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
|
||||
@@ -49,13 +49,4 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
saveExportedVideo: (videoData: ArrayBuffer, fileName: string) => {
|
||||
return ipcRenderer.invoke('save-exported-video', videoData, fileName)
|
||||
},
|
||||
minimizeWindow: () => {
|
||||
return ipcRenderer.invoke('minimize-window')
|
||||
},
|
||||
maximizeWindow: () => {
|
||||
return ipcRenderer.invoke('maximize-window')
|
||||
},
|
||||
closeWindow: () => {
|
||||
return ipcRenderer.invoke('close-window')
|
||||
},
|
||||
})
|
||||
+2
-10
@@ -47,21 +47,13 @@ export function createHudOverlayWindow(): BrowserWindow {
|
||||
}
|
||||
|
||||
export function createEditorWindow(): BrowserWindow {
|
||||
const isMac = process.platform === 'darwin'
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
// On macOS, use hiddenInset for native controls; on Windows, frameless
|
||||
...(isMac ? {
|
||||
titleBarStyle: 'hiddenInset',
|
||||
trafficLightPosition: { x: 12, y: 12 },
|
||||
} : {
|
||||
frame: false,
|
||||
icon: undefined, // No app icon on Windows
|
||||
}),
|
||||
titleBarStyle: 'hiddenInset',
|
||||
trafficLightPosition: { x: 12, y: 12 },
|
||||
transparent: false,
|
||||
resizable: true,
|
||||
alwaysOnTop: false,
|
||||
|
||||
+2
-3
@@ -8,8 +8,7 @@
|
||||
"build": "tsc && vite build && electron-builder",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"build:mac": "tsc && vite build && electron-builder --mac",
|
||||
"build:win": "tsc && vite build && electron-builder --win --x64"
|
||||
"build:mac": "tsc && vite build && electron-builder --mac"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fix-webm-duration/fix": "^1.0.1",
|
||||
@@ -57,4 +56,4 @@
|
||||
"vite-plugin-electron-renderer": "^0.14.5"
|
||||
},
|
||||
"main": "dist-electron/main.js"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import PlaybackControls from "./PlaybackControls";
|
||||
import TimelineEditor from "./timeline/TimelineEditor";
|
||||
import { SettingsPanel } from "./SettingsPanel";
|
||||
import { ExportDialog } from "./ExportDialog";
|
||||
import { WindowControls } from "./WindowControls";
|
||||
|
||||
import type { Span } from "dnd-timeline";
|
||||
import {
|
||||
DEFAULT_ZOOM_DEPTH,
|
||||
@@ -275,7 +275,7 @@ export default function VideoEditor() {
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
>
|
||||
<div className="flex-1" />
|
||||
<WindowControls />
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4 gap-4 flex min-h-0 relative">
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
export function WindowControls() {
|
||||
// Only show custom controls on Windows
|
||||
const isWindows = navigator.userAgent.includes('Windows');
|
||||
|
||||
if (!isWindows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleMinimize = () => {
|
||||
window.electronAPI?.minimizeWindow?.();
|
||||
};
|
||||
|
||||
const handleMaximize = () => {
|
||||
window.electronAPI?.maximizeWindow?.();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
window.electronAPI?.closeWindow?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center" style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}>
|
||||
{/* Minimize - Horizontal Line */}
|
||||
<button
|
||||
onClick={handleMinimize}
|
||||
className="w-12 h-8 flex items-center justify-center hover:bg-white/10 transition-colors group"
|
||||
aria-label="Minimize"
|
||||
>
|
||||
<svg width="12" height="1" viewBox="0 0 12 1" className="text-gray-400 group-hover:text-white transition-colors">
|
||||
<rect width="12" height="1" fill="currentColor" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Maximize - Square */}
|
||||
<button
|
||||
onClick={handleMaximize}
|
||||
className="w-12 h-8 flex items-center justify-center hover:bg-white/10 transition-colors group"
|
||||
aria-label="Maximize"
|
||||
>
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" className="text-gray-400 group-hover:text-white transition-colors">
|
||||
<rect x="0" y="0" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="1" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Close - X */}
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="w-12 h-8 flex items-center justify-center hover:bg-[#e81123] transition-colors group"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" className="text-gray-400 group-hover:text-white transition-colors">
|
||||
<path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="currentColor" strokeWidth="1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -119,9 +119,7 @@ export class VideoExporter {
|
||||
|
||||
const canvas = this.renderer!.getCanvas();
|
||||
|
||||
// CRITICAL FIX for Windows: Explicitly specify colorSpace when creating VideoFrame from canvas
|
||||
// Without this, VideoFrame.colorSpace can be null on Windows, causing "Cannot read properties of null" error
|
||||
// Using BT.709 with sRGB transfer (IEC 61966-2-1) which is standard for HD video
|
||||
|
||||
// @ts-ignore - TypeScript definitions may not include all VideoFrameInit properties
|
||||
const exportFrame = new VideoFrame(canvas, {
|
||||
timestamp,
|
||||
|
||||
Reference in New Issue
Block a user