diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-05-28 19:51:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-28 19:51:37 +0200 |
commit | 97408875613c39ea0acc33af41c5caf6a82e7ba1 (patch) | |
tree | 48e9411543fbde7fad0af62b402fadc32bbd4e92 | |
parent | Read CSS assets in recipes as utf8 (#1459) (diff) | |
download | ferdium-app-97408875613c39ea0acc33af41c5caf6a82e7ba1.tar.gz ferdium-app-97408875613c39ea0acc33af41c5caf6a82e7ba1.tar.zst ferdium-app-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.js | 123 | ||||
-rw-r--r-- | src/webview/screenshare.js | 139 |
2 files changed, 141 insertions, 121 deletions
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 6180270b2..5a24effa2 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 */ |
2 | import { ipcRenderer, desktopCapturer } from 'electron'; | 2 | import { ipcRenderer } from 'electron'; |
3 | import { getCurrentWebContents } from '@electron/remote'; | 3 | import { getCurrentWebContents } from '@electron/remote'; |
4 | import path from 'path'; | 4 | import path from 'path'; |
5 | import { autorun, computed, observable } from 'mobx'; | 5 | import { autorun, computed, observable } from 'mobx'; |
@@ -27,75 +27,13 @@ import { switchDict, getSpellcheckerLocaleByFuzzyIdentifier } from './spellcheck | |||
27 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; | 27 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; |
28 | import contextMenu from './contextMenu'; | 28 | import contextMenu from './contextMenu'; |
29 | import './notifications'; | 29 | import './notifications'; |
30 | import { screenShareCss } from './screenshare'; | ||
30 | 31 | ||
31 | import { DEFAULT_APP_SETTINGS } from '../config'; | 32 | import { DEFAULT_APP_SETTINGS } from '../config'; |
32 | import { isDevMode } from '../environment'; | 33 | import { isDevMode } from '../environment'; |
33 | 34 | ||
34 | const debug = require('debug')('Ferdi:Plugin'); | 35 | const debug = require('debug')('Ferdi:Plugin'); |
35 | 36 | ||
36 | const 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 | |||
99 | class RecipeController { | 37 | class RecipeController { |
100 | @observable settings = { | 38 | @observable settings = { |
101 | overrideSpellcheckerLanguage: false, | 39 | overrideSpellcheckerLanguage: false, |
@@ -438,60 +376,3 @@ window.open = (url, frameName, features) => { | |||
438 | if (isDevMode) { | 376 | if (isDevMode) { |
439 | window.log = console.log; | 377 | window.log = console.log; |
440 | } | 378 | } |
441 | |||
442 | // Patch getDisplayMedia for screen sharing | ||
443 | window.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 000000000..2715f2e3e --- /dev/null +++ b/src/webview/screenshare.js | |||
@@ -0,0 +1,139 @@ | |||
1 | import { desktopCapturer } from 'electron'; | ||
2 | |||
3 | const CANCEL_ID = 'desktop-capturer-selection__cancel'; | ||
4 | |||
5 | export 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 | ||
76 | window.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 | }); | ||