diff --git a/data/theme/gnome-shell-sass/widgets/_panel.scss b/data/theme/gnome-shell-sass/widgets/_panel.scss index 3fc57eb08..fd3e7d2da 100644 --- a/data/theme/gnome-shell-sass/widgets/_panel.scss +++ b/data/theme/gnome-shell-sass/widgets/_panel.scss @@ -40,7 +40,14 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px $screenshot_ui_button_red; + } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px $warning_color; + StBoxLayout { margin: 0 $base_padding; } + } + &.screen-recording-indicator, + &.screen-sharing-indicator { StBoxLayout { spacing: $base_padding; } @@ -66,6 +73,9 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15); } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.15); + } } &:hover { @@ -80,6 +90,9 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1); } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.1); + } } &:active:hover, &:overview:hover, &:focus:hover, &:checked:hover { @@ -94,6 +107,9 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2); } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.2); + } } // status area icons @@ -137,6 +153,9 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15); } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.15); + } } &:hover { @@ -151,6 +170,9 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1); } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.1); + } } &:active:hover, &:overview:hover, &:focus:hover, &:checked:hover { @@ -165,6 +187,9 @@ $panel_transition_duration: 250ms; // same as the overview transition duration &.screen-recording-indicator { box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2); } + &.screen-sharing-indicator { + box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.2); + } } } } diff --git a/js/ui/panel.js b/js/ui/panel.js index 5bf469b69..992cdd8e9 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -441,6 +441,7 @@ const PANEL_ITEM_IMPLEMENTATIONS = { 'keyboard': imports.ui.status.keyboard.InputSourceIndicator, 'dwellClick': imports.ui.status.dwellClick.DwellClickIndicator, 'screenRecording': imports.ui.status.remoteAccess.ScreenRecordingIndicator, + 'screenSharing': imports.ui.status.remoteAccess.ScreenSharingIndicator, }; var Panel = GObject.registerClass( diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js index 5712b4d24..acf584d8c 100644 --- a/js/ui/sessionMode.js +++ b/js/ui/sessionMode.js @@ -94,7 +94,7 @@ const _modes = { panel: { left: ['activities', 'appMenu'], center: ['dateMenu'], - right: ['screenRecording', 'dwellClick', 'a11y', 'keyboard', 'aggregateMenu'], + right: ['screenRecording', 'screenSharing', 'dwellClick', 'a11y', 'keyboard', 'aggregateMenu'], }, }, }; diff --git a/js/ui/status/remoteAccess.js b/js/ui/status/remoteAccess.js index a3dfd652c..08436d7d1 100644 --- a/js/ui/status/remoteAccess.js +++ b/js/ui/status/remoteAccess.js @@ -1,11 +1,10 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -/* exported RemoteAccessApplet, ScreenRecordingIndicator */ +/* exported RemoteAccessApplet, ScreenRecordingIndicator, ScreenSharingIndicator */ const { Atk, Clutter, GLib, GObject, Meta, St } = imports.gi; const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; // Minimum amount of time the shared indicator is visible (in micro seconds) const MIN_SHARED_INDICATOR_VISIBLE_TIME_US = 5 * GLib.TIME_SPAN_SECOND; @@ -21,97 +20,30 @@ class RemoteAccessApplet extends PanelMenu.SystemIndicator { return; this._handles = new Set(); - this._sharedIndicator = null; - this._recordingIndicator = null; - this._menuSection = null; + + this._indicator = this._addIndicator(); + this._indicator.set({ + style_class: 'screencast-indicator', + icon_name: 'media-record-symbolic', + }); controller.connect('new-handle', (o, handle) => { this._onNewHandle(handle); }); - } - - _ensureControls() { - if (this._sharedIndicator && this._recordingIndicator) - return; - - this._sharedIndicator = this._addIndicator(); - this._sharedIndicator.visible = false; - this._sharedIndicator.icon_name = 'screen-shared-symbolic'; - this._sharedIndicator.add_style_class_name('remote-access-indicator'); - - this._sharedItem = - new PopupMenu.PopupSubMenuMenuItem(_("Screen is Being Shared"), - true); - this._sharedItem.menu.addAction(_("Turn off"), - () => { - for (let handle of this._handles) { - if (!handle.is_recording) - handle.stop(); - } - }); - this._sharedItem.icon.icon_name = 'screen-shared-symbolic'; - this.menu.addMenuItem(this._sharedItem); - - this._recordingIndicator = this._addIndicator(); - this._recordingIndicator.icon_name = 'media-record-symbolic'; - this._recordingIndicator.add_style_class_name('screencast-indicator'); - } - - _isScreenShared() { - return [...this._handles].some(handle => !handle.is_recording); + this._sync(); } _isRecording() { - const recordingHandles = - [...this._handles].filter(handle => handle.is_recording); - // Screenshot UI screencasts have their own panel, so don't show this // indicator if there's only a screenshot UI screencast. if (Main.screenshotUI.screencast_in_progress) - return recordingHandles.length > 1; + return this._handles.size > 1; - return recordingHandles.length > 0; - } - - _hideSharedIndicator() { - this._sharedIndicator.visible = false; - delete this._hideSharedIndicatorId; - return GLib.SOURCE_REMOVE; + return this._handles.size > 0; } _sync() { - if (this._hideSharedIndicatorId) { - GLib.source_remove(this._hideSharedIndicatorId); - delete this._hideSharedIndicatorId; - } - - if (this._isScreenShared()) { - if (!this._sharedIndicator.visible) - this._visibleTimeUs = GLib.get_monotonic_time(); - this._sharedIndicator.visible = true; - this._sharedItem.visible = true; - } else { - if (this._sharedIndicator.visible) { - const currentTimeUs = GLib.get_monotonic_time(); - const timeSinceVisibleUs = currentTimeUs - this._visibleTimeUs; - - if (timeSinceVisibleUs >= MIN_SHARED_INDICATOR_VISIBLE_TIME_US) { - this._hideSharedIndicator(); - } else { - const timeUntilHideUs = - MIN_SHARED_INDICATOR_VISIBLE_TIME_US - timeSinceVisibleUs; - this._hideSharedIndicatorId = - GLib.timeout_add( - GLib.PRIORITY_DEFAULT, - timeUntilHideUs / GLib.TIME_SPAN_MILLISECOND, - this._hideSharedIndicator.bind(this)); - } - } - - this._sharedItem.visible = false; - } - - this._recordingIndicator.visible = this._isRecording(); + this._indicator.visible = this._isRecording(); } _onStopped(handle) { @@ -120,20 +52,12 @@ class RemoteAccessApplet extends PanelMenu.SystemIndicator { } _onNewHandle(handle) { - // We can't possibly know about all types of screen sharing on X11, so - // showing these controls on X11 might give a false sense of security. - // Thus, only enable these controls when using Wayland, where we are - // in control of sharing. - // - // We still want to show screen recordings though, to indicate when - // the built in screen recorder is active, no matter the session type. - if (!Meta.is_wayland_compositor() && !handle.is_recording) + if (!handle.is_recording) return; this._handles.add(handle); handle.connect('stopped', this._onStopped.bind(this)); - this._ensureControls(); this._sync(); } }); @@ -207,3 +131,101 @@ var ScreenRecordingIndicator = GObject.registerClass({ } } }); + +var ScreenSharingIndicator = GObject.registerClass({ + Signals: {'menu-set': {}}, +}, class ScreenSharingIndicator extends PanelMenu.ButtonBox { + _init() { + super._init({ + reactive: true, + can_focus: true, + track_hover: true, + accessible_name: _('Stop Screen Sharing'), + accessible_role: Atk.Role.PUSH_BUTTON, + }); + this.add_style_class_name('screen-sharing-indicator'); + + this._box = new St.BoxLayout(); + this.add_child(this._box); + + let icon = new St.Icon({icon_name: 'screen-shared-symbolic'}); + this._box.add_child(icon); + + icon = new St.Icon({icon_name: 'window-close-symbolic'}); + this._box.add_child(icon); + + this._controller = global.backend.get_remote_access_controller(); + + this._handles = new Set(); + + this._controller?.connect('new-handle', + (o, handle) => this._onNewHandle(handle)); + + this._sync(); + } + + _onNewHandle(handle) { + // We can't possibly know about all types of screen sharing on X11, so + // showing these controls on X11 might give a false sense of security. + // Thus, only enable these controls when using Wayland, where we are + // in control of sharing. + if (!Meta.is_wayland_compositor()) + return; + + if (handle.isRecording) + return; + + this._handles.add(handle); + handle.connect('stopped', () => { + this._handles.delete(handle); + this._sync(); + }); + this._sync(); + } + + vfunc_event(event) { + if (event.type() === Clutter.EventType.TOUCH_BEGIN || + event.type() === Clutter.EventType.BUTTON_PRESS) + this._stopSharing(); + + return Clutter.EVENT_PROPAGATE; + } + + _stopSharing() { + for (const handle of this._handles) + handle.stop(); + } + + _hideIndicator() { + this.hide(); + delete this._hideIndicatorId; + return GLib.SOURCE_REMOVE; + } + + _sync() { + if (this._hideIndicatorId) { + GLib.source_remove(this._hideIndicatorId); + delete this._hideIndicatorId; + } + + if (this._handles.size > 0) { + if (!this.visible) + this._visibleTimeUs = GLib.get_monotonic_time(); + this.show(); + } else if (this.visible) { + const currentTimeUs = GLib.get_monotonic_time(); + const timeSinceVisibleUs = currentTimeUs - this._visibleTimeUs; + + if (timeSinceVisibleUs >= MIN_SHARED_INDICATOR_VISIBLE_TIME_US) { + this._hideIndicator(); + } else { + const timeUntilHideUs = + MIN_SHARED_INDICATOR_VISIBLE_TIME_US - timeSinceVisibleUs; + this._hideIndicatorId = + GLib.timeout_add(GLib.PRIORITY_DEFAULT, + timeUntilHideUs / GLib.TIME_SPAN_MILLISECOND, + () => this._hideIndicator()); + } + } + } +});