diff --git a/res/css/_components.scss b/res/css/_components.scss index a6e1f81583..3fc7468539 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -106,6 +106,7 @@ @import "./views/elements/_AddressTile.scss"; @import "./views/elements/_DesktopBuildsNotice.scss"; @import "./views/elements/_DirectorySearchBox.scss"; +@import "./views/elements/_DesktopCapturerSourcePicker.scss"; @import "./views/elements/_Dropdown.scss"; @import "./views/elements/_EditableItemList.scss"; @import "./views/elements/_ErrorBoundary.scss"; diff --git a/res/css/views/elements/_DesktopCapturerSourcePicker.scss b/res/css/views/elements/_DesktopCapturerSourcePicker.scss new file mode 100644 index 0000000000..69dde5925e --- /dev/null +++ b/res/css/views/elements/_DesktopCapturerSourcePicker.scss @@ -0,0 +1,72 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_desktopCapturerSourcePicker { + overflow: hidden; +} + +.mx_desktopCapturerSourcePicker_tabLabels { + display: flex; + padding: 0 0 8px 0; +} + +.mx_desktopCapturerSourcePicker_tabLabel, +.mx_desktopCapturerSourcePicker_tabLabel_selected { + width: 100%; + text-align: center; + border-radius: 8px; + padding: 8px 0; + font-size: $font-13px; +} + +.mx_desktopCapturerSourcePicker_tabLabel_selected { + background-color: $tab-label-active-bg-color; + color: $tab-label-active-fg-color; +} + +.mx_desktopCapturerSourcePicker_panel { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; + height: 500px; + overflow: overlay; +} + +.mx_desktopCapturerSourcePicker_stream_button { + display: flex; + flex-direction: column; + margin: 8px; + border-radius: 4px; +} + +.mx_desktopCapturerSourcePicker_stream_button:hover, +.mx_desktopCapturerSourcePicker_stream_button:focus { + background: $roomtile-selected-bg-color; +} + +.mx_desktopCapturerSourcePicker_stream_thumbnail { + margin: 4px; + width: 312px; +} + +.mx_desktopCapturerSourcePicker_stream_name { + margin: 0 4px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 312px; +} diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 9dee7594ea..b5d4ed155f 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -82,6 +82,7 @@ import CountlyAnalytics from "./CountlyAnalytics"; import {UIFeature} from "./settings/UIFeature"; import { CallError } from "matrix-js-sdk/src/webrtc/call"; import { logger } from 'matrix-js-sdk/src/logger'; +import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker" import { Action } from './dispatcher/actions'; import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; @@ -572,7 +573,15 @@ export default class CallHandler { }); return; } - call.placeScreenSharingCall(remoteElement, localElement); + + call.placeScreenSharingCall( + remoteElement, + localElement, + async () : Promise => { + const {finished} = Modal.createDialog(DesktopCapturerSourcePicker); + const [source] = await finished; + return source; + }); } else { console.error("Unknown conf call type: %s", type); } diff --git a/src/components/views/elements/DesktopCapturerSourcePicker.tsx b/src/components/views/elements/DesktopCapturerSourcePicker.tsx new file mode 100644 index 0000000000..e53683b0ef --- /dev/null +++ b/src/components/views/elements/DesktopCapturerSourcePicker.tsx @@ -0,0 +1,166 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { _t } from '../../../languageHandler'; +import BaseDialog from "..//dialogs/BaseDialog" +import AccessibleButton from './AccessibleButton'; +import {getDesktopCapturerSources} from "matrix-js-sdk/src/webrtc/call"; + +export interface DesktopCapturerSource { + id: string; + name: string; + thumbnailURL; +} + +export enum Tabs { + Screens = "screens", + Windows = "windows", +} + +export interface DesktopCapturerSourceIProps { + source: DesktopCapturerSource; + onSelect(source: DesktopCapturerSource): void; +} + +export class ExistingSource extends React.Component { + constructor(props) { + super(props); + } + + onClick = (ev) => { + this.props.onSelect(this.props.source); + } + + render() { + return ( + + + {this.props.source.name} + + ); + } +} + +export interface DesktopCapturerSourcePickerIState { + selectedTab: Tabs; + sources: Array; +} +export interface DesktopCapturerSourcePickerIProps { + onFinished(source: DesktopCapturerSource): void; +} + +export default class DesktopCapturerSourcePicker extends React.Component< + DesktopCapturerSourcePickerIProps, + DesktopCapturerSourcePickerIState + > { + interval; + + constructor(props) { + super(props); + + this.state = { + selectedTab: Tabs.Screens, + sources: [], + }; + } + + componentDidMount() { + // We update the sources every 500ms to get newer thumbnails + this.interval = setInterval(async () => { + this.setState({ + sources: await getDesktopCapturerSources(), + }); + }, 500); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + onSelect = (source) => { + this.props.onFinished(source); + } + + onScreensClick = (ev) => { + this.setState({selectedTab: Tabs.Screens}); + } + + onWindowsClick = (ev) => { + this.setState({selectedTab: Tabs.Windows}); + } + + onCloseClick = (ev) => { + this.props.onFinished(null); + } + + render() { + let sources; + if (this.state.selectedTab === Tabs.Screens) { + sources = this.state.sources + .filter((source) => { + return source.id.startsWith("screen"); + }) + .map((source) => { + return ; + }); + } else { + sources = this.state.sources + .filter((source) => { + return source.id.startsWith("window"); + }) + .map((source) => { + return ; + }); + } + + const buttonStyle = "mx_desktopCapturerSourcePicker_tabLabel"; + const screensButtonStyle = buttonStyle + ((this.state.selectedTab === Tabs.Screens) ? "_selected" : ""); + const windowsButtonStyle = buttonStyle + ((this.state.selectedTab === Tabs.Windows) ? "_selected" : ""); + + return ( + +
+ + {_t("Screens")} + + + {_t("Windows")} + +
+
+ { sources } +
+
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fcd8202db1..06aeec75fd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1852,6 +1852,9 @@ "Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages", "This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files", "This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages", + "Share your screen": "Share your screen", + "Screens": "Screens", + "Windows": "Windows", "Join": "Join", "No results": "No results", "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.",