mirror of
https://gitlab.com/thebiblelover7/dotfiles.git
synced 2025-09-14 07:33:49 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,641 @@
|
||||
'use strict';
|
||||
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
|
||||
|
||||
const SESSION_TIMEOUT = 15;
|
||||
|
||||
|
||||
const RemoteSession = GObject.registerClass({
|
||||
GTypeName: 'GSConnectRemoteSession',
|
||||
Implements: [Gio.DBusInterface],
|
||||
Signals: {
|
||||
'closed': {
|
||||
flags: GObject.SignalFlags.RUN_FIRST,
|
||||
},
|
||||
},
|
||||
}, class RemoteSession extends Gio.DBusProxy {
|
||||
|
||||
_init(objectPath) {
|
||||
super._init({
|
||||
g_bus_type: Gio.BusType.SESSION,
|
||||
g_name: 'org.gnome.Mutter.RemoteDesktop',
|
||||
g_object_path: objectPath,
|
||||
g_interface_name: 'org.gnome.Mutter.RemoteDesktop.Session',
|
||||
g_flags: Gio.DBusProxyFlags.NONE,
|
||||
});
|
||||
|
||||
this._started = false;
|
||||
}
|
||||
|
||||
vfunc_g_signal(sender_name, signal_name, parameters) {
|
||||
if (signal_name === 'Closed')
|
||||
this.emit('closed');
|
||||
}
|
||||
|
||||
_call(name, parameters = null) {
|
||||
if (!this._started)
|
||||
return;
|
||||
|
||||
this.call(name, parameters, Gio.DBusCallFlags.NONE, -1, null, null);
|
||||
}
|
||||
|
||||
get session_id() {
|
||||
try {
|
||||
return this.get_cached_property('SessionId').unpack();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
if (this._started)
|
||||
return;
|
||||
|
||||
// Initialize the proxy
|
||||
await new Promise((resolve, reject) => {
|
||||
this.init_async(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
null,
|
||||
(proxy, res) => {
|
||||
try {
|
||||
proxy.init_finish(res);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Start the session
|
||||
await new Promise((resolve, reject) => {
|
||||
this.call(
|
||||
'Start',
|
||||
null,
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
null,
|
||||
(proxy, res) => {
|
||||
try {
|
||||
resolve(proxy.call_finish(res));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this._started = true;
|
||||
} catch (e) {
|
||||
this.destroy();
|
||||
|
||||
Gio.DBusError.strip_remote_error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this._started) {
|
||||
this._started = false;
|
||||
this.call('Stop', null, Gio.DBusCallFlags.NONE, -1, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
_translateButton(button) {
|
||||
switch (button) {
|
||||
case Gdk.BUTTON_PRIMARY:
|
||||
return 0x110;
|
||||
|
||||
case Gdk.BUTTON_MIDDLE:
|
||||
return 0x112;
|
||||
|
||||
case Gdk.BUTTON_SECONDARY:
|
||||
return 0x111;
|
||||
|
||||
case 4:
|
||||
return 0; // FIXME
|
||||
|
||||
case 5:
|
||||
return 0x10F; // up
|
||||
}
|
||||
}
|
||||
|
||||
movePointer(dx, dy) {
|
||||
this._call(
|
||||
'NotifyPointerMotionRelative',
|
||||
GLib.Variant.new('(dd)', [dx, dy])
|
||||
);
|
||||
}
|
||||
|
||||
pressPointer(button) {
|
||||
button = this._translateButton(button);
|
||||
|
||||
this._call(
|
||||
'NotifyPointerButton',
|
||||
GLib.Variant.new('(ib)', [button, true])
|
||||
);
|
||||
}
|
||||
|
||||
releasePointer(button) {
|
||||
button = this._translateButton(button);
|
||||
|
||||
this._call(
|
||||
'NotifyPointerButton',
|
||||
GLib.Variant.new('(ib)', [button, false])
|
||||
);
|
||||
}
|
||||
|
||||
clickPointer(button) {
|
||||
button = this._translateButton(button);
|
||||
|
||||
this._call(
|
||||
'NotifyPointerButton',
|
||||
GLib.Variant.new('(ib)', [button, true])
|
||||
);
|
||||
|
||||
this._call(
|
||||
'NotifyPointerButton',
|
||||
GLib.Variant.new('(ib)', [button, false])
|
||||
);
|
||||
}
|
||||
|
||||
doubleclickPointer(button) {
|
||||
this.clickPointer(button);
|
||||
this.clickPointer(button);
|
||||
}
|
||||
|
||||
scrollPointer(dx, dy) {
|
||||
// NOTE: NotifyPointerAxis only seems to work on Wayland, but maybe
|
||||
// NotifyPointerAxisDiscrete is the better choice anyways
|
||||
if (HAVE_WAYLAND) {
|
||||
this._call(
|
||||
'NotifyPointerAxis',
|
||||
GLib.Variant.new('(ddu)', [dx, dy, 0])
|
||||
);
|
||||
this._call(
|
||||
'NotifyPointerAxis',
|
||||
GLib.Variant.new('(ddu)', [0, 0, 1])
|
||||
);
|
||||
} else if (dy > 0) {
|
||||
this._call(
|
||||
'NotifyPointerAxisDiscrete',
|
||||
GLib.Variant.new('(ui)', [Gdk.ScrollDirection.UP, 1])
|
||||
);
|
||||
} else if (dy < 0) {
|
||||
this._call(
|
||||
'NotifyPointerAxisDiscrete',
|
||||
GLib.Variant.new('(ui)', [Gdk.ScrollDirection.UP, -1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Keyboard Events
|
||||
*/
|
||||
pressKeysym(keysym) {
|
||||
this._call(
|
||||
'NotifyKeyboardKeysym',
|
||||
GLib.Variant.new('(ub)', [keysym, true])
|
||||
);
|
||||
}
|
||||
|
||||
releaseKeysym(keysym) {
|
||||
this._call(
|
||||
'NotifyKeyboardKeysym',
|
||||
GLib.Variant.new('(ub)', [keysym, false])
|
||||
);
|
||||
}
|
||||
|
||||
pressreleaseKeysym(keysym) {
|
||||
this._call(
|
||||
'NotifyKeyboardKeysym',
|
||||
GLib.Variant.new('(ub)', [keysym, true])
|
||||
);
|
||||
this._call(
|
||||
'NotifyKeyboardKeysym',
|
||||
GLib.Variant.new('(ub)', [keysym, false])
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* High-level keyboard input
|
||||
*/
|
||||
pressKey(input, modifiers) {
|
||||
// Press Modifiers
|
||||
if (modifiers & Gdk.ModifierType.MOD1_MASK)
|
||||
this.pressKeysym(Gdk.KEY_Alt_L);
|
||||
if (modifiers & Gdk.ModifierType.CONTROL_MASK)
|
||||
this.pressKeysym(Gdk.KEY_Control_L);
|
||||
if (modifiers & Gdk.ModifierType.SHIFT_MASK)
|
||||
this.pressKeysym(Gdk.KEY_Shift_L);
|
||||
if (modifiers & Gdk.ModifierType.SUPER_MASK)
|
||||
this.pressKeysym(Gdk.KEY_Super_L);
|
||||
|
||||
if (typeof input === 'string') {
|
||||
const keysym = Gdk.unicode_to_keyval(input.codePointAt(0));
|
||||
this.pressreleaseKeysym(keysym);
|
||||
} else {
|
||||
this.pressreleaseKeysym(input);
|
||||
}
|
||||
|
||||
// Release Modifiers
|
||||
if (modifiers & Gdk.ModifierType.MOD1_MASK)
|
||||
this.releaseKeysym(Gdk.KEY_Alt_L);
|
||||
if (modifiers & Gdk.ModifierType.CONTROL_MASK)
|
||||
this.releaseKeysym(Gdk.KEY_Control_L);
|
||||
if (modifiers & Gdk.ModifierType.SHIFT_MASK)
|
||||
this.releaseKeysym(Gdk.KEY_Shift_L);
|
||||
if (modifiers & Gdk.ModifierType.SUPER_MASK)
|
||||
this.releaseKeysym(Gdk.KEY_Super_L);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.__disposed === undefined) {
|
||||
this.__disposed = true;
|
||||
GObject.signal_handlers_destroy(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
class Controller {
|
||||
constructor() {
|
||||
this._nameAppearedId = 0;
|
||||
this._session = null;
|
||||
this._sessionCloseId = 0;
|
||||
this._sessionExpiry = 0;
|
||||
this._sessionExpiryId = 0;
|
||||
this._sessionStarting = false;
|
||||
|
||||
// Watch for the RemoteDesktop portal
|
||||
this._nameWatcherId = Gio.bus_watch_name(
|
||||
Gio.BusType.SESSION,
|
||||
'org.gnome.Mutter.RemoteDesktop',
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
this._onNameAppeared.bind(this),
|
||||
this._onNameVanished.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
get connection() {
|
||||
if (this._connection === undefined)
|
||||
this._connection = null;
|
||||
|
||||
return this._connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a Wayland session, specifically for distributions that
|
||||
* don't ship pipewire support (eg. Debian/Ubuntu).
|
||||
*
|
||||
* FIXME: this is a super ugly hack that should go away
|
||||
*
|
||||
* @return {boolean} %true if wayland is not supported
|
||||
*/
|
||||
_checkWayland() {
|
||||
if (HAVE_WAYLAND) {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
HAVE_REMOTEINPUT = false;
|
||||
const service = Gio.Application.get_default();
|
||||
|
||||
if (service === null)
|
||||
return true;
|
||||
|
||||
// First we're going to disabled the affected plugins on all devices
|
||||
for (const device of service.manager.devices.values()) {
|
||||
const supported = device.settings.get_strv('supported-plugins');
|
||||
let index;
|
||||
|
||||
if ((index = supported.indexOf('mousepad')) > -1)
|
||||
supported.splice(index, 1);
|
||||
|
||||
if ((index = supported.indexOf('presenter')) > -1)
|
||||
supported.splice(index, 1);
|
||||
|
||||
device.settings.set_strv('supported-plugins', supported);
|
||||
}
|
||||
|
||||
// Second we need each backend to rebuild its identity packet and
|
||||
// broadcast the amended capabilities to the network
|
||||
for (const backend of service.manager.backends.values())
|
||||
backend.buildIdentity();
|
||||
|
||||
service.manager.identify();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onNameAppeared(connection, name, name_owner) {
|
||||
try {
|
||||
this._connection = connection;
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
_onNameVanished(connection, name) {
|
||||
try {
|
||||
if (this._session !== null)
|
||||
this._onSessionClosed(this._session);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
_onSessionClosed(session) {
|
||||
// Disconnect from the session
|
||||
if (this._sessionClosedId > 0) {
|
||||
session.disconnect(this._sessionClosedId);
|
||||
this._sessionClosedId = 0;
|
||||
}
|
||||
|
||||
// Destroy the session
|
||||
session.destroy();
|
||||
this._session = null;
|
||||
}
|
||||
|
||||
_onSessionExpired() {
|
||||
// If the session has been used recently, schedule a new expiry
|
||||
const remainder = Math.floor(this._sessionExpiry - (Date.now() / 1000));
|
||||
|
||||
if (remainder > 0) {
|
||||
this._sessionExpiryId = GLib.timeout_add_seconds(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
remainder,
|
||||
this._onSessionExpired.bind(this)
|
||||
);
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// Otherwise if there's an active session, close it
|
||||
if (this._session !== null)
|
||||
this._session.stop();
|
||||
|
||||
// Reset the GSource Id
|
||||
this._sessionExpiryId = 0;
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
_createRemoteDesktopSession() {
|
||||
if (this.connection === null)
|
||||
return Promise.reject(new Error('No DBus connection'));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connection.call(
|
||||
'org.gnome.Mutter.RemoteDesktop',
|
||||
'/org/gnome/Mutter/RemoteDesktop',
|
||||
'org.gnome.Mutter.RemoteDesktop',
|
||||
'CreateSession',
|
||||
null,
|
||||
null,
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
null,
|
||||
(connection, res) => {
|
||||
try {
|
||||
res = connection.call_finish(res);
|
||||
resolve(res.deepUnpack()[0]);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_createScreenCastSession(sessionId) {
|
||||
if (this.connection === null)
|
||||
return Promise.reject(new Error('No DBus connection'));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = new GLib.Variant('(a{sv})', [{
|
||||
'disable-animations': GLib.Variant.new_boolean(false),
|
||||
'remote-desktop-session-id': GLib.Variant.new_string(sessionId),
|
||||
}]);
|
||||
|
||||
this.connection.call(
|
||||
'org.gnome.Mutter.ScreenCast',
|
||||
'/org/gnome/Mutter/ScreenCast',
|
||||
'org.gnome.Mutter.ScreenCast',
|
||||
'CreateSession',
|
||||
options,
|
||||
null,
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
null,
|
||||
(connection, res) => {
|
||||
try {
|
||||
res = connection.call_finish(res);
|
||||
resolve(res.deepUnpack()[0]);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async _ensureAdapter() {
|
||||
try {
|
||||
// Update the timestamp of the last event
|
||||
this._sessionExpiry = Math.floor((Date.now() / 1000) + SESSION_TIMEOUT);
|
||||
|
||||
// Session is active
|
||||
if (this._session !== null)
|
||||
return;
|
||||
|
||||
// Mutter's RemoteDesktop is not available, fall back to Atspi
|
||||
if (this.connection === null) {
|
||||
debug('Falling back to Atspi');
|
||||
|
||||
// If we got here in Wayland, we need to re-adjust and bail
|
||||
if (this._checkWayland())
|
||||
return;
|
||||
|
||||
const fallback = imports.service.components.atspi;
|
||||
this._session = new fallback.Controller();
|
||||
|
||||
// Mutter is available and there isn't another session starting
|
||||
} else if (this._sessionStarting === false) {
|
||||
this._sessionStarting = true;
|
||||
|
||||
debug('Creating Mutter RemoteDesktop session');
|
||||
|
||||
// This takes three steps: creating the remote desktop session,
|
||||
// starting the session, and creating a screencast session for
|
||||
// the remote desktop session.
|
||||
const objectPath = await this._createRemoteDesktopSession();
|
||||
|
||||
this._session = new RemoteSession(objectPath);
|
||||
await this._session.start();
|
||||
|
||||
await this._createScreenCastSession(this._session.session_id);
|
||||
|
||||
// Watch for the session ending
|
||||
this._sessionClosedId = this._session.connect(
|
||||
'closed',
|
||||
this._onSessionClosed.bind(this)
|
||||
);
|
||||
|
||||
if (this._sessionExpiryId === 0) {
|
||||
this._sessionExpiryId = GLib.timeout_add_seconds(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
SESSION_TIMEOUT,
|
||||
this._onSessionExpired.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
this._sessionStarting = false;
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
|
||||
if (this._session !== null) {
|
||||
this._session.destroy();
|
||||
this._session = null;
|
||||
}
|
||||
|
||||
this._sessionStarting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Pointer Events
|
||||
*/
|
||||
movePointer(dx, dy) {
|
||||
try {
|
||||
if (dx === 0 && dy === 0)
|
||||
return;
|
||||
|
||||
this._ensureAdapter();
|
||||
this._session.movePointer(dx, dy);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
pressPointer(button) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.pressPointer(button);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
releasePointer(button) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.releasePointer(button);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
clickPointer(button) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.clickPointer(button);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
doubleclickPointer(button) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.doubleclickPointer(button);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
scrollPointer(dx, dy) {
|
||||
if (dx === 0 && dy === 0)
|
||||
return;
|
||||
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.scrollPointer(dx, dy);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Keyboard Events
|
||||
*/
|
||||
pressKeysym(keysym) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.pressKeysym(keysym);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
releaseKeysym(keysym) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.releaseKeysym(keysym);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
pressreleaseKeysym(keysym) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.pressreleaseKeysym(keysym);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* High-level keyboard input
|
||||
*/
|
||||
pressKey(input, modifiers) {
|
||||
try {
|
||||
this._ensureAdapter();
|
||||
this._session.pressKey(input, modifiers);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._session !== null) {
|
||||
// Disconnect from the session
|
||||
if (this._sessionClosedId > 0) {
|
||||
this._session.disconnect(this._sessionClosedId);
|
||||
this._sessionClosedId = 0;
|
||||
}
|
||||
|
||||
this._session.destroy();
|
||||
this._session = null;
|
||||
}
|
||||
|
||||
if (this._nameWatcherId > 0) {
|
||||
Gio.bus_unwatch_name(this._nameWatcherId);
|
||||
this._nameWatcherId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The service class for this component
|
||||
*/
|
||||
var Component = Controller;
|
||||
|
Reference in New Issue
Block a user