[gnome] Update gnome extensions
This commit is contained in:
@ -367,9 +367,12 @@ export const ChannelService = GObject.registerClass({
|
||||
if (!packet.body.deviceName)
|
||||
throw new Error('missing deviceName');
|
||||
|
||||
// Reject invalid device names
|
||||
if (!Device.validateName(packet.body.deviceName))
|
||||
throw new Error(`invalid deviceName "${packet.body.deviceName}"`);
|
||||
// Sanitize invalid device names
|
||||
if (!Device.validateName(packet.body.deviceName)) {
|
||||
const sanitized = Device.sanitizeName(packet.body.deviceName);
|
||||
debug(`Sanitized invalid device name "${packet.body.deviceName}" to "${sanitized}"`);
|
||||
packet.body.deviceName = sanitized;
|
||||
}
|
||||
|
||||
debug(packet);
|
||||
|
||||
@ -704,6 +707,25 @@ export const Channel = GObject.registerClass({
|
||||
return this._authenticate(connection);
|
||||
}
|
||||
|
||||
async _exchangeIdentities() {
|
||||
await this.sendPacket(this.backend.identity);
|
||||
const identity = await this.readPacket();
|
||||
|
||||
if (this.identity.body.protocolVersion !== identity.body.protocolVersion) {
|
||||
this.identity = null;
|
||||
throw new Error(`Unexpected protocol version ${identity.protocolVersion}; ` +
|
||||
`handshake started with protocol version ${this.identity.protocolVersion}`);
|
||||
}
|
||||
|
||||
if (this.identity.body.deviceId !== identity.body.deviceId) {
|
||||
this.identity = null;
|
||||
throw new Error(`Unexpected device ID "${identity.body.deviceId}"; ` +
|
||||
`handshake started with device ID "${this.identity.body.deviceId}"`);
|
||||
}
|
||||
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate an incoming connection
|
||||
*
|
||||
@ -740,17 +762,19 @@ export const Channel = GObject.registerClass({
|
||||
if (!this.identity.body.deviceName)
|
||||
throw new Error('missing deviceName');
|
||||
|
||||
// Reject invalid device names
|
||||
if (!Device.validateName(this.identity.body.deviceName))
|
||||
throw new Error(`invalid deviceName "${this.identity.body.deviceName}"`);
|
||||
// Sanitize invalid device names
|
||||
if (!Device.validateName(this.identity.body.deviceName)) {
|
||||
const sanitized = Device.sanitizeName(this.identity.body.deviceName);
|
||||
debug(`Sanitized invalid device name "${this.identity.body.deviceName}" to "${sanitized}"`);
|
||||
this.identity.body.deviceName = sanitized;
|
||||
}
|
||||
|
||||
this._connection = await this._encryptClient(connection);
|
||||
|
||||
// Starting with protocol version 8, the devices are expected to
|
||||
// exchange identity packets again after TLS negotiation
|
||||
if (this.identity.body.protocolVersion >= 8) {
|
||||
await this.sendPacket(this.backend.identity);
|
||||
this.identity = await this.readPacket();
|
||||
await this._exchangeIdentities();
|
||||
}
|
||||
} catch (e) {
|
||||
this.close();
|
||||
@ -780,8 +804,7 @@ export const Channel = GObject.registerClass({
|
||||
// Starting with protocol version 8, the devices are expected to
|
||||
// exchange identity packets again after TLS negotiation
|
||||
if (this.identity.body.protocolVersion >= 8) {
|
||||
await this.sendPacket(this.backend.identity);
|
||||
this.identity = await this.readPacket();
|
||||
await this._exchangeIdentities();
|
||||
}
|
||||
} catch (e) {
|
||||
this.close();
|
||||
|
||||
@ -64,7 +64,7 @@ const Store = GObject.registerClass({
|
||||
/**
|
||||
* Parse an EContact and add it to the store.
|
||||
*
|
||||
* @param {EBookContacts.Contact} econtact - an EContact to parse
|
||||
* @param {"EBookContacts.Contact"} econtact - an EContact to parse
|
||||
* @param {string} [origin] - an optional origin string
|
||||
*/
|
||||
async _parseEContact(econtact, origin = 'desktop') {
|
||||
|
||||
@ -15,8 +15,17 @@ let Gvc = null;
|
||||
try {
|
||||
// Add gnome-shell's typelib dir to the search path
|
||||
const typelibDir = GLib.build_filenamev([Config.GNOME_SHELL_LIBDIR, 'gnome-shell']);
|
||||
GIRepository.Repository.prepend_search_path(typelibDir);
|
||||
GIRepository.Repository.prepend_library_path(typelibDir);
|
||||
|
||||
if (GIRepository.Repository.hasOwnProperty('prepend_search_path')) {
|
||||
// GNOME <= 48 / GIRepository 2.0
|
||||
GIRepository.Repository.prepend_search_path(typelibDir);
|
||||
GIRepository.Repository.prepend_library_path(typelibDir);
|
||||
} else {
|
||||
// GNOME 49+ / GIRepository 3.0
|
||||
const repo = GIRepository.Repository.dup_default();
|
||||
repo.prepend_search_path(typelibDir);
|
||||
repo.prepend_library_path(typelibDir);
|
||||
}
|
||||
|
||||
Gvc = (await import('gi://Gvc')).default;
|
||||
} catch {}
|
||||
|
||||
@ -7,12 +7,18 @@
|
||||
import Gdk from 'gi://Gdk?version=3.0';
|
||||
import 'gi://GdkPixbuf?version=2.0';
|
||||
import Gio from 'gi://Gio?version=2.0';
|
||||
import 'gi://GIRepository?version=2.0';
|
||||
import GLib from 'gi://GLib?version=2.0';
|
||||
import GObject from 'gi://GObject?version=2.0';
|
||||
import Gtk from 'gi://Gtk?version=3.0';
|
||||
import 'gi://Pango?version=1.0';
|
||||
|
||||
// GNOME 49 uses GIRepository 3.0
|
||||
import('gi://GIRepository?version=3.0').catch(() => {
|
||||
import('gi://GIRepository?version=2.0').catch(() => {});
|
||||
});
|
||||
|
||||
import('gi://GioUnix?version=2.0').catch(() => {}); // Set version for optional dependency
|
||||
|
||||
import system from 'system';
|
||||
|
||||
import './init.js';
|
||||
@ -21,8 +27,7 @@ import Config from '../config.js';
|
||||
import Device from './device.js';
|
||||
import Manager from './manager.js';
|
||||
import * as ServiceUI from './ui/service.js';
|
||||
|
||||
import('gi://GioUnix?version=2.0').catch(() => {}); // Set version for optional dependency
|
||||
import {MissingOpensslError} from '../utils/exceptions.js';
|
||||
|
||||
|
||||
/**
|
||||
@ -48,14 +53,14 @@ const Service = GObject.registerClass({
|
||||
|
||||
_migrateConfiguration() {
|
||||
if (!Device.validateName(this.settings.get_string('name')))
|
||||
this.settings.set('name', GLib.get_host_name().slice(0, 32));
|
||||
this.settings.set_string('name', GLib.get_host_name().slice(0, 32));
|
||||
|
||||
const [certPath, keyPath] = [
|
||||
GLib.build_filenamev([Config.CONFIGDIR, 'certificate.pem']),
|
||||
GLib.build_filenamev([Config.CONFIGDIR, 'private.pem']),
|
||||
];
|
||||
const certificate = Gio.TlsCertificate.new_for_paths(certPath, keyPath,
|
||||
null);
|
||||
|
||||
const certificate = Gio.TlsCertificate.new_for_paths(certPath, keyPath, null);
|
||||
|
||||
if (Device.validateId(certificate.common_name))
|
||||
return;
|
||||
@ -92,7 +97,7 @@ const Service = GObject.registerClass({
|
||||
|
||||
// Notify the user
|
||||
const notification = Gio.Notification.new(_('Settings Migrated'));
|
||||
notification.set_body(_('GSConnect has updated to support changes to the KDE Connect protocol. Some devices may need to be repaired.'));
|
||||
notification.set_body(_('GSConnect has updated to support changes to the KDE Connect protocol. Some devices may need to be re-paired.'));
|
||||
notification.set_icon(new Gio.ThemedIcon({name: 'dialog-warning'}));
|
||||
notification.set_priority(Gio.NotificationPriority.HIGH);
|
||||
this.send_notification('settings-migrated', notification);
|
||||
@ -211,8 +216,9 @@ const Service = GObject.registerClass({
|
||||
* Report a service-level error
|
||||
*
|
||||
* @param {object} error - An Error or object with name, message and stack
|
||||
* @param {string} [notification_id] - An optional id for the notification
|
||||
*/
|
||||
notify_error(error) {
|
||||
notify_error(error, notification_id) {
|
||||
try {
|
||||
// Always log the error
|
||||
logError(error);
|
||||
@ -226,8 +232,12 @@ const Service = GObject.registerClass({
|
||||
if (error.name === undefined)
|
||||
error.name = 'Error';
|
||||
|
||||
if (notification_id !== undefined)
|
||||
id = notification_id;
|
||||
else
|
||||
id = error.url || error.message.trim();
|
||||
|
||||
if (error.url !== undefined) {
|
||||
id = error.url;
|
||||
body = _('Click for help troubleshooting');
|
||||
priority = Gio.NotificationPriority.URGENT;
|
||||
|
||||
@ -238,7 +248,6 @@ const Service = GObject.registerClass({
|
||||
url: error.url,
|
||||
});
|
||||
} else {
|
||||
id = error.message.trim();
|
||||
body = _('Click for more information');
|
||||
priority = Gio.NotificationPriority.HIGH;
|
||||
|
||||
@ -298,7 +307,18 @@ const Service = GObject.registerClass({
|
||||
this._initActions();
|
||||
|
||||
// TODO: remove after a reasonable period of time
|
||||
this._migrateConfiguration();
|
||||
try {
|
||||
this._migrateConfiguration();
|
||||
if (this.settings.get_boolean('missing-openssl'))
|
||||
this.withdraw_notification('gsconnect-missing-openssl');
|
||||
this.settings.set_boolean('missing-openssl', false);
|
||||
} catch (e) {
|
||||
if (e instanceof MissingOpensslError) {
|
||||
this.settings.set_boolean('missing-openssl', true);
|
||||
this.notify_error(e, 'gsconnect-missing-openssl');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.manager.start();
|
||||
}
|
||||
|
||||
@ -144,6 +144,14 @@ const Device = GObject.registerClass({
|
||||
return name.trim() && /^[^"',;:.!?()[\]<>]{1,32}$/.test(name);
|
||||
}
|
||||
|
||||
static sanitizeName(name) {
|
||||
// Remove all prohibited characters
|
||||
const sanitized = name.replaceAll(/["',;:.!?()[\]<>]/g, '');
|
||||
if (sanitized.length < 1)
|
||||
throw new Error('No valid characters in device name!');
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
get channel() {
|
||||
if (this._channel === undefined)
|
||||
this._channel = null;
|
||||
|
||||
@ -10,6 +10,7 @@ import GLib from 'gi://GLib';
|
||||
|
||||
import Config from '../config.js';
|
||||
import {setup, setupGettext} from '../utils/setup.js';
|
||||
import {MissingOpensslError} from '../utils/exceptions.js';
|
||||
|
||||
|
||||
// Promise Wrappers
|
||||
@ -62,10 +63,22 @@ const extensionFolder = GLib.path_get_dirname(serviceFolder);
|
||||
setup(extensionFolder);
|
||||
setupGettext();
|
||||
|
||||
|
||||
if (Config.IS_USER) {
|
||||
// Infer libdir by assuming gnome-shell shares a common prefix with gjs;
|
||||
// assume the parent directory if it's not there
|
||||
let libdir = GIRepository.Repository.get_search_path().find(path => {
|
||||
let gir_paths;
|
||||
|
||||
if (GIRepository.Repository.hasOwnProperty('get_search_path')) {
|
||||
// GNOME <= 48 / GIRepository 2.0
|
||||
gir_paths = GIRepository.Repository.get_search_path();
|
||||
} else {
|
||||
// GNOME 49+ / GIRepository 3.0
|
||||
const repo = GIRepository.Repository.dup_default();
|
||||
gir_paths = repo.get_search_path();
|
||||
}
|
||||
|
||||
let libdir = gir_paths.find(path => {
|
||||
return path.endsWith('/gjs/girepository-1.0');
|
||||
}).replace('/gjs/girepository-1.0', '');
|
||||
|
||||
@ -110,7 +123,7 @@ globalThis.HAVE_GNOME = GLib.getenv('GSCONNECT_MODE')?.toLowerCase() !== 'cli' &
|
||||
* @param {Error|string} message - A string or Error to log
|
||||
* @param {string} [prefix] - An optional prefix for the warning
|
||||
*/
|
||||
const _debugCallerMatch = new RegExp(/([^@]*)@([^:]*):([^:]*)/);
|
||||
const _debugCallerMatch = new RegExp(/^([^@]+)@(.*):(\d+):(\d+)$/);
|
||||
// eslint-disable-next-line func-style
|
||||
const _debugFunc = function (error, prefix = null) {
|
||||
let caller, message;
|
||||
@ -127,7 +140,9 @@ const _debugFunc = function (error, prefix = null) {
|
||||
message = `${prefix}: ${message}`;
|
||||
|
||||
const [, func, file, line] = _debugCallerMatch.exec(caller);
|
||||
const script = file.replace(Config.PACKAGE_DATADIR, '');
|
||||
let script = file.replace(Config.PACKAGE_DATADIR, '');
|
||||
if (script.startsWith('file:///'))
|
||||
script = script.slice(8);
|
||||
|
||||
GLib.log_structured('GSConnect', GLib.LogLevelFlags.LEVEL_MESSAGE, {
|
||||
'MESSAGE': `[${script}:${func}:${line}]: ${message}`,
|
||||
@ -338,10 +353,11 @@ GLib.Variant.prototype.full_unpack = _full_unpack;
|
||||
* @param {string} keyPath - Absolute path to a private key in PEM format
|
||||
* @param {string} commonName - A unique common name for the certificate
|
||||
* @returns {Gio.TlsCertificate} A TLS certificate
|
||||
* @throws MissingOpensslError on missing openssl binary
|
||||
*/
|
||||
Gio.TlsCertificate.new_for_paths = function (certPath, keyPath, commonName = null) {
|
||||
if (GLib.find_program_in_path(Config.OPENSSL_PATH) === null) {
|
||||
const error = new Error();
|
||||
const error = new MissingOpensslError();
|
||||
error.name = _('OpenSSL not found');
|
||||
error.url = `${Config.PACKAGE_URL}/wiki/Error#openssl-not-found`;
|
||||
throw error;
|
||||
|
||||
@ -13,6 +13,8 @@ import Device from './device.js';
|
||||
|
||||
import * as LanBackend from './backends/lan.js';
|
||||
|
||||
import {MissingOpensslError} from '../utils/exceptions.js';
|
||||
|
||||
const DEVICE_NAME = 'org.gnome.Shell.Extensions.GSConnect.Device';
|
||||
const DEVICE_PATH = '/org/gnome/Shell/Extensions/GSConnect/Device';
|
||||
const DEVICE_IFACE = Config.DBUS.lookup_interface(DEVICE_NAME);
|
||||
@ -102,10 +104,22 @@ const Manager = GObject.registerClass({
|
||||
|
||||
get certificate() {
|
||||
if (this._certificate === undefined) {
|
||||
this._certificate = Gio.TlsCertificate.new_for_paths(
|
||||
GLib.build_filenamev([Config.CONFIGDIR, 'certificate.pem']),
|
||||
GLib.build_filenamev([Config.CONFIGDIR, 'private.pem']),
|
||||
null);
|
||||
const app = Gio.Application.get_default();
|
||||
try {
|
||||
this._certificate = Gio.TlsCertificate.new_for_paths(
|
||||
GLib.build_filenamev([Config.CONFIGDIR, 'certificate.pem']),
|
||||
GLib.build_filenamev([Config.CONFIGDIR, 'private.pem']),
|
||||
null);
|
||||
if (this.settings.get_boolean('missing-openssl'))
|
||||
app?.withdraw_notification('gsconnect-missing-openssl');
|
||||
this.settings.set_boolean('missing-openssl', false);
|
||||
} catch (e) {
|
||||
if (e instanceof MissingOpensslError) {
|
||||
this.settings.set_boolean('missing-openssl', true);
|
||||
app?.notify_error(e, 'gsconnect-missing-openssl');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return this._certificate;
|
||||
@ -165,8 +179,7 @@ const Manager = GObject.registerClass({
|
||||
notif.set_icon(new Gio.ThemedIcon({name: 'dialog-warning'}));
|
||||
notif.set_priority(Gio.NotificationPriority.HIGH);
|
||||
notif.set_default_action('app.preferences');
|
||||
|
||||
Gio.Application.prototype.withdraw_notification.call(
|
||||
Gio.Application.prototype.send_notification.call(
|
||||
application,
|
||||
'discovery-warning',
|
||||
notif
|
||||
|
||||
@ -43,8 +43,6 @@ const MPRISPlugin = GObject.registerClass({
|
||||
this._transferring = new WeakSet();
|
||||
this._updating = new WeakSet();
|
||||
|
||||
this._queueTimers = new Map();
|
||||
|
||||
this._mpris = Components.acquire('mpris');
|
||||
|
||||
this._playerAddedId = this._mpris.connect(
|
||||
@ -78,12 +76,6 @@ const MPRISPlugin = GObject.registerClass({
|
||||
disconnected() {
|
||||
super.disconnected();
|
||||
|
||||
for (const [identity, timer] of this._queueTimers) {
|
||||
if (timer)
|
||||
GLib.source_remove(timer);
|
||||
this._queueTimers.delete(identity);
|
||||
}
|
||||
|
||||
for (const [identity, player] of this._players) {
|
||||
this._players.delete(identity);
|
||||
player.destroy();
|
||||
@ -267,35 +259,18 @@ const MPRISPlugin = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.body.hasOwnProperty('requestNowPlaying') ||
|
||||
packet.body.hasOwnProperty('requestVolume')) {
|
||||
const response = this._getUpdate(player.Identity, packet);
|
||||
this._sendUpdate(player, response);
|
||||
}
|
||||
// Information Request
|
||||
let hasResponse = false;
|
||||
|
||||
} catch (e) {
|
||||
debug(e, this.device.name);
|
||||
} finally {
|
||||
this._updating.delete(player);
|
||||
}
|
||||
}
|
||||
const response = {
|
||||
type: 'kdeconnect.mpris',
|
||||
body: {
|
||||
player: packet.body.player,
|
||||
},
|
||||
};
|
||||
|
||||
// Respond to information request (or push updated information)
|
||||
_getUpdate(identity, packet) {
|
||||
|
||||
const player = this._mpris?.getPlayer(identity);
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
const response = {
|
||||
type: 'kdeconnect.mpris',
|
||||
body: {
|
||||
player: player.Identity,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
if (packet.body.hasOwnProperty('requestNowPlaying')) {
|
||||
hasResponse = true;
|
||||
|
||||
Object.assign(response.body, {
|
||||
pos: Math.floor(player.Position / 1000),
|
||||
@ -356,61 +331,31 @@ const MPRISPlugin = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.body.hasOwnProperty('requestVolume'))
|
||||
if (packet.body.hasOwnProperty('requestVolume')) {
|
||||
hasResponse = true;
|
||||
response.body.volume = Math.floor(player.Volume * 100);
|
||||
}
|
||||
|
||||
return response;
|
||||
if (hasResponse)
|
||||
this.device.sendPacket(response);
|
||||
} catch (e) {
|
||||
debug(e, this.device.name);
|
||||
} finally {
|
||||
this._updating.delete(player);
|
||||
}
|
||||
}
|
||||
|
||||
_sendUpdate(player, packet = null) {
|
||||
if (!player || (!packet && this._updating.has(player)))
|
||||
return GLib.SOURCE_REMOVE;
|
||||
|
||||
debug(`Sending update for ${player.Identity}`);
|
||||
this._updating.add(player);
|
||||
|
||||
if (this._queueTimers.has(player.Identity)) {
|
||||
const timer_id = this._queueTimers.get(player.Identity);
|
||||
if (timer_id) {
|
||||
debug(`Stopping timer id ${timer_id}`);
|
||||
GLib.source_remove(timer_id);
|
||||
}
|
||||
this._queueTimers.delete(player.Identity);
|
||||
}
|
||||
if (!packet) {
|
||||
packet = this._getUpdate(player.Identity, {
|
||||
body: {
|
||||
requestNowPlaying: true,
|
||||
requestVolume: true,
|
||||
},
|
||||
}, false);
|
||||
}
|
||||
this.device.sendPacket(packet);
|
||||
|
||||
this._updating.delete(player);
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
_onPlayerChanged(mpris, player) {
|
||||
if (!this.settings.get_boolean('share-players'))
|
||||
return;
|
||||
|
||||
// Set a timer to send the updated state after a short delay.
|
||||
// Allows further state changes to be bundled into a single packet.
|
||||
if (this._queueTimers.has(player.Identity))
|
||||
return;
|
||||
this._queueTimers.set(player.Identity, 0);
|
||||
|
||||
const timer_id = GLib.timeout_add(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
250, // ms (0.25 seconds)
|
||||
this._sendUpdate.bind(this, player)
|
||||
);
|
||||
this._queueTimers.set(player.Identity, timer_id);
|
||||
debug(`Set update timer id ${timer_id} for ${player.Identity}`);
|
||||
this._handleCommand({
|
||||
body: {
|
||||
player: player.Identity,
|
||||
requestNowPlaying: true,
|
||||
requestVolume: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_onPlayerSeeked(mpris, player, offset) {
|
||||
@ -490,11 +435,6 @@ const MPRISPlugin = GObject.registerClass({
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const [identity, timer] of this._queueTimers) {
|
||||
this._queueTimers.delete(identity);
|
||||
GLib.source_remove(timer);
|
||||
}
|
||||
|
||||
if (this._mpris !== undefined) {
|
||||
this._mpris.disconnect(this._playerAddedId);
|
||||
this._mpris.disconnect(this._playerRemovedId);
|
||||
|
||||
@ -71,17 +71,35 @@ const SharePlugin = GObject.registerClass({
|
||||
}
|
||||
|
||||
handlePacket(packet) {
|
||||
const {filename, text, url} = packet.body;
|
||||
|
||||
// TODO: composite jobs (lastModified, numberOfFiles, totalPayloadSize)
|
||||
if (packet.body.hasOwnProperty('filename')) {
|
||||
if (filename !== undefined) {
|
||||
debug(`Remote wants to share file "${filename}".`);
|
||||
if (this.settings.get_boolean('receive-files'))
|
||||
this._handleFile(packet);
|
||||
else
|
||||
this._refuseFile(packet);
|
||||
} else if (packet.body.hasOwnProperty('text')) {
|
||||
this._handleText(packet);
|
||||
} else if (packet.body.hasOwnProperty('url')) {
|
||||
this._handleUri(packet);
|
||||
return;
|
||||
}
|
||||
if (text === undefined && url === undefined)
|
||||
throw new Error('Share request has invalid payload, ignoring.');
|
||||
|
||||
if (this.settings.get_boolean('launch-urls')) {
|
||||
let shared_url = url;
|
||||
if (url === undefined) {
|
||||
const urls = URI.findUrls(text);
|
||||
if (urls.length === 1)
|
||||
shared_url = urls[0].url;
|
||||
}
|
||||
if (shared_url !== undefined) {
|
||||
debug(`Launching shared URL "${shared_url}".`);
|
||||
return this._handleUri(shared_url);
|
||||
}
|
||||
}
|
||||
const message = text || url;
|
||||
debug('Displaying shared message.');
|
||||
this._handleText(message);
|
||||
}
|
||||
|
||||
_ensureReceiveDirectory() {
|
||||
@ -232,15 +250,14 @@ const SharePlugin = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
_handleUri(packet) {
|
||||
const uri = packet.body.url;
|
||||
_handleUri(uri) {
|
||||
Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null);
|
||||
}
|
||||
|
||||
_handleText(packet) {
|
||||
_handleText(message) {
|
||||
const dialog = new Gtk.MessageDialog({
|
||||
text: _('Text Shared By %s').format(this.device.name),
|
||||
secondary_text: URI.linkify(packet.body.text),
|
||||
secondary_text: URI.linkify(message),
|
||||
secondary_use_markup: true,
|
||||
buttons: Gtk.ButtonsType.CLOSE,
|
||||
});
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import GIRepository from 'gi://GIRepository';
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
import * as Components from '../components/index.js';
|
||||
@ -12,17 +10,6 @@ import * as Core from '../core.js';
|
||||
import Plugin from '../plugin.js';
|
||||
|
||||
|
||||
let Gvc = null;
|
||||
try {
|
||||
// Add gnome-shell's typelib dir to the search path
|
||||
const typelibDir = GLib.build_filenamev([Config.GNOME_SHELL_LIBDIR, 'gnome-shell']);
|
||||
GIRepository.Repository.prepend_search_path(typelibDir);
|
||||
GIRepository.Repository.prepend_library_path(typelibDir);
|
||||
|
||||
Gvc = (await import('gi://Gvc')).default;
|
||||
} catch {}
|
||||
|
||||
|
||||
export const Metadata = {
|
||||
label: _('System Volume'),
|
||||
description: _('Enable the paired device to control the system volume'),
|
||||
@ -128,7 +115,7 @@ const SystemVolumePlugin = GObject.registerClass({
|
||||
/**
|
||||
* Update the cache for @stream
|
||||
*
|
||||
* @param {Gvc.MixerStream} stream - The stream to cache
|
||||
* @param {"Gvc.MixerStream"} stream - The stream to cache
|
||||
* @returns {object} The updated cache object
|
||||
*/
|
||||
_updateCache(stream) {
|
||||
@ -148,7 +135,7 @@ const SystemVolumePlugin = GObject.registerClass({
|
||||
/**
|
||||
* Send the state of a local sink
|
||||
*
|
||||
* @param {Gvc.MixerControl} mixer - The mixer that owns the stream
|
||||
* @param {"Gvc.MixerControl"} mixer - The mixer that owns the stream
|
||||
* @param {number} id - The Id of the stream that changed
|
||||
*/
|
||||
_sendSink(mixer, id) {
|
||||
|
||||
Reference in New Issue
Block a user