2018-07-20 14:50:50 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2022-07-27 10:29:26 +00:00
|
|
|
/* exported RemoteAccessApplet, ScreenRecordingIndicator, ScreenSharingIndicator */
|
2018-07-20 14:50:50 +00:00
|
|
|
|
2023-06-08 04:52:46 +00:00
|
|
|
const Atk = imports.gi.Atk;
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
const GLib = imports.gi.GLib;
|
|
|
|
const GObject = imports.gi.GObject;
|
|
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const St = imports.gi.St;
|
2018-07-20 14:50:50 +00:00
|
|
|
|
2021-08-17 07:58:23 +00:00
|
|
|
const Main = imports.ui.main;
|
2018-07-20 14:50:50 +00:00
|
|
|
const PanelMenu = imports.ui.panelMenu;
|
2022-07-27 10:54:24 +00:00
|
|
|
const {SystemIndicator} = imports.ui.quickSettings;
|
2018-07-20 14:50:50 +00:00
|
|
|
|
2022-01-27 09:48:59 +00:00
|
|
|
// Minimum amount of time the shared indicator is visible (in micro seconds)
|
|
|
|
const MIN_SHARED_INDICATOR_VISIBLE_TIME_US = 5 * GLib.TIME_SPAN_SECOND;
|
|
|
|
|
2019-10-28 18:35:33 +00:00
|
|
|
var RemoteAccessApplet = GObject.registerClass(
|
2022-07-27 10:54:24 +00:00
|
|
|
class RemoteAccessApplet extends SystemIndicator {
|
2019-07-16 09:24:13 +00:00
|
|
|
_init() {
|
|
|
|
super._init();
|
2018-07-20 14:50:50 +00:00
|
|
|
|
2020-04-21 16:04:56 +00:00
|
|
|
let controller = global.backend.get_remote_access_controller();
|
2018-07-20 14:50:50 +00:00
|
|
|
|
|
|
|
if (!controller)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._handles = new Set();
|
2022-07-27 10:29:26 +00:00
|
|
|
|
|
|
|
this._indicator = this._addIndicator();
|
2022-11-18 17:50:43 +00:00
|
|
|
this._indicator.icon_name = 'media-record-symbolic';
|
2023-07-28 09:12:49 +00:00
|
|
|
this._indicator.add_style_class_name('privacy-indicator');
|
2018-07-20 14:50:50 +00:00
|
|
|
|
2019-08-20 00:20:08 +00:00
|
|
|
controller.connect('new-handle', (o, handle) => {
|
2018-07-20 14:50:50 +00:00
|
|
|
this._onNewHandle(handle);
|
|
|
|
});
|
2022-07-27 10:29:26 +00:00
|
|
|
this._sync();
|
2020-04-21 16:06:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_isRecording() {
|
2022-02-09 07:00:07 +00:00
|
|
|
// 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)
|
2022-07-27 10:29:26 +00:00
|
|
|
return this._handles.size > 1;
|
2022-02-09 07:00:07 +00:00
|
|
|
|
2022-07-27 10:29:26 +00:00
|
|
|
return this._handles.size > 0;
|
2022-01-27 09:48:59 +00:00
|
|
|
}
|
|
|
|
|
2018-07-20 14:50:50 +00:00
|
|
|
_sync() {
|
2022-07-27 10:29:26 +00:00
|
|
|
this._indicator.visible = this._isRecording();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2018-07-20 14:50:50 +00:00
|
|
|
|
|
|
|
_onStopped(handle) {
|
|
|
|
this._handles.delete(handle);
|
|
|
|
this._sync();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2018-07-20 14:50:50 +00:00
|
|
|
|
|
|
|
_onNewHandle(handle) {
|
2022-07-27 10:29:26 +00:00
|
|
|
if (!handle.is_recording)
|
2020-10-13 14:28:51 +00:00
|
|
|
return;
|
|
|
|
|
2018-07-20 14:50:50 +00:00
|
|
|
this._handles.add(handle);
|
|
|
|
handle.connect('stopped', this._onStopped.bind(this));
|
|
|
|
|
2020-04-21 16:06:06 +00:00
|
|
|
this._sync();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2022-02-08 16:18:01 +00:00
|
|
|
|
|
|
|
var ScreenRecordingIndicator = GObject.registerClass({
|
|
|
|
Signals: { 'menu-set': {} },
|
|
|
|
}, class ScreenRecordingIndicator extends PanelMenu.ButtonBox {
|
|
|
|
_init() {
|
|
|
|
super._init({
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
track_hover: true,
|
|
|
|
accessible_name: _('Stop Screencast'),
|
|
|
|
accessible_role: Atk.Role.PUSH_BUTTON,
|
|
|
|
});
|
|
|
|
this.add_style_class_name('screen-recording-indicator');
|
|
|
|
|
|
|
|
this._box = new St.BoxLayout();
|
|
|
|
this.add_child(this._box);
|
|
|
|
|
|
|
|
this._label = new St.Label({
|
|
|
|
text: '0:00',
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
this._box.add_child(this._label);
|
|
|
|
|
2023-06-19 19:52:29 +00:00
|
|
|
this._icon = new St.Icon({icon_name: 'screencast-stop-symbolic'});
|
2022-02-08 16:18:01 +00:00
|
|
|
this._box.add_child(this._icon);
|
|
|
|
|
|
|
|
this.hide();
|
|
|
|
Main.screenshotUI.connect(
|
|
|
|
'notify::screencast-in-progress',
|
|
|
|
this._onScreencastInProgressChanged.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_event(event) {
|
|
|
|
if (event.type() === Clutter.EventType.TOUCH_BEGIN ||
|
|
|
|
event.type() === Clutter.EventType.BUTTON_PRESS)
|
|
|
|
Main.screenshotUI.stopScreencast();
|
|
|
|
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
_updateLabel() {
|
|
|
|
const minutes = this._secondsPassed / 60;
|
|
|
|
const seconds = this._secondsPassed % 60;
|
|
|
|
this._label.text = '%d:%02d'.format(minutes, seconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onScreencastInProgressChanged() {
|
|
|
|
if (Main.screenshotUI.screencast_in_progress) {
|
|
|
|
this.show();
|
|
|
|
|
|
|
|
this._secondsPassed = 0;
|
|
|
|
this._updateLabel();
|
|
|
|
|
|
|
|
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
|
|
|
|
this._secondsPassed += 1;
|
|
|
|
this._updateLabel();
|
|
|
|
return GLib.SOURCE_CONTINUE;
|
|
|
|
});
|
|
|
|
GLib.Source.set_name_by_id(
|
|
|
|
this._timeoutId, '[gnome-shell] screen recording indicator tick');
|
|
|
|
} else {
|
|
|
|
this.hide();
|
|
|
|
|
|
|
|
GLib.source_remove(this._timeoutId);
|
|
|
|
delete this._timeoutId;
|
|
|
|
|
|
|
|
delete this._secondsPassed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2022-07-27 10:29:26 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-19 19:52:29 +00:00
|
|
|
icon = new St.Icon({icon_name: 'screencast-stop-symbolic'});
|
2022-07-27 10:29:26 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|