[gnome] Update gnome extensions
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
export default {
|
||||
PACKAGE_VERSION: 62,
|
||||
PACKAGE_VERSION: 71,
|
||||
PACKAGE_URL: 'https://github.com/GSConnect/gnome-shell-extension-gsconnect',
|
||||
PACKAGE_BUGREPORT: 'https://github.com/GSConnect/gnome-shell-extension-gsconnect/issues/new',
|
||||
PACKAGE_DATADIR: '/usr/local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io',
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,9 +5,10 @@
|
||||
"shell-version": [
|
||||
"46",
|
||||
"47",
|
||||
"48"
|
||||
"48",
|
||||
"49"
|
||||
],
|
||||
"url": "https://github.com/GSConnect/gnome-shell-extension-gsconnect/wiki",
|
||||
"uuid": "gsconnect@andyholmes.github.io",
|
||||
"version": 62
|
||||
"version": 71
|
||||
}
|
||||
Binary file not shown.
@ -267,6 +267,7 @@ export const Panel = GObject.registerClass({
|
||||
'sharing', 'sharing-page',
|
||||
'desktop-list', 'clipboard', 'clipboard-sync', 'mousepad', 'mpris', 'systemvolume',
|
||||
'share', 'share-list', 'receive-files', 'receive-directory',
|
||||
'links', 'links-list', 'launch-urls',
|
||||
|
||||
// Battery
|
||||
'battery',
|
||||
@ -467,6 +468,7 @@ export const Panel = GObject.registerClass({
|
||||
|
||||
settings = this.pluginSettings('share');
|
||||
this.actions.add_action(settings.create_action('receive-files'));
|
||||
this.actions.add_action(settings.create_action('launch-urls'));
|
||||
|
||||
settings = this.pluginSettings('sms');
|
||||
this.actions.add_action(settings.create_action('legacy-sms'));
|
||||
|
||||
@ -146,7 +146,8 @@ export const Window = GObject.registerClass({
|
||||
Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-window.ui',
|
||||
Children: [
|
||||
// HeaderBar
|
||||
'headerbar', 'infobar', 'stack',
|
||||
'headerbar', 'stack',
|
||||
'infobar_discoverable', 'infobar_openssl',
|
||||
'service-menu', 'service-edit', 'refresh-button',
|
||||
'device-menu', 'prev-button',
|
||||
|
||||
@ -203,12 +204,21 @@ export const Window = GObject.registerClass({
|
||||
// Discoverable InfoBar
|
||||
this.settings.bind(
|
||||
'discoverable',
|
||||
this.infobar,
|
||||
this.infobar_discoverable,
|
||||
'reveal-child',
|
||||
Gio.SettingsBindFlags.INVERT_BOOLEAN
|
||||
);
|
||||
this.add_action(this.settings.create_action('discoverable'));
|
||||
|
||||
// OpenSSL-missing infobar
|
||||
this.settings.bind(
|
||||
'missing-openssl',
|
||||
this.infobar_openssl,
|
||||
'reveal-child',
|
||||
Gio.SettingsBindFlags.DEFAULT
|
||||
);
|
||||
this.add_action(this.settings.create_action('missing-openssl'));
|
||||
|
||||
// Application Menu
|
||||
this._initMenu();
|
||||
|
||||
@ -297,7 +307,9 @@ export const Window = GObject.registerClass({
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
if (this.service.active && this.device_list.get_children().length < 1) {
|
||||
const missing_openssl = this.settings.get_boolean('missing-openssl');
|
||||
const no_devices = this.service.active && this.device_list.get_children().length < 1;
|
||||
if (missing_openssl || no_devices) {
|
||||
this.device_list_spinner.active = true;
|
||||
this.service.activate_action('refresh', null);
|
||||
} else {
|
||||
@ -487,6 +499,14 @@ export const Window = GObject.registerClass({
|
||||
this.service_edit.grab_focus();
|
||||
}
|
||||
|
||||
_onRetryOpenssl(button, event) {
|
||||
this.settings.set_boolean('enabled', false);
|
||||
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT_IDLE, 2, () => {
|
||||
this.settings.set_boolean('enabled', true);
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Context Switcher
|
||||
*/
|
||||
|
||||
Binary file not shown.
@ -38,6 +38,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<key name="discoverable" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
<key name="missing-openssl" type="b">
|
||||
<default>false</default>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<!-- Window Geometry -->
|
||||
@ -160,6 +163,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<key name="receive-directory" type="s">
|
||||
<default>""</default>
|
||||
</key>
|
||||
<key name="launch-urls" type="b">
|
||||
<default>false</default>
|
||||
</key>
|
||||
</schema>
|
||||
<schema id="org.gnome.Shell.Extensions.GSConnect.Plugin.SMS">
|
||||
<key name="legacy-sms" type="b">
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
export class MissingOpensslError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'MissingOpensslError';
|
||||
}
|
||||
}
|
||||
@ -164,7 +164,7 @@ export const Clipboard = GObject.registerClass(
|
||||
proc.communicate_utf8_async(null, null, (proc, res) => {
|
||||
try {
|
||||
const [, stdout, stderr] =
|
||||
proc.communicate_utf8_finish(res);
|
||||
proc.communicate_utf8_finish(res);
|
||||
if (proc.get_successful())
|
||||
resolve(stdout.trim().split('\n'));
|
||||
else
|
||||
@ -194,7 +194,7 @@ export const Clipboard = GObject.registerClass(
|
||||
proc.communicate_utf8_async(null, null, (proc, res) => {
|
||||
try {
|
||||
const [, stdout, stderr] =
|
||||
proc.communicate_utf8_finish(res);
|
||||
proc.communicate_utf8_finish(res);
|
||||
if (proc.get_successful())
|
||||
resolve(stdout);
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user