mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Adding support for Windows to use navigator.mediaDevices.getDisplayMedia (#2270)
This commit is contained in:
parent
99598737df
commit
47caf65b9e
@ -11,6 +11,10 @@ jest.mock('../src/common/env', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const mockThumbnail = {
|
||||
toDataURL: () => {},
|
||||
};
|
||||
|
||||
describe('screen picker', () => {
|
||||
const keyCode = {
|
||||
pageDown: { keyCode: 34 },
|
||||
@ -40,26 +44,26 @@ describe('screen picker', () => {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
selectedSource: {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
|
||||
@ -85,7 +89,7 @@ describe('screen picker', () => {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
};
|
||||
const customSelector = 'button.ScreenPicker-share-button';
|
||||
wrapper.setState({ selectedSource });
|
||||
@ -101,7 +105,7 @@ describe('screen picker', () => {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
selectedSource: undefined,
|
||||
@ -119,14 +123,14 @@ describe('screen picker', () => {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
selectedSource: {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const applicationScreenStateMock = {
|
||||
@ -135,14 +139,14 @@ describe('screen picker', () => {
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
selectedSource: {
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
|
||||
@ -175,7 +179,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const customSelector = '.ScreenPicker-item-container';
|
||||
@ -185,19 +189,19 @@ describe('screen picker', () => {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
selectedSource: {
|
||||
@ -205,7 +209,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
wrapper.setState(applicationScreenStateMock);
|
||||
@ -222,7 +226,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const customSelector = '.ScreenPicker-item-container';
|
||||
@ -232,19 +236,19 @@ describe('screen picker', () => {
|
||||
display_id: '0',
|
||||
id: '0',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
selectedSource: {
|
||||
@ -252,7 +256,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
wrapper.setState(applicationScreenStateMock);
|
||||
@ -289,7 +293,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -306,7 +310,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -323,7 +327,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -340,7 +344,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -357,7 +361,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -374,7 +378,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '2',
|
||||
name: 'Application screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -389,7 +393,7 @@ describe('screen picker', () => {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Application screen 1',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
wrapper.setState(stateMock);
|
||||
@ -418,7 +422,7 @@ describe('screen picker', () => {
|
||||
fileName: 'fullscreen',
|
||||
id: '0',
|
||||
name: 'Application screen 0',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(React.createElement(ScreenPicker));
|
||||
@ -437,19 +441,19 @@ describe('screen picker', () => {
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application Screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '2',
|
||||
name: 'Application Screen 2',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '3',
|
||||
name: 'Application Screen 3',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -466,10 +470,20 @@ describe('screen picker', () => {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '2',
|
||||
id: '2',
|
||||
name: 'Screen 2',
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '3',
|
||||
id: '3',
|
||||
name: 'screen 3',
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{ display_id: '2', id: '2', name: 'Screen 2', thumbnail: undefined },
|
||||
{ display_id: '3', id: '3', name: 'screen 3', thumbnail: undefined },
|
||||
],
|
||||
};
|
||||
wrapper.setState(entireScreenStateMock);
|
||||
@ -486,10 +500,20 @@ describe('screen picker', () => {
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '2',
|
||||
name: 'Screen 2',
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '3',
|
||||
name: 'screen 3',
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{ display_id: '', id: '2', name: 'Screen 2', thumbnail: undefined },
|
||||
{ display_id: '', id: '3', name: 'screen 3', thumbnail: undefined },
|
||||
],
|
||||
};
|
||||
env.isWindowsOS = true;
|
||||
@ -509,10 +533,20 @@ describe('screen picker', () => {
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '2',
|
||||
name: 'Screen 2',
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '3',
|
||||
name: 'screen 3',
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{ display_id: '', id: '2', name: 'Screen 2', thumbnail: undefined },
|
||||
{ display_id: '', id: '3', name: 'screen 3', thumbnail: undefined },
|
||||
],
|
||||
};
|
||||
env.isWindowsOS = false;
|
||||
@ -531,13 +565,13 @@ describe('screen picker', () => {
|
||||
display_id: '1',
|
||||
id: '1',
|
||||
name: 'Entire screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
{
|
||||
display_id: '',
|
||||
id: '1',
|
||||
name: 'Application screen',
|
||||
thumbnail: undefined,
|
||||
thumbnail: mockThumbnail,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -1,19 +1,103 @@
|
||||
import { session } from 'electron';
|
||||
import { desktopCapturer, ipcMain, session } from 'electron';
|
||||
import { NOTIFICATION_WINDOW_TITLE } from '../common/api-interface';
|
||||
import { isDevEnv, isMac } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
import {
|
||||
ICustomBrowserWindowConstructorOpts,
|
||||
windowHandler,
|
||||
} from './window-handler';
|
||||
import { createComponentWindow, windowExists } from './window-utils';
|
||||
|
||||
/**
|
||||
* This is currently supported only on macOS 15+.
|
||||
* setDisplayMediaRequestHandler injects into navigator.mediaDevices.getDisplayMedia().
|
||||
* With the macOS-only option { useSystemPicker: true },
|
||||
* everyting is handled natively by the OS.
|
||||
* For MacOS 15+ the { useSystemPicker: true } overrides the code set in the handler,
|
||||
* and uses the native implementation.
|
||||
*
|
||||
* For all other OSes and versions, the regular screen share flow will be used.
|
||||
* But for other versions and OSes, the code is executed.
|
||||
*/
|
||||
export const setDisplayMediaRequestHandler = () => {
|
||||
const { defaultSession } = session;
|
||||
|
||||
defaultSession.setDisplayMediaRequestHandler(
|
||||
async (_request, _callback) => {
|
||||
// TODO - Add support for Windows.
|
||||
async (_request, callback) => {
|
||||
logger.info('display-media-request-handler: getting sources');
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen', 'window'],
|
||||
thumbnailSize: {
|
||||
height: 150,
|
||||
width: 150,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedSources = sources.filter(
|
||||
(source) => source.name !== NOTIFICATION_WINDOW_TITLE,
|
||||
);
|
||||
|
||||
const browserWindowOptions: ICustomBrowserWindowConstructorOpts =
|
||||
windowHandler.getWindowOpts(
|
||||
{
|
||||
alwaysOnTop: true,
|
||||
autoHideMenuBar: true,
|
||||
frame: false,
|
||||
modal: false,
|
||||
height: isMac ? 519 : 523,
|
||||
width: 580,
|
||||
show: false,
|
||||
fullscreenable: false,
|
||||
},
|
||||
{
|
||||
devTools: isDevEnv,
|
||||
},
|
||||
);
|
||||
const screenPickerWindow = createComponentWindow(
|
||||
'screen-picker',
|
||||
browserWindowOptions,
|
||||
);
|
||||
|
||||
screenPickerWindow.webContents.once('did-finish-load', () => {
|
||||
if (!screenPickerWindow || !windowExists(screenPickerWindow)) {
|
||||
return;
|
||||
}
|
||||
screenPickerWindow.webContents.send('screen-picker-data', {
|
||||
sources: updatedSources,
|
||||
});
|
||||
});
|
||||
|
||||
const mainWebContents = windowHandler.getMainWebContents();
|
||||
if (!mainWebContents) {
|
||||
return;
|
||||
}
|
||||
mainWebContents.send('screen-picker-data', updatedSources);
|
||||
|
||||
ipcMain.on(
|
||||
'screen-source-select',
|
||||
(_event, source: Electron.DesktopCapturerSource) => {
|
||||
if (source) {
|
||||
windowHandler.drawScreenShareIndicatorFrame(source);
|
||||
}
|
||||
logger.info('display-media-request-handler: source selected', source);
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.once(
|
||||
'screen-source-selected',
|
||||
(_event, source: Electron.DesktopCapturerSource) => {
|
||||
screenPickerWindow.close();
|
||||
logger.info(
|
||||
'display-media-request-handler: source to be shared',
|
||||
source,
|
||||
);
|
||||
if (!source) {
|
||||
windowHandler.closeScreenSharingIndicator();
|
||||
/**
|
||||
* Passing the empty stream crashes the main process,
|
||||
* but passing an empty callback throws an AbortError.
|
||||
*/
|
||||
// @ts-ignore
|
||||
callback();
|
||||
} else {
|
||||
callback({ video: source });
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
{ useSystemPicker: true },
|
||||
);
|
||||
|
@ -2419,6 +2419,36 @@ export class WindowHandler {
|
||||
app.exit();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns constructor opts for the browser window
|
||||
*
|
||||
* @param windowOpts {Electron.BrowserWindowConstructorOptions}
|
||||
* @param webPreferences {Electron.WebPreferences}
|
||||
*/
|
||||
public getWindowOpts(
|
||||
windowOpts: Electron.BrowserWindowConstructorOptions,
|
||||
webPreferences: Electron.WebPreferences,
|
||||
): ICustomBrowserWindowConstructorOpts {
|
||||
const defaultPreferencesOpts = {
|
||||
...{
|
||||
sandbox: IS_SAND_BOXED,
|
||||
nodeIntegration: IS_NODE_INTEGRATION_ENABLED,
|
||||
contextIsolation: this.contextIsolation,
|
||||
backgroundThrottling: this.backgroundThrottling,
|
||||
enableRemoteModule: true,
|
||||
disableBlinkFeatures: AUX_CLICK,
|
||||
},
|
||||
...webPreferences,
|
||||
};
|
||||
const defaultWindowOpts = {
|
||||
alwaysOnTop: false,
|
||||
webPreferences: defaultPreferencesOpts,
|
||||
winKey: getGuid(),
|
||||
};
|
||||
|
||||
return { ...defaultWindowOpts, ...windowOpts };
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for app load timeouts and reloads if required
|
||||
*/
|
||||
@ -2507,36 +2537,6 @@ export class WindowHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns constructor opts for the browser window
|
||||
*
|
||||
* @param windowOpts {Electron.BrowserWindowConstructorOptions}
|
||||
* @param webPreferences {Electron.WebPreferences}
|
||||
*/
|
||||
private getWindowOpts(
|
||||
windowOpts: Electron.BrowserWindowConstructorOptions,
|
||||
webPreferences: Electron.WebPreferences,
|
||||
): ICustomBrowserWindowConstructorOpts {
|
||||
const defaultPreferencesOpts = {
|
||||
...{
|
||||
sandbox: IS_SAND_BOXED,
|
||||
nodeIntegration: IS_NODE_INTEGRATION_ENABLED,
|
||||
contextIsolation: this.contextIsolation,
|
||||
backgroundThrottling: this.backgroundThrottling,
|
||||
enableRemoteModule: true,
|
||||
disableBlinkFeatures: AUX_CLICK,
|
||||
},
|
||||
...webPreferences,
|
||||
};
|
||||
const defaultWindowOpts = {
|
||||
alwaysOnTop: false,
|
||||
webPreferences: defaultPreferencesOpts,
|
||||
winKey: getGuid(),
|
||||
};
|
||||
|
||||
return { ...defaultWindowOpts, ...windowOpts };
|
||||
}
|
||||
|
||||
/**
|
||||
* getUserAgent retrieves current window user-agent and updates it
|
||||
* depending on global config setup
|
||||
|
@ -172,7 +172,7 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
<div className='ScreenPicker-screen-section-box'>
|
||||
<img
|
||||
className='ScreenPicker-img-wrapper'
|
||||
src={source.thumbnail as any}
|
||||
src={source.thumbnail.toDataURL()}
|
||||
alt='thumbnail image'
|
||||
/>
|
||||
</div>
|
||||
@ -190,7 +190,7 @@ export default class ScreenPicker extends React.Component<{}, IState> {
|
||||
<div className='ScreenPicker-screen-section-box'>
|
||||
<img
|
||||
className='ScreenPicker-img-wrapper'
|
||||
src={source.thumbnail as any}
|
||||
src={source.thumbnail.toDataURL()}
|
||||
alt='thumbnail image'
|
||||
/>
|
||||
</div>
|
||||
|
@ -139,16 +139,9 @@ export const getSource = async (
|
||||
}
|
||||
}
|
||||
|
||||
const updatedSources = sources
|
||||
.filter((source) => source.name !== NOTIFICATION_WINDOW_TITLE)
|
||||
.map((source) => {
|
||||
return {
|
||||
...source,
|
||||
...{
|
||||
thumbnail: source.thumbnail.toDataURL(),
|
||||
},
|
||||
};
|
||||
});
|
||||
const updatedSources = sources.filter(
|
||||
(source) => source.name !== NOTIFICATION_WINDOW_TITLE,
|
||||
);
|
||||
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.openScreenPickerWindow,
|
||||
|
Loading…
Reference in New Issue
Block a user