diff --git a/.gitignore b/.gitignore index 98dbe0c..ffdab31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -/node_modules/ +node_modules/ +/packages/record-desktop-gtk/dist/ /packages/record-desktop-electron/dist/ *.log play.js diff --git a/packages/record-desktop-electron/src/renderer/components/gallery/gallery.js b/packages/record-desktop-electron/src/renderer/components/gallery/gallery.js index f239ea6..7133825 100644 --- a/packages/record-desktop-electron/src/renderer/components/gallery/gallery.js +++ b/packages/record-desktop-electron/src/renderer/components/gallery/gallery.js @@ -4,8 +4,6 @@ import styles from './style.css'; import GalleryFile from '../gallery-file/gallery-file'; import { remote } from 'electron'; -import debounce from 'lodash/debounce'; - import { ipcRenderer } from 'electron'; import { @@ -16,7 +14,7 @@ import { NEW_FILE } from '../../../shared/constants'; -import detectViewport from './detect-viewport'; +// import detectViewport from './detect-viewport'; const devConsole = console; @@ -47,16 +45,16 @@ export default class Gallery extends React.Component { componentDidMount() { this.getFiles(); - const onPageScroll = () => { - const visibility = detectViewport('.imageBlock'); + // const onPageScroll = () => { + // const visibility = detectViewport('.imageBlock'); - this.setState({ - files: this.state.files.map((file, index) => ({ - ...file, - visible: visibility[index] - })) - }); - }; + // this.setState({ + // files: this.state.files.map((file, index) => ({ + // ...file, + // visible: visibility[index] + // })) + // }); + // }; ipcRenderer.on(NEW_FILE, () => this.getFiles()); // window.onscroll = debounce(onPageScroll, 50, { trailing: true }); diff --git a/packages/record-desktop-gtk/README.md b/packages/record-desktop-gtk/README.md new file mode 100644 index 0000000..b37c00c --- /dev/null +++ b/packages/record-desktop-gtk/README.md @@ -0,0 +1,12 @@ +## Install + +```sh +$ sudo apt-get install gjs libkeybinder-3.0-0 gir1.2-keybinder +``` + +## Usage + +```sh +$ yarn build +$ gjs ./dist/main.js +``` diff --git a/packages/record-desktop-gtk/package.json b/packages/record-desktop-gtk/package.json new file mode 100644 index 0000000..31a64f3 --- /dev/null +++ b/packages/record-desktop-gtk/package.json @@ -0,0 +1,9 @@ +{ + "name": "record-desktop-gtk", + "version": "0.5.9", + "description": "Record gifs and take screenshots on Linux", + "main": "dist/lib.js", + "scripts": { + "build": "../../node_modules/.bin/webpack --config webpack.config.js" + } +} diff --git a/packages/record-desktop-gtk/src/execa-mock.js b/packages/record-desktop-gtk/src/execa-mock.js new file mode 100644 index 0000000..5da0c9b --- /dev/null +++ b/packages/record-desktop-gtk/src/execa-mock.js @@ -0,0 +1,24 @@ +'use strict'; + +const GLib = require('gi/GLib'); + +module.exports = { + shell, + stdout +}; + +// @TODO: change to async +// http://devdocs.baznga.org/glib20~2.50.0/glib.spawn_command_line_sync +// http://devdocs.baznga.org/glib20~2.50.0/glib.spawn_command_line_async +async function shell(cmd) { + const [, out] = GLib.spawn_command_line_sync(`bash -c "${cmd}"`); + return Promise.resolve(out.toString()); +} + +// @TODO: change to async +// http://devdocs.baznga.org/glib20~2.50.0/glib.spawn_sync +// http://devdocs.baznga.org/glib20~2.50.0/glib.spawn_async +async function stdout(cmd, args = [],) { + const [, out] = GLib.spawn_sync(null, [cmd, ...args], null, GLib.SpawnFlags.SEARCH_PATH, null); + return Promise.resolve(out.toString()); +} diff --git a/packages/record-desktop-gtk/src/index.js b/packages/record-desktop-gtk/src/index.js new file mode 100644 index 0000000..745ecd4 --- /dev/null +++ b/packages/record-desktop-gtk/src/index.js @@ -0,0 +1,44 @@ +'use strict'; + +require('gi').versions.Keybinder = '3.0'; + +const GObject = require('gi/GObject'); +const Gtk = require('gi/Gtk'); +const Keybinder = require('gi/Keybinder'); + +const { selectRegion, takeScreenshot } = require('record-desktop'); + +const Keyboard = GObject.registerClass( + {}, + class Keyboard extends Gtk.ApplicationWindow { + _init(params) { + super._init(params); + + const hotkey = 'X'; + Keybinder.init(); + + const res = Keybinder.bind(hotkey, async () => { + print(`${hotkey} start`); + const outputFile = '/tmp/1.png'; + + const { width, height, x, y } = await selectRegion(); + await takeScreenshot({ width, height, x, y, outputFile }); + + print(`${hotkey} click`, outputFile); + }); + + print(hotkey, res); + } + } +); + +const RecordDesktopGtkApplication = GObject.registerClass( + {}, + class RecordDesktopGtkApplication extends Gtk.Application { + vfunc_activate() { + this.keyboard = new Keyboard({ application: this }); + } + } +); + +new RecordDesktopGtkApplication().run([]); diff --git a/packages/record-desktop-gtk/webpack.config.js b/packages/record-desktop-gtk/webpack.config.js new file mode 100644 index 0000000..375ebc0 --- /dev/null +++ b/packages/record-desktop-gtk/webpack.config.js @@ -0,0 +1,28 @@ +const path = require('path'); + +module.exports = { + entry: { + main: `${__dirname}/src/index.js`, + }, + output: { + filename: '[name].js', + path: `${__dirname}/dist`, + libraryTarget: 'var', + library: '[name]' + }, + resolve: { + alias: { + 'execa': `${__dirname}/src/execa-mock.js` + }, + modules: [ + 'node_modules' + ] + }, + externals: { + 'gi': 'imports.gi', + 'gi/GLib': 'imports.gi.GLib', + 'gi/GObject': 'imports.gi.GObject', + 'gi/Gtk': 'imports.gi.Gtk', + 'gi/Keybinder': 'imports.gi.Keybinder' + } +}; diff --git a/packages/record-desktop/package.json b/packages/record-desktop/package.json index 2a027ff..746d87c 100644 --- a/packages/record-desktop/package.json +++ b/packages/record-desktop/package.json @@ -1,5 +1,9 @@ { "name": "record-desktop", "version": "0.5.9", - "main": "src/index.js" + "main": "src/index.js", + "dependencies": { + "execa": "^0.10.0", + "p-cancelable": "^0.4.1" + } } diff --git a/packages/record-desktop/src/index.js b/packages/record-desktop/src/index.js index bcdc8be..5959256 100644 --- a/packages/record-desktop/src/index.js +++ b/packages/record-desktop/src/index.js @@ -1,6 +1,7 @@ 'use strict'; const execa = require('execa'); +const PCancelable = require('p-cancelable'); module.exports = { recordGif, @@ -18,25 +19,20 @@ function recordGif({ outputFile, width, height, x, y }) { } function recordGifByzanz({ outputFile, width, height, x, y }) { - const coords = typeof width === 'undefined' ? [] : [ - `--x=${x}`, - `--y=${y}`, - `--width=${width}`, - `--height=${height}` - ]; - - const args = coords.concat(outputFile); - - const proc = execa.shell(`byzanz-record ${args.join(' ')}`); // shell is needed for internal byzanz forking - - const finish = () => { - proc.kill('SIGINT'); - }; - - return { - proc, - finish - }; + return new PCancelable((resolve, reject, onCancel) => { + const coords = typeof width === 'undefined' ? [] : [ + `--x=${x}`, + `--y=${y}`, + `--width=${width}`, + `--height=${height}` + ]; + + const args = coords.concat(outputFile); + const proc = execa.shell(`byzanz-record ${args.join(' ')}`); // shell is needed for internal byzanz forking + + proc.then(resolve, reject); + onCancel(() => proc.kill('SIGINT')); + }); } async function selectRegion() { diff --git a/packages/record-desktop/src/unix-utils/ffmpeg.js b/packages/record-desktop/src/unix-utils/ffmpeg.js index 9c85278..29353d4 100644 --- a/packages/record-desktop/src/unix-utils/ffmpeg.js +++ b/packages/record-desktop/src/unix-utils/ffmpeg.js @@ -1,49 +1,42 @@ +'use strict'; + /* inspiration: https://github.com/wulkano/aperture https://github.com/wulkano/aperture/pull/5/files https://github.com/wulkano/aperture/pull/32 */ -import execa from 'execa'; -const devConsole = console; + +const execa = require('execa'); +const PCancelable = require('p-cancelable'); export function ffmpeg({ outputFile, fps = 30, x, y, width, height, offset = 0, showCursor }) { - const args = ['-f', 'x11grab']; - - if (typeof width !== 'undefined') { - if (offset > 0) { - devConsole.log(x, y, width, height); - devConsole.log(offset); - x = x - offset; - y = y - offset; - width = width + offset * 2; - height = height + offset * 2; - devConsole.log(x, y, width, height); + return new PCancelable((resolve, reject, onCancel) => { + const args = ['-f', 'x11grab']; + + if (typeof width !== 'undefined') { + if (offset > 0) { + x = x - offset; + y = y - offset; + width = width + offset * 2; + height = height + offset * 2; + } + + args.push('-video_size', `${width}x${height}`); + args.push('-i', `:0+${x},${y}`) + } else { + args.push('-i', ':0'); } - args.push('-video_size', `${width}x${height}`); - args.push('-i', `:0+${x},${y}`) - } else { - args.push('-i', ':0'); - } - - args.push('-y') // force overwrite existing file - args.push('-framerate', fps, '-draw_mouse', +(showCursor === true), outputFile); + args.push('-y') // force overwrite existing file + args.push('-framerate', fps, '-draw_mouse', +(showCursor === true), outputFile); - const proc = execa('ffmpeg', args); + const proc = execa('ffmpeg', args); - // proc.stderr.setEncoding('utf8'); - // proc.stderr.on('data', str => { - // devConsole.log(str) - // }) - - const finish = () => new Promise(resolve => { - proc.stdin.setEncoding('utf8'); - proc.stdin.write('q'); - resolve(); - }) - - return { - proc, - finish - }; + proc.then(resolve, reject); + onCancel(() => { + proc.stdin.setEncoding('utf8'); + proc.stdin.write('q'); + }); + }); } + diff --git a/yarn.lock b/yarn.lock index 6b8d021..d570c09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2630,6 +2630,16 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -3551,6 +3561,18 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +execa@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + dependencies: + cross-spawn "^6.0.0" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + execa@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" @@ -5803,6 +5825,10 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +nice-try@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -6182,6 +6208,10 @@ output-file-sync@^1.1.2: mkdirp "^0.5.1" object-assign "^4.1.0" +p-cancelable@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -6324,7 +6354,7 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" -path-key@^2.0.0: +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -8008,6 +8038,7 @@ style-loader@^0.20.3: sumchecker@2.0.2, sumchecker@^1.2.0, sumchecker@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" + integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= dependencies: debug "^2.2.0"