From 37e613bb05d4e9bb9d83d9910bd1e46151e7341a Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 24 Oct 2022 18:54:24 +0100 Subject: [PATCH] Fix embedded Element Call screen sharing (#9485) * Fix embedded Element Call screen sharing Makes it a request in each direction rather than a request and reply since replies to requests time out and so can't wait for user interaction. * Fix tests --- src/models/Call.ts | 21 ++++++++----- src/stores/widgets/ElementWidgetActions.ts | 15 +++++++++- test/models/Call-test.ts | 34 +++++++++++++++------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/models/Call.ts b/src/models/Call.ts index ed9e227d24..c3ef2e6775 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -816,7 +816,7 @@ export class ElementCall extends Call { this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); - this.messaging!.on(`action:${ElementWidgetActions.Screenshare}`, this.onScreenshare); + this.messaging!.on(`action:${ElementWidgetActions.ScreenshareRequest}`, this.onScreenshareRequest); } protected async performDisconnection(): Promise { @@ -832,7 +832,7 @@ export class ElementCall extends Call { this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); - this.messaging!.off(`action:${ElementWidgetActions.Screenshare}`, this.onSpotlightLayout); + this.messaging!.off(`action:${ElementWidgetActions.ScreenshareRequest}`, this.onScreenshareRequest); super.setDisconnected(); } @@ -952,19 +952,24 @@ export class ElementCall extends Call { await this.messaging!.transport.reply(ev.detail, {}); // ack }; - private onScreenshare = async (ev: CustomEvent) => { + private onScreenshareRequest = async (ev: CustomEvent) => { ev.preventDefault(); if (PlatformPeg.get().supportsDesktopCapturer()) { + await this.messaging!.transport.reply(ev.detail, { pending: true }); + const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); const [source] = await finished; - await this.messaging!.transport.reply(ev.detail, { - failed: !source, - desktopCapturerSourceId: source, - }); + if (source) { + await this.messaging!.transport.send(ElementWidgetActions.ScreenshareStart, { + desktopCapturerSourceId: source, + }); + } else { + await this.messaging!.transport.send(ElementWidgetActions.ScreenshareStop, {}); + } } else { - await this.messaging!.transport.reply(ev.detail, {}); + await this.messaging!.transport.reply(ev.detail, { pending: false }); } }; } diff --git a/src/stores/widgets/ElementWidgetActions.ts b/src/stores/widgets/ElementWidgetActions.ts index fa60b9ea82..1d0437a2ce 100644 --- a/src/stores/widgets/ElementWidgetActions.ts +++ b/src/stores/widgets/ElementWidgetActions.ts @@ -26,10 +26,23 @@ export enum ElementWidgetActions { MuteVideo = "io.element.mute_video", UnmuteVideo = "io.element.unmute_video", StartLiveStream = "im.vector.start_live_stream", + + // Element Call -> host requesting to start a screenshare + // (ie. expects a ScreenshareStart once the user has picked a source) + // replies with { pending } where pending is true if the host has asked + // the user to choose a window and false if not (ie. if the host isn't + // running within Electron) + ScreenshareRequest = "io.element.screenshare_request", + // host -> Element Call telling EC to start screen sharing with + // the given source + ScreenshareStart = "io.element.screenshare_start", + // host -> Element Call telling EC to stop screen sharing, or that + // the user cancelled when selecting a source after a ScreenshareRequest + ScreenshareStop = "io.element.screenshare_stop", + // Actions for switching layouts TileLayout = "io.element.tile_layout", SpotlightLayout = "io.element.spotlight_layout", - Screenshare = "io.element.screenshare", OpenIntegrationManager = "integration_manager_open", diff --git a/test/models/Call-test.ts b/test/models/Call-test.ts index 3134adf111..df959a44a0 100644 --- a/test/models/Call-test.ts +++ b/test/models/Call-test.ts @@ -820,19 +820,25 @@ describe("ElementCall", () => { await call.connect(); messaging.emit( - `action:${ElementWidgetActions.Screenshare}`, + `action:${ElementWidgetActions.ScreenshareRequest}`, new CustomEvent("widgetapirequest", { detail: {} }), ); - waitFor(() => { + await waitFor(() => { expect(messaging!.transport.reply).toHaveBeenCalledWith( expect.objectContaining({}), - expect.objectContaining({ desktopCapturerSourceId: sourceId }), + expect.objectContaining({ pending: true }), + ); + }); + + await waitFor(() => { + expect(messaging!.transport.send).toHaveBeenCalledWith( + "io.element.screenshare_start", expect.objectContaining({ desktopCapturerSourceId: sourceId }), ); }); }); - it("passes failed if we couldn't get a source id", async () => { + it("sends ScreenshareStop if we couldn't get a source id", async () => { jest.spyOn(Modal, "createDialog").mockReturnValue( { finished: new Promise((r) => r([null])) } as IHandle, ); @@ -841,32 +847,38 @@ describe("ElementCall", () => { await call.connect(); messaging.emit( - `action:${ElementWidgetActions.Screenshare}`, + `action:${ElementWidgetActions.ScreenshareRequest}`, new CustomEvent("widgetapirequest", { detail: {} }), ); - waitFor(() => { + await waitFor(() => { expect(messaging!.transport.reply).toHaveBeenCalledWith( expect.objectContaining({}), - expect.objectContaining({ failed: true }), + expect.objectContaining({ pending: true }), + ); + }); + + await waitFor(() => { + expect(messaging!.transport.send).toHaveBeenCalledWith( + "io.element.screenshare_stop", expect.objectContaining({ }), ); }); }); - it("passes an empty object if we don't support desktop capturer", async () => { + it("replies with pending: false if we don't support desktop capturer", async () => { jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false); await call.connect(); messaging.emit( - `action:${ElementWidgetActions.Screenshare}`, + `action:${ElementWidgetActions.ScreenshareRequest}`, new CustomEvent("widgetapirequest", { detail: {} }), ); - waitFor(() => { + await waitFor(() => { expect(messaging!.transport.reply).toHaveBeenCalledWith( expect.objectContaining({}), - expect.objectContaining({}), + expect.objectContaining({ pending: false }), ); }); });