desktop-capturer.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { BrowserWindow } from 'electron/main';
  2. const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer');
  3. const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
  4. let currentlyRunning: {
  5. options: ElectronInternal.GetSourcesOptions;
  6. getSources: Promise<ElectronInternal.GetSourcesResult[]>;
  7. }[] = [];
  8. // |options.types| can't be empty and must be an array
  9. function isValid (options: Electron.SourcesOptions) {
  10. return Array.isArray(options?.types);
  11. }
  12. export { isDisplayMediaSystemPickerAvailable };
  13. export async function getSources (args: Electron.SourcesOptions) {
  14. if (!isValid(args)) throw new Error('Invalid options');
  15. const resizableValues = new Map();
  16. if (process.platform === 'darwin') {
  17. // Fix for bug in ScreenCaptureKit that modifies a window's styleMask the first time
  18. // it captures a non-resizable window. We record each non-resizable window's styleMask,
  19. // and we restore modified styleMasks later, after the screen capture.
  20. for (const win of BrowserWindow.getAllWindows()) {
  21. resizableValues.set([win.id], win.resizable);
  22. }
  23. }
  24. const captureWindow = args.types.includes('window');
  25. const captureScreen = args.types.includes('screen');
  26. const { thumbnailSize = { width: 150, height: 150 } } = args;
  27. const { fetchWindowIcons = false } = args;
  28. const options = {
  29. captureWindow,
  30. captureScreen,
  31. thumbnailSize,
  32. fetchWindowIcons
  33. };
  34. for (const running of currentlyRunning) {
  35. if (deepEqual(running.options, options)) {
  36. // If a request is currently running for the same options
  37. // return that promise
  38. return running.getSources;
  39. }
  40. }
  41. const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
  42. let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
  43. const stopRunning = () => {
  44. if (capturer) {
  45. delete capturer._onerror;
  46. delete capturer._onfinished;
  47. capturer = null;
  48. if (process.platform === 'darwin') {
  49. for (const win of BrowserWindow.getAllWindows()) {
  50. if (resizableValues.has(win.id)) {
  51. win.resizable = resizableValues.get(win.id);
  52. }
  53. };
  54. }
  55. }
  56. // Remove from currentlyRunning once we resolve or reject
  57. currentlyRunning = currentlyRunning.filter(running => running.options !== options);
  58. };
  59. capturer._onerror = (error: string) => {
  60. stopRunning();
  61. reject(error);
  62. };
  63. capturer._onfinished = (sources: Electron.DesktopCapturerSource[]) => {
  64. stopRunning();
  65. resolve(sources);
  66. };
  67. capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
  68. });
  69. currentlyRunning.push({
  70. options,
  71. getSources
  72. });
  73. return getSources;
  74. }