feat: send screen recordings (#128)

* first pass implementation

* remove screenrecord shortcut hint

* refactor

* fix: prevent mirror review of screen recording

* feat: allow recording with webcam overlay

* fix: review screen recording without clipped content

* tidy keyboard hints consistent position

* refactor: re-use one component for screen picker

huddles and screen clips use same picker component now. We had to make
sure that tailwind works for both of them.

* fix: improve visibility of keyboard hints

During compose video or screen recording, the keyboard hints were
invisible if the content was super bright.
This commit was merged in pull request #128.
This commit is contained in:
Arjun Patel
2026-04-08 17:14:39 -07:00
committed by GitHub
parent bebd814c46
commit be7a42c483
18 changed files with 694 additions and 29 deletions
+73
View File
@@ -25,6 +25,7 @@ if (process.platform === 'darwin' && !app.isPackaged) {
let mainWindow: BrowserWindow | null = null;
let autoplayWindow: BrowserWindow | null = null;
let huddleWindow: BrowserWindow | null = null;
let screenRecordWindow: BrowserWindow | null = null;
const createWindow = () => {
mainWindow = new BrowserWindow({
@@ -121,6 +122,43 @@ function positionAutoplayWindow() {
autoplayWindow.setPosition(width - winW - 16, 16);
}
const createScreenRecordWindow = () => {
if (screenRecordWindow) return;
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
const winW = 240;
const winH = 48;
screenRecordWindow = new BrowserWindow({
width: winW,
height: winH,
x: Math.round((width - winW) / 2),
y: height - winH - 32,
resizable: false,
frame: false,
alwaysOnTop: true,
skipTaskbar: true,
focusable: true,
show: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
screenRecordWindow.setVisibleOnAllWorkspaces(true);
if (SCREEN_RECORD_WINDOW_VITE_DEV_SERVER_URL) {
screenRecordWindow.loadURL(SCREEN_RECORD_WINDOW_VITE_DEV_SERVER_URL);
} else {
screenRecordWindow.loadFile(
path.join(__dirname, `../renderer/${SCREEN_RECORD_WINDOW_VITE_NAME}/index.html`),
);
}
screenRecordWindow.on('closed', () => {
screenRecordWindow = null;
});
};
// Window control IPC handlers
ipcMain.on('window:minimize', (event) => {
BrowserWindow.fromWebContents(event.sender)?.minimize();
@@ -170,6 +208,41 @@ ipcMain.handle('screen:get-sources', async () => {
}));
});
// Screen recording IPC handlers
ipcMain.on('screen-record:start', (_event, data: { includeCamera: boolean }) => {
createScreenRecordWindow();
if (!screenRecordWindow) return;
// Resize based on whether camera preview is shown
const winW = data.includeCamera ? 200 : 240;
const winH = data.includeCamera ? 176 : 48;
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
screenRecordWindow.setSize(winW, winH);
screenRecordWindow.setPosition(
Math.round((width - winW) / 2),
height - winH - 32,
);
const send = () => {
screenRecordWindow?.webContents.send('screen-record:init', data);
screenRecordWindow?.showInactive();
};
if (screenRecordWindow.webContents.isLoading()) {
screenRecordWindow.webContents.once('did-finish-load', send);
} else {
send();
}
});
ipcMain.on('screen-record:stop', () => {
mainWindow?.webContents.send('screen-record:stopped');
screenRecordWindow?.close();
mainWindow?.focus();
});
ipcMain.on('screen-record:cancel', () => {
screenRecordWindow?.close();
});
// Autoplay IPC handlers
ipcMain.on('autoplay:play', (_event, payload) => {
if (!autoplayWindow) createAutoplayWindow();