Skip to content

Commit e8d6fe3

Browse files
authored
Merge branch 'main' into adjust-zoom-speed
2 parents 8118a0c + db10f92 commit e8d6fe3

54 files changed

Lines changed: 2609 additions & 665 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
APP_NAME=Openscreen
2+
BUNDLE_ID=com.siddharthvaddem.openscreen
3+
4+
APPLE_ID=
5+
TEAM_ID=
6+
SIGN_IDENTITY="Developer ID Application: Samir Patil ()"
7+
CSC_NAME="Samir Patil ()"
8+
9+
NOTARY_PROFILE=OpenScreen-notary
10+
APPLE_APP_SPECIFIC_PASSWORD=

.github/workflows/build.yml

Lines changed: 164 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ name: Build Electron App
33

44
on:
55
workflow_dispatch:
6+
inputs:
7+
arch:
8+
description: 'Architecture to build'
9+
required: true
10+
default: 'both'
11+
type: choice
12+
options:
13+
- arm64
14+
- x64
15+
- both
616

717
jobs:
818
build-windows:
@@ -36,38 +46,180 @@ jobs:
3646

3747
build-macos:
3848
runs-on: macos-latest
49+
strategy:
50+
matrix:
51+
arch: ${{ github.event.inputs.arch == 'both' && fromJSON('["arm64", "x64"]') || fromJSON(format('["{0}"]', github.event.inputs.arch)) }}
52+
3953
steps:
54+
# ─── Checkout ─────────────────────────────────────────────
4055
- name: Checkout code
41-
uses: actions/checkout@v3
56+
uses: actions/checkout@v4
4257

58+
# ─── Setup Node.js ────────────────────────────────────────
4359
- name: Setup Node.js
44-
uses: actions/setup-node@v3
60+
uses: actions/setup-node@v4
4561
with:
46-
node-version: '22'
62+
node-version: 22
63+
cache: npm
4764

65+
# ─── Setup Python (needed by some native deps) ────────────
4866
- name: Setup Python
49-
uses: actions/setup-python@v4
67+
uses: actions/setup-python@v5
5068
with:
5169
python-version: '3.11'
5270

71+
# ─── Install Dependencies ─────────────────────────────────
5372
- name: Install dependencies
5473
run: npm ci
5574

56-
- name: Install app dependencies
57-
run: npx electron-builder install-app-deps
58-
59-
- name: Build macOS app
60-
run: npm run build:mac
75+
# ─── Import Code Signing Certificate ──────────────────────
76+
# This is the KEY step that makes CI signing work.
77+
# We create a temporary keychain, import the .p12 cert into it,
78+
# and set it as the default so codesign can find it.
79+
- name: Import code signing certificate
6180
env:
81+
MAC_CERTIFICATE_P12: ${{ secrets.MAC_CERTIFICATE_P12 }}
82+
MAC_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
83+
run: |
84+
# Create a temporary keychain
85+
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain-db
86+
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
87+
88+
# Create and configure keychain
89+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
90+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
91+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
92+
93+
# Decode and import certificate
94+
echo "$MAC_CERTIFICATE_P12" | base64 --decode > $RUNNER_TEMP/certificate.p12
95+
security import $RUNNER_TEMP/certificate.p12 \
96+
-k "$KEYCHAIN_PATH" \
97+
-P "$MAC_CERTIFICATE_PASSWORD" \
98+
-T /usr/bin/codesign \
99+
-T /usr/bin/security
100+
101+
# Allow codesign to access the keychain without UI prompt
102+
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
103+
104+
# Add to keychain search path (makes it the default)
105+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
106+
107+
# Verify the identity is available
108+
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
109+
110+
# Clean up the .p12 file
111+
rm -f $RUNNER_TEMP/certificate.p12
112+
113+
# ─── Build Vite + Electron ────────────────────────────────
114+
- name: Build Vite + Electron
115+
run: npx tsc && npx vite build
116+
117+
# ─── Package with electron-builder ────────────────────────
118+
# electron-builder handles deep codesigning the .app bundle
119+
# "notarize: false" in electron-builder.json5 prevents it from
120+
# trying its own notarization flow
121+
- name: Package .app bundle
122+
run: npx electron-builder --mac --${{ matrix.arch }} --dir
123+
env:
124+
CSC_NAME: "Samir Patil (N26FZ4GW28)"
62125
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63126

64-
- name: Upload macOS build
127+
# ─── Read version from package.json ───────────────────────
128+
- name: Get version
129+
id: version
130+
run: echo "version=$(node -p 'require(\"./package.json\").version')" >> $GITHUB_OUTPUT
131+
132+
# ─── Locate the .app bundle ───────────────────────────────
133+
- name: Find .app bundle
134+
id: find_app
135+
run: |
136+
VERSION="${{ steps.version.outputs.version }}"
137+
echo "=== Release directory contents ==="
138+
ls -laR "release/${VERSION}/" || echo "release/${VERSION}/ not found"
139+
echo "=== Searching for .app bundle ==="
140+
APP_BUNDLE=$(find "release/${VERSION}" -maxdepth 4 -name "*.app" -type d | head -n1)
141+
if [ -z "$APP_BUNDLE" ]; then
142+
echo "::error::No .app bundle found in release/${VERSION}/"
143+
exit 1
144+
fi
145+
echo "app_bundle=$APP_BUNDLE" >> $GITHUB_OUTPUT
146+
echo "Found: $APP_BUNDLE"
147+
148+
# ─── Verify .app signature ────────────────────────────────
149+
- name: Verify .app code signature
150+
run: codesign --verify --deep --strict "${{ steps.find_app.outputs.app_bundle }}"
151+
152+
# ─── Create DMG ───────────────────────────────────────────
153+
- name: Create DMG
154+
id: dmg
155+
run: |
156+
VERSION="${{ steps.version.outputs.version }}"
157+
ARCH="${{ matrix.arch }}"
158+
DMG_NAME="Openscreen-Mac-${ARCH}-${VERSION}.dmg"
159+
RELEASE_DIR="release/${VERSION}"
160+
DMG_OUTPUT="${RELEASE_DIR}/${DMG_NAME}"
161+
STAGING="${RELEASE_DIR}/dmg-staging"
162+
163+
mkdir -p "$STAGING"
164+
cp -R "${{ steps.find_app.outputs.app_bundle }}" "$STAGING/"
165+
ln -s /Applications "$STAGING/Applications"
166+
167+
hdiutil create \
168+
-srcfolder "$STAGING" \
169+
-volname "Openscreen" \
170+
-fs HFS+ \
171+
-fsargs "-c c=64,a=16,e=16" \
172+
-format UDBZ \
173+
"$DMG_OUTPUT"
174+
175+
rm -rf "$STAGING"
176+
177+
echo "dmg_path=$DMG_OUTPUT" >> $GITHUB_OUTPUT
178+
echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT
179+
180+
# ─── Sign DMG ─────────────────────────────────────────────
181+
- name: Sign DMG
182+
run: |
183+
codesign --force \
184+
--sign "Developer ID Application: Samir Patil (N26FZ4GW28)" \
185+
--timestamp \
186+
"${{ steps.dmg.outputs.dmg_path }}"
187+
188+
# ─── Notarize DMG ────────────────────────────────────────
189+
# On CI we can't use keychain profiles for notarytool, so we
190+
# pass credentials directly via env vars / flags
191+
- name: Notarize DMG
192+
run: |
193+
xcrun notarytool submit "${{ steps.dmg.outputs.dmg_path }}" \
194+
--apple-id "${{ secrets.APPLE_ID }}" \
195+
--team-id "${{ secrets.APPLE_TEAM_ID }}" \
196+
--password "${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}" \
197+
--wait
198+
timeout-minutes: 15
199+
200+
# ─── Staple ───────────────────────────────────────────────
201+
- name: Staple notarization ticket
202+
run: xcrun stapler staple "${{ steps.dmg.outputs.dmg_path }}"
203+
204+
# ─── Validate ─────────────────────────────────────────────
205+
- name: Validate stapled DMG
206+
run: |
207+
xcrun stapler validate "${{ steps.dmg.outputs.dmg_path }}"
208+
spctl -a -vv -t install "${{ steps.dmg.outputs.dmg_path }}"
209+
210+
# ─── Upload Artifact ──────────────────────────────────────
211+
- name: Upload notarized DMG
65212
uses: actions/upload-artifact@v4
66213
with:
67-
name: macos-installer
68-
path: release/**/*.dmg
214+
name: openscreen-mac-${{ matrix.arch }}
215+
path: ${{ steps.dmg.outputs.dmg_path }}
69216
retention-days: 30
70217

218+
# ─── Cleanup Keychain ─────────────────────────────────────
219+
- name: Cleanup keychain
220+
if: always()
221+
run: security delete-keychain $RUNNER_TEMP/build.keychain-db || true
222+
71223
build-linux:
72224
runs-on: ubuntu-latest
73225
steps:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dist
1212
dist-electron
1313
dist-ssr
1414
*.local
15+
.env
1516

1617
# Editor directories and files
1718
.vscode/*

electron-builder.json5

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@
2020
"!CONTRIBUTING.md",
2121
"!LICENSE"
2222
],
23-
"extraResources": [
24-
{
25-
"from": "public/wallpapers",
26-
"to": "assets/wallpapers"
27-
}
28-
],
29-
"publish": [{"provider": "github"}],
30-
31-
"mac": {
32-
"hardenedRuntime": false,
23+
"extraResources": [
24+
{
25+
"from": "public/wallpapers",
26+
"to": "assets/wallpapers"
27+
}
28+
],
29+
30+
"mac": {
31+
"notarize": false,
32+
"hardenedRuntime": true,
33+
"entitlements": "macos.entitlements",
34+
"entitlementsInherit": "macos.entitlements",
3335
"target": [
3436
{
3537
"target": "dmg",
@@ -38,13 +40,13 @@
3840
],
3941
"icon": "icons/icons/mac/icon.icns",
4042
"artifactName": "${productName}-Mac-${arch}-${version}-Installer.${ext}",
41-
"extendInfo": {
42-
"NSAudioCaptureUsageDescription": "OpenScreen needs audio capture permission to record system audio.",
43-
"NSMicrophoneUsageDescription": "OpenScreen needs microphone access to record voice audio.",
44-
"NSCameraUsageDescription": "OpenScreen needs camera access to record webcam video.",
45-
"NSCameraUseContinuityCameraDeviceType": true,
46-
"com.apple.security.device.audio-input": true
47-
}
43+
"extendInfo": {
44+
"NSAudioCaptureUsageDescription": "OpenScreen needs audio capture permission to record system audio.",
45+
"NSMicrophoneUsageDescription": "OpenScreen needs microphone access to record voice audio.",
46+
"NSCameraUsageDescription": "OpenScreen needs camera access to record webcam video.",
47+
"NSCameraUseContinuityCameraDeviceType": true,
48+
"com.apple.security.device.audio-input": true
49+
}
4850
},
4951
"linux": {
5052
"target": [
@@ -54,14 +56,14 @@
5456
"artifactName": "${productName}-Linux-${version}.${ext}",
5557
"category": "AudioVideo"
5658
},
57-
"win": {
58-
"target": [
59-
"nsis"
60-
],
61-
"icon": "icons/icons/win/icon.ico"
62-
},
63-
"nsis": {
64-
"oneClick": false,
65-
"allowToChangeInstallationDirectory": true
66-
}
67-
}
59+
"win": {
60+
"target": [
61+
"nsis"
62+
],
63+
"icon": "icons/icons/win/icon.ico"
64+
},
65+
"nsis": {
66+
"oneClick": false,
67+
"allowToChangeInstallationDirectory": true
68+
}
69+
}

electron/main.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,12 @@ let mainWindow: BrowserWindow | null = null;
6262
let sourceSelectorWindow: BrowserWindow | null = null;
6363
let tray: Tray | null = null;
6464
let selectedSourceName = "";
65+
const isMac = process.platform === "darwin";
66+
const trayIconSize = isMac ? 16 : 24;
6567

6668
// Tray Icons
67-
const defaultTrayIcon = getTrayIcon("openscreen.png");
68-
const recordingTrayIcon = getTrayIcon("rec-button.png");
69+
const defaultTrayIcon = getTrayIcon("openscreen.png", trayIconSize);
70+
const recordingTrayIcon = getTrayIcon("rec-button.png", trayIconSize);
6971

7072
function createWindow() {
7173
mainWindow = createHudOverlayWindow();
@@ -199,12 +201,12 @@ function createTray() {
199201
});
200202
}
201203

202-
function getTrayIcon(filename: string) {
204+
function getTrayIcon(filename: string, size: number) {
203205
return nativeImage
204206
.createFromPath(path.join(process.env.VITE_PUBLIC || RENDERER_DIST, filename))
205207
.resize({
206-
width: 24,
207-
height: 24,
208+
width: size,
209+
height: size,
208210
quality: "best",
209211
});
210212
}

electron/windows.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ ipcMain.on("hud-overlay-hide", () => {
1717
}
1818
});
1919

20+
/**
21+
* Creates the always-on-top HUD overlay window centred at the bottom of the
22+
* primary display. The window is frameless, transparent, and follows the user
23+
* across macOS Spaces so it is never lost when switching virtual desktops.
24+
*/
2025
export function createHudOverlayWindow(): BrowserWindow {
2126
const primaryDisplay = screen.getPrimaryDisplay();
2227
const { workArea } = primaryDisplay;
@@ -51,6 +56,12 @@ export function createHudOverlayWindow(): BrowserWindow {
5156
},
5257
});
5358

59+
// Follow the user across macOS Spaces (virtual desktops).
60+
// Without this the HUD stays pinned to the Space it was first opened on.
61+
if (process.platform === "darwin") {
62+
win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
63+
}
64+
5465
win.webContents.on("did-finish-load", () => {
5566
win?.webContents.send("main-process-message", new Date().toLocaleString());
5667
});
@@ -74,6 +85,10 @@ export function createHudOverlayWindow(): BrowserWindow {
7485
return win;
7586
}
7687

88+
/**
89+
* Creates the main editor window. Starts maximised with a hidden title bar on
90+
* macOS. This window is not always-on-top and appears in the taskbar/dock.
91+
*/
7792
export function createEditorWindow(): BrowserWindow {
7893
const isMac = process.platform === "darwin";
7994

@@ -120,6 +135,10 @@ export function createEditorWindow(): BrowserWindow {
120135
return win;
121136
}
122137

138+
/**
139+
* Creates the floating source-selector window used to pick a screen or window
140+
* to record. Frameless, transparent, and follows the user across macOS Spaces.
141+
*/
123142
export function createSourceSelectorWindow(): BrowserWindow {
124143
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
125144

@@ -142,6 +161,12 @@ export function createSourceSelectorWindow(): BrowserWindow {
142161
},
143162
});
144163

164+
// Follow the user across macOS Spaces so the selector appears on the
165+
// active desktop regardless of where the HUD was originally opened.
166+
if (process.platform === "darwin") {
167+
win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
168+
}
169+
145170
if (VITE_DEV_SERVER_URL) {
146171
win.loadURL(VITE_DEV_SERVER_URL + "?windowType=source-selector");
147172
} else {

icons/icons/mac/icon.icns

-85.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)