Skip to content

Commit

Permalink
Merge pull request #73 from catdad/capture
Browse files Browse the repository at this point in the history
implementing desktop capture
  • Loading branch information
catdad authored Jun 27, 2024
2 parents cee1807 + 05a1f52 commit 6d1c415
Show file tree
Hide file tree
Showing 26 changed files with 922 additions and 78 deletions.
76 changes: 69 additions & 7 deletions lib/browser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { BrowserWindow } = require('electron');
const { BrowserWindow, dialog, systemPreferences } = require('electron');

const isomorphic = require('./isomorphic.js');
const name = 'browser';
Expand All @@ -17,15 +17,77 @@ const minimize = () => void BrowserWindow.getFocusedWindow().minimize();

const close = () => void BrowserWindow.getFocusedWindow().close();

const clickthrough = () => {
const window = BrowserWindow.getFocusedWindow();
const focus = () => {
for (const window of BrowserWindow.getAllWindows()) {
window.restore();
window.show();
}
};

const enterClickthrough = async () => {
for (const window of BrowserWindow.getAllWindows()) {
window.blur();

window.setIgnoreMouseEvents(true, { forward: true });
window.setAlwaysOnTop(true);
window.setVisibleOnAllWorkspaces(true);
}
};

const exitClickthrough = () => {
for (const window of BrowserWindow.getAllWindows()) {
window.setIgnoreMouseEvents(false);
window.setAlwaysOnTop(false);
window.setVisibleOnAllWorkspaces(false);
}
};

const directoryDialog = (defaultPath) => {
// sync method will actually block the entire Electron application
// which, in this case, is in fact what we want... the rare case
const result = dialog.showOpenDialogSync({
defaultPath,
properties: ['openDirectory']
});

if (Array.isArray(result)) {
return { canceled: false, destination: result[0] };
}

window.setIgnoreMouseEvents(true);
window.setAlwaysOnTop(true);
window.setOpacity(0.5);
return { canceled: true };
};

const getCapturePermissionStatus = async () => {
if (!['darwin', 'win32'].includes(process.platform)) {
return true;
}

const status = await systemPreferences.getMediaAccessStatus('screen');

return status === 'granted';
};

const requestCapturePermission = async () => {
if (process.platform !== 'darwin') {
return true;
}

const granted = await systemPreferences.askForMediaAccess('screen');

return granted === true;
};

module.exports = isomorphic({
name,
implementation: { close, minimize, maximizeToggle, clickthrough }
implementation: {
close,
minimize,
maximizeToggle,
focus,
enterClickthrough,
exitClickthrough,
directoryDialog,
getCapturePermissionStatus,
requestCapturePermission
}
});
4 changes: 4 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require('path');
const fs = require('fs-extra');
const _ = require('lodash');

const { app } = require('electron');
const is = require('./is.js');
const root = require('./root.js');
const name = 'config';
Expand Down Expand Up @@ -75,6 +76,9 @@ const implementation = {
setProp: (name, value) => {
_.set(configObj, name, value);
autoSave();
},
getPath: (name) => {
return app.getPath(name);
}
};

Expand Down
30 changes: 27 additions & 3 deletions lib/isomorphic.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const log = require('./log.js');

const gid = () => Math.random().toString(36).substr(2);

module.exports = ({ name, implementation }) => {
module.exports = ({ name, implementation, events }) => {
const { info, error } = log(`${name}-isomorphic`);
const CB_CHANNEL = `${name}-channel`;

Expand All @@ -13,6 +13,18 @@ module.exports = ({ name, implementation }) => {
if (is.main && electron.ipcMain.listenerCount(CB_CHANNEL) === 0) {
info(`initializing main ipc for ${name} module`);

if (events) {
const emit = events.emit.bind(events);

events.emit = (evname, ...args) => {
emit(name, ...args);

for (const window of electron.BrowserWindow.getAllWindows()) {
window.webContents.send(`${name}:event`, { evname, args });
}
};
}

const callback = (ev, id) => (err, result) => {
const cbName = `${name}:callback:${id}`;
const threadTimestamp = Date.now();
Expand Down Expand Up @@ -51,6 +63,12 @@ module.exports = ({ name, implementation }) => {
});
}

if (!is.main && events) {
electron.ipcRenderer.on(`${name}:event`, (ev, { evname, args }) => {
events.emit(evname, ...args);
});
}

const rendererSend = (opname, args) => {
const id = gid() + gid();

Expand All @@ -67,8 +85,14 @@ module.exports = ({ name, implementation }) => {
});
};

return keys.reduce((memo, key) => {
return Object.defineProperties(keys.reduce((memo, key) => {
memo[key] = is.main ? implementation[key] : (...args) => rendererSend(key, args);
return memo;
}, {});
}, {}), {
events: {
enumerable: true,
writable: false,
value: events
}
});
};
51 changes: 51 additions & 0 deletions lib/keyboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const name = 'keyboard';

const EventEmitter = require('events');
const { app, globalShortcut } = require('electron');
const is = require('./is.js');
const { info, error } = require('./log.js')(name);

const isomorphic = require('./isomorphic.js');

const events = new EventEmitter();

let initialized = false;
const shortcuts = {};

if (is.main) {
app.whenReady().then(() => {
initialized = true;
Object.keys(shortcuts).forEach(accelerator => add(accelerator));
});

app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
}

function add(accelerator) {
shortcuts[accelerator] = true;

if (!initialized) {
return;
}
info(`adding keyboard shortcut: ${accelerator}`);

globalShortcut.unregister(accelerator);

globalShortcut.register(accelerator, () => {
info(`triggering keyboard shortcut: ${accelerator}`);
events.emit(accelerator);
});
}

function remove(accelerator) {
info(`removing keyboard shortcut: ${accelerator}`);

globalShortcut.unregister(accelerator);
delete shortcuts[accelerator];
}

const implementation = { add, remove };

module.exports = isomorphic({ name, implementation, events });
90 changes: 78 additions & 12 deletions lib/video-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,36 @@ const { map: commands, SYMBOL_RETURN_STDOUT } = require('video-tools');

const videoQueue = new PQueue({ concurrency: 1 });
const taskInfo = { current: 0, total: 0, taskCount: 0, remainingCount: 0 };
let currentStdin;

const resetStdio = () => {
// for some reason, ffmpeg sometimes turns the terminal blue
// and I can't get it to stop even when I try stripping all style characters
// so at least reset the terminal back to normal once ffmpeg is done
process.stdout.write('\x1b[0m');
process.stderr.write('\x1b[0m');
};

// do this on startup, for development purposes
resetStdio();

// self-flushing stream that also resets the terminal each time
const cleanStream = (iostream, hook, passthrough = false) => through((chunk, enc, cb) => {
if (iostream && iostream.write) {
iostream.write(chunk);
resetStdio();
}

if (hook) {
hook(chunk);
}

if (passthrough) {
cb(null, chunk);
} else {
cb();
}
});

const getFrames = async (args) => {
const meta = await commands.meta(...args);
Expand All @@ -21,6 +51,8 @@ const exec = async (command, args, { debug = false, frames = 0, onUpdate = () =>
throw new Error(`"${command}" is not a know command`);
}

args[0] = args[0] || {};

if (['container', 'x264'].includes(command) && frames) {
const debug = get(args, '0.debug') || get(args, '0.dry');

Expand All @@ -29,12 +61,13 @@ const exec = async (command, args, { debug = false, frames = 0, onUpdate = () =>
process.stderr.write(chunk);
}

resetStdio();

cb(null, chunk);
});

args[0] = args[0] || {};
args[0].stderr = stderr;
args[0].stdout = process.stdout;
args[0].stdout = cleanStream(process.stdout);

delete args[0].debug;

Expand All @@ -43,22 +76,25 @@ const exec = async (command, args, { debug = false, frames = 0, onUpdate = () =>
const frame = Number(info.frame);
onUpdate(frame);
});
} else if (command === 'desktop') {
currentStdin = through();

args[0].stdin = currentStdin;
args[0].stdout = cleanStream(process.stdout);
args[0].stderr = cleanStream(process.stderr);
} else if (debug) {
args[0] = args[0] || {};
args[0].stderr = process.stderr;
args[0].stdout = cleanStream(process.stdout);
args[0].stderr = cleanStream(process.stderr);
}

if (args && args[0] && args[0].output === '-') {
args[0].output = SYMBOL_RETURN_STDOUT;
}

return await commands[command](...args).finally(() => {
// for some reason, ffmpeg sometimes turns the terminal blue
// and I can't get it to stop even when I try stripping all style characters
// so at least reset the terminal back to normal once ffmpeg is done
process.stdout.write('\x1b[0m');
process.stderr.write('\x1b[0m');
});
return await (commands[command](...args).finally(() => {
currentStdin = null;
resetStdio();
}));
};

const queue = async (command, args) => {
Expand All @@ -85,6 +121,36 @@ const queueInspect = async () => {
return { taskCurrent, taskTotal };
};

const implementation = { queue, exec, queueInspect, getFrames };
const stopCurrent = () => {
if (currentStdin) {
currentStdin.write('q');
currentStdin.end();
}

currentStdin = null;
};

const getCaptureDeviceList = async () => {
let stdout = '', stderr = '';

await commands['desktop']({
stdout: cleanStream(null, chunk => (stdout += chunk.toString())),
stderr: cleanStream(null, chunk => (stderr += chunk.toString())),
list: true
}).finally(() => {
resetStdio();
});

const result = stderr
.replace(/\r\n/g, '\n')
.split('\n')
.map(line => line.replace(/^\[[^\]]{1,}\] /, ''))
.filter(l => !!l)
.filter(l => l.indexOf('dummy') !== 0);

return { stdout, stderr, result };
};

const implementation = { queue, exec, queueInspect, getFrames, stopCurrent, getCaptureDeviceList };

module.exports = isomorphic({ name, implementation });
1 change: 1 addition & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require('./lib/app-id.js')(app);
require('./lib/video-tools.js');
require('./lib/progress.js');
require('./lib/browser.js');
require('./lib/keyboard.js');
const log = require('./lib/log.js')('main');
const config = require('./lib/config.js');
const menu = require('./lib/menu.js');
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"pretty-ms": "^7.0.1",
"tempy": "^0.5.0",
"through2": "^4.0.2",
"video-tools": "https://github.com/catdad/video-tools/tarball/73bc61bb385292b872085eddb13dcfb522c12d0a"
"video-tools": "https://github.com/catdad/video-tools/tarball/1371198143e81ec8beca5d195c0b16d2f2c025d9"
},
"electronmon": {
"patterns": [
Expand Down
2 changes: 1 addition & 1 deletion renderer/App/App.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.wrapper {
height: var(--app-height);
overflow: auto;
background-color: var(--color-background);
background-color: var(--overwritable-background);
}

.app {
Expand Down
Loading

0 comments on commit 6d1c415

Please sign in to comment.