aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-05-28 19:51:37 +0200
committerLibravatar GitHub <noreply@github.com>2021-05-28 19:51:37 +0200
commit97408875613c39ea0acc33af41c5caf6a82e7ba1 (patch)
tree48e9411543fbde7fad0af62b402fadc32bbd4e92
parentRead CSS assets in recipes as utf8 (#1459) (diff)
downloadferdi-97408875613c39ea0acc33af41c5caf6a82e7ba1.tar.gz
ferdi-97408875613c39ea0acc33af41c5caf6a82e7ba1.tar.zst
ferdi-97408875613c39ea0acc33af41c5caf6a82e7ba1.zip
Screen share refactoring (#1460)
* Extract screenshare into a separate file Extracted from 240c3a72363e7388779c9ed3c6467ec63bb64d94 according to https://github.com/getferdi/ferdi/pull/1456#discussion_r641194876 * Cosmetic screenshare changes and cancellation * Makes the screen/window selector more readable * Adds a Cancel button to close the selector
-rw-r--r--src/webview/recipe.js123
-rw-r--r--src/webview/screenshare.js139
2 files changed, 141 insertions, 121 deletions
diff --git a/src/webview/recipe.js b/src/webview/recipe.js
index 6180270b..5a24effa 100644
--- a/src/webview/recipe.js
+++ b/src/webview/recipe.js
@@ -1,5 +1,5 @@
1/* eslint-disable import/first */ 1/* eslint-disable import/first */
2import { ipcRenderer, desktopCapturer } from 'electron'; 2import { ipcRenderer } from 'electron';
3import { getCurrentWebContents } from '@electron/remote'; 3import { getCurrentWebContents } from '@electron/remote';
4import path from 'path'; 4import path from 'path';
5import { autorun, computed, observable } from 'mobx'; 5import { autorun, computed, observable } from 'mobx';
@@ -27,75 +27,13 @@ import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellcheck
27import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; 27import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode';
28import contextMenu from './contextMenu'; 28import contextMenu from './contextMenu';
29import './notifications'; 29import './notifications';
30import { screenShareCss } from './screenshare';
30 31
31import { DEFAULT_APP_SETTINGS } from '../config'; 32import { DEFAULT_APP_SETTINGS } from '../config';
32import { isDevMode } from '../environment'; 33import { isDevMode } from '../environment';
33 34
34const debug = require('debug')('Ferdi:Plugin'); 35const debug = require('debug')('Ferdi:Plugin');
35 36
36const screenShareCss = `
37.desktop-capturer-selection {
38 position: fixed;
39 top: 0;
40 left: 0;
41 width: 100%;
42 height: 100vh;
43 background: rgba(30,30,30,.75);
44 color: #fff;
45 z-index: 10000000;
46 display: flex;
47 align-items: center;
48 justify-content: center;
49}
50.desktop-capturer-selection__scroller {
51 width: 100%;
52 max-height: 100vh;
53 overflow-y: auto;
54}
55.desktop-capturer-selection__list {
56 max-width: calc(100% - 100px);
57 margin: 50px;
58 padding: 0;
59 display: flex;
60 flex-wrap: wrap;
61 list-style: none;
62 overflow: hidden;
63 justify-content: center;
64}
65.desktop-capturer-selection__item {
66 display: flex;
67 margin: 4px;
68}
69.desktop-capturer-selection__btn {
70 display: flex;
71 flex-direction: column;
72 align-items: stretch;
73 width: 145px;
74 margin: 0;
75 border: 0;
76 border-radius: 3px;
77 padding: 4px;
78 background: #252626;
79 text-align: left;
80 transition: background-color .15s, box-shadow .15s;
81}
82.desktop-capturer-selection__btn:hover,
83.desktop-capturer-selection__btn:focus {
84 background: rgba(98,100,167,.8);
85}
86.desktop-capturer-selection__thumbnail {
87 width: 100%;
88 height: 81px;
89 object-fit: cover;
90}
91.desktop-capturer-selection__name {
92 margin: 6px 0 6px;
93 white-space: nowrap;
94 text-overflow: ellipsis;
95 overflow: hidden;
96}
97`;
98
99class RecipeController { 37class RecipeController {
100 @observable settings = { 38 @observable settings = {
101 overrideSpellcheckerLanguage: false, 39 overrideSpellcheckerLanguage: false,
@@ -438,60 +376,3 @@ window.open = (url, frameName, features) => {
438if (isDevMode) { 376if (isDevMode) {
439 window.log = console.log; 377 window.log = console.log;
440} 378}
441
442// Patch getDisplayMedia for screen sharing
443window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve, reject) => {
444 try {
445 const sources = await desktopCapturer.getSources({ types: ['screen', 'window'] });
446
447 const selectionElem = document.createElement('div');
448 selectionElem.classList = 'desktop-capturer-selection';
449 selectionElem.innerHTML = `
450 <div class="desktop-capturer-selection__scroller">
451 <ul class="desktop-capturer-selection__list">
452 ${sources.map(({
453 id, name, thumbnail,
454 }) => `
455 <li class="desktop-capturer-selection__item">
456 <button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
457 <img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
458 <span class="desktop-capturer-selection__name">${name}</span>
459 </button>
460 </li>
461 `).join('')}
462 </ul>
463 </div>
464 `;
465 document.body.appendChild(selectionElem);
466
467 document.querySelectorAll('.desktop-capturer-selection__btn')
468 .forEach((button) => {
469 button.addEventListener('click', async () => {
470 try {
471 const id = button.getAttribute('data-id');
472 const mediaSource = sources.find(source => source.id === id);
473 if (!mediaSource) {
474 throw new Error(`Source with id ${id} does not exist`);
475 }
476
477 const stream = await window.navigator.mediaDevices.getUserMedia({
478 audio: false,
479 video: {
480 mandatory: {
481 chromeMediaSource: 'desktop',
482 chromeMediaSourceId: mediaSource.id,
483 },
484 },
485 });
486 resolve(stream);
487
488 selectionElem.remove();
489 } catch (err) {
490 reject(err);
491 }
492 });
493 });
494 } catch (err) {
495 reject(err);
496 }
497});
diff --git a/src/webview/screenshare.js b/src/webview/screenshare.js
new file mode 100644
index 00000000..2715f2e3
--- /dev/null
+++ b/src/webview/screenshare.js
@@ -0,0 +1,139 @@
1import { desktopCapturer } from 'electron';
2
3const CANCEL_ID = 'desktop-capturer-selection__cancel';
4
5export const screenShareCss = `
6.desktop-capturer-selection {
7 position: fixed;
8 top: 0;
9 left: 0;
10 width: 100%;
11 height: 100vh;
12 background: rgba(30,30,30,.75);
13 color: #fff;
14 z-index: 10000000;
15 display: flex;
16 align-items: center;
17 justify-content: center;
18}
19.desktop-capturer-selection__scroller {
20 width: 100%;
21 max-height: 100vh;
22 overflow-y: auto;
23}
24.desktop-capturer-selection__list {
25 max-width: calc(100% - 100px);
26 margin: 50px;
27 padding: 0;
28 display: flex;
29 flex-wrap: wrap;
30 list-style: none;
31 overflow: hidden;
32 justify-content: center;
33}
34.desktop-capturer-selection__item {
35 display: flex;
36 margin: 4px;
37}
38.desktop-capturer-selection__btn {
39 display: flex;
40 flex-direction: column;
41 align-items: stretch;
42 width: 145px;
43 margin: 0;
44 border: 0;
45 border-radius: 3px;
46 padding: 4px;
47 background: #252626;
48 text-align: left;
49 transition: background-color .15s, box-shadow .15s, color .15s;
50 color: #dedede;
51}
52.desktop-capturer-selection__btn:hover,
53.desktop-capturer-selection__btn:focus {
54 background: rgba(98,100,167,.8);
55 box-shadow: 0 0 4px rgba(0,0,0,0.45), 0 0 2px rgba(0,0,0,0.25);
56 color: #fff;
57}
58.desktop-capturer-selection__thumbnail {
59 width: 100%;
60 height: 81px;
61 object-fit: cover;
62}
63.desktop-capturer-selection__name {
64 margin: 6px 0;
65 white-space: nowrap;
66 text-overflow: ellipsis;
67 text-align: center;
68 overflow: hidden;
69}
70.desktop-capturer-selection__name--cancel {
71 margin: auto 0;
72}
73`;
74
75// Patch getDisplayMedia for screen sharing
76window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve, reject) => {
77 try {
78 const sources = await desktopCapturer.getSources({ types: ['screen', 'window'] });
79
80 const selectionElem = document.createElement('div');
81 selectionElem.classList = 'desktop-capturer-selection';
82 selectionElem.innerHTML = `
83 <div class="desktop-capturer-selection__scroller">
84 <ul class="desktop-capturer-selection__list">
85 ${sources.map(({
86 id, name, thumbnail,
87 }) => `
88 <li class="desktop-capturer-selection__item">
89 <button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
90 <img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
91 <span class="desktop-capturer-selection__name">${name}</span>
92 </button>
93 </li>
94 `).join('')}
95 <li class="desktop-capturer-selection__item">
96 <button class="desktop-capturer-selection__btn" data-id="${CANCEL_ID}" title="Cancel">
97 <span class="desktop-capturer-selection__name desktop-capturer-selection__name--cancel">Cancel</span>
98 </button>
99 </li>
100 </ul>
101 </div>
102 `;
103 document.body.appendChild(selectionElem);
104
105 document.querySelectorAll('.desktop-capturer-selection__btn')
106 .forEach((button) => {
107 button.addEventListener('click', async () => {
108 try {
109 const id = button.getAttribute('data-id');
110 if (id === CANCEL_ID) {
111 reject(new Error('Cancelled by user'));
112 } else {
113 const mediaSource = sources.find(source => source.id === id);
114 if (!mediaSource) {
115 throw new Error(`Source with id ${id} does not exist`);
116 }
117
118 const stream = await window.navigator.mediaDevices.getUserMedia({
119 audio: false,
120 video: {
121 mandatory: {
122 chromeMediaSource: 'desktop',
123 chromeMediaSourceId: mediaSource.id,
124 },
125 },
126 });
127 resolve(stream);
128 }
129 } catch (err) {
130 reject(err);
131 } finally {
132 selectionElem.remove();
133 }
134 });
135 });
136 } catch (err) {
137 reject(err);
138 }
139});