1013 lines
34 KiB
JavaScript
1013 lines
34 KiB
JavaScript
|
/* DING: Desktop Icons New Generation for GNOME Shell
|
||
|
*
|
||
|
* Copyright (C) 2019-2022 Sergio Costas (rastersoft@gmail.com)
|
||
|
* Based on code original (C) Carlos Soriano
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, version 3 of the License.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
/* exported GtkVfsMetadata, extensionControl, discreteGpuAvailable, RemoteFileOperations, init */
|
||
|
'use strict';
|
||
|
const {Gio, GLib, Gdk, Gtk} = imports.gi;
|
||
|
const Signals = imports.signals;
|
||
|
const DBusInterfaces = imports.dbusInterfaces;
|
||
|
const DesktopIconsUtil = imports.desktopIconsUtil;
|
||
|
|
||
|
var NautilusFileOperations2 = null;
|
||
|
var FreeDesktopFileManager = null;
|
||
|
var GnomeNautilusPreview = null;
|
||
|
var SwitcherooControl = null;
|
||
|
var GnomeArchiveManager = null;
|
||
|
var GtkVfsMetadata = null;
|
||
|
var extensionControl = null;
|
||
|
|
||
|
var discreteGpuAvailable = false;
|
||
|
var dbusManagerObject;
|
||
|
var RemoteFileOperations;
|
||
|
|
||
|
const Gettext = imports.gettext.domain('ding');
|
||
|
|
||
|
const _ = Gettext.gettext;
|
||
|
|
||
|
class ProxyManager {
|
||
|
/*
|
||
|
* This class manages a DBus object through a DBusProxy. Any access to the proxy when the
|
||
|
* object isn't available results in a notification specifying that an specific program
|
||
|
* is needed to run that option.
|
||
|
*
|
||
|
* The proxy itself is accessed through the 'proxy' property (read-only). Any access to
|
||
|
* it will check the availability and show the notification if it isn't available. To get
|
||
|
* access to it without triggering this, it is possible to use the 'proxyNoCheck' property.
|
||
|
*
|
||
|
* Whether the object is or not available can be checked with the 'isAvailable' property.
|
||
|
* Also, every time the availability changes, the signal 'changed-status' is emitted.
|
||
|
*/
|
||
|
constructor(dbusManager, serviceName, objectName, interfaceName, inSystemBus, programNeeded) {
|
||
|
this._dbusManager = dbusManager;
|
||
|
this._serviceName = serviceName;
|
||
|
this._objectName = objectName;
|
||
|
this._interfaceName = interfaceName;
|
||
|
this._inSystemBus = inSystemBus;
|
||
|
this._signals = {};
|
||
|
this._signalsIDs = {};
|
||
|
this._connectSignals = {};
|
||
|
this._connectSignalsIDs = {};
|
||
|
this._beingLaunched = false;
|
||
|
if (typeof programNeeded == 'string') {
|
||
|
// if 'programNeeded' is a string, create a generic message for the notification.
|
||
|
this._programNeeded = [
|
||
|
_('"${programName}" is needed for Desktop Icons').replace('${programName}', programNeeded),
|
||
|
_('For this functionality to work in Desktop Icons, you must install "${programName}" in your system.').replace('${programName}', programNeeded),
|
||
|
programNeeded,
|
||
|
];
|
||
|
} else {
|
||
|
// instead, if it's not, it is presumed to be an array with two sentences, one for the notification title and another for the main text.
|
||
|
this._programNeeded = programNeeded;
|
||
|
}
|
||
|
this._timeout = 0;
|
||
|
this._available = false;
|
||
|
this._proxy = null;
|
||
|
if (this._dbusManager.checkIsAvailable(this._serviceName, this._inSystemBus)) {
|
||
|
this.makeNewProxy();
|
||
|
}
|
||
|
dbusManager.connect(inSystemBus ? 'changed-availability-system' : 'changed-availability-local', () => {
|
||
|
const newAvailability = this._dbusManager.checkIsAvailable(this._serviceName, this._inSystemBus);
|
||
|
if (newAvailability != this._available) {
|
||
|
if (newAvailability) {
|
||
|
this.makeNewProxy();
|
||
|
} else {
|
||
|
this._available = false;
|
||
|
this._proxy = null;
|
||
|
this.emit('changed-status', false);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
connectSignalToProxy(signal, cb) {
|
||
|
this._connectSignals[signal] = cb;
|
||
|
if (this._proxy) {
|
||
|
this._connectSignalsIDs[signal] = this._proxy.connectSignal(signal, cb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
connectToProxy(signal, cb) {
|
||
|
this._signals[signal] = cb;
|
||
|
if (this._proxy) {
|
||
|
this._signalsIDs[signal] = this._proxy.connect(signal, cb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
disconnectFromProxy(signal) {
|
||
|
if (signal in this._signalsIDs) {
|
||
|
if (this._proxy) {
|
||
|
this._proxy.disconnect(this._signalsIDs[signal]);
|
||
|
}
|
||
|
delete this._signalsIDs[signal];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
disconnectSignalFromProxy(signal) {
|
||
|
if (signal in this._connectSignalsIDs) {
|
||
|
if (this._proxy) {
|
||
|
this._proxy.disconnectSignal(this._connectSignalsIDs[signal]);
|
||
|
}
|
||
|
delete this._connectSignalsIDs[signal];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async makeNewProxy(delay = 0) {
|
||
|
if (delay !== 0) {
|
||
|
await DesktopIconsUtil.waitDelayMs(delay);
|
||
|
if (!this._dbusManager.checkIsAvailable(this._serviceName, this._inSystemBus)) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
if (this._beingLaunched) {
|
||
|
return;
|
||
|
}
|
||
|
this._interfaceXML = this._dbusManager.getInterface(this._serviceName, this._objectName, this._interfaceName, this._inSystemBus, false);
|
||
|
if (this._interfaceXML) {
|
||
|
this._beingLaunched = true;
|
||
|
try {
|
||
|
new Gio.DBusProxy.makeProxyWrapper(this._interfaceXML)(
|
||
|
this._inSystemBus ? Gio.DBus.system : Gio.DBus.session,
|
||
|
this._serviceName,
|
||
|
this._objectName,
|
||
|
(proxy, error) => {
|
||
|
this._beingLaunched = false;
|
||
|
if (error === null) {
|
||
|
for (let signal in this._signals) {
|
||
|
this._signalsIDs[signal] = proxy.connect(signal, this._signals[signal]);
|
||
|
}
|
||
|
for (let signal in this._connectSignals) {
|
||
|
this._connectSignalsIDs[signal] = proxy.connectSignal(signal, this._connectSignals[signal]);
|
||
|
}
|
||
|
this._available = true;
|
||
|
this._proxy = proxy;
|
||
|
print(`DBus interface for ${this._programNeeded[2]} (${this._interfaceName}) is now available.`);
|
||
|
this.emit('changed-status', true);
|
||
|
} else {
|
||
|
logError(error, `Error creating proxy, ${this._programNeeded[2]} (${this._interfaceName}); relaunching.\n`);
|
||
|
this.makeNewProxy(1000);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
} catch (e) {
|
||
|
logError(e, `Error creating proxy, ${this._programNeeded[0]}`);
|
||
|
this._beingLaunched = false;
|
||
|
this.makeNewProxy(1000);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
get isAvailable() {
|
||
|
return this._available;
|
||
|
}
|
||
|
|
||
|
get proxyNoCheck() {
|
||
|
return this._proxy;
|
||
|
}
|
||
|
|
||
|
get proxy() {
|
||
|
if (!this._available) {
|
||
|
if (this._programNeeded && (this._timeout == 0)) {
|
||
|
print(this._programNeeded[0]);
|
||
|
print(this._programNeeded[1]);
|
||
|
this._dbusManager.doNotify(this._programNeeded[0], this._programNeeded[1]);
|
||
|
this._timeout = GLib.timeout_add(
|
||
|
GLib.PRIORITY_DEFAULT,
|
||
|
1000,
|
||
|
() => {
|
||
|
this._timeout = 0;
|
||
|
return false;
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
return this._proxy;
|
||
|
}
|
||
|
}
|
||
|
Signals.addSignalMethods(ProxyManager.prototype);
|
||
|
|
||
|
|
||
|
class DBusManager {
|
||
|
/*
|
||
|
* This class manages all the DBus operations. A ProxyManager() class can subscribe to this to be notified
|
||
|
* whenever a change in the bus has occurred (like a server has been added or removed). It also can ask
|
||
|
* for a DBus interface, either getting it from the dbusInterfaces.js file or using DBus Introspection (which
|
||
|
* allows to get the currently available interface and, that way, know if an object implements an specific
|
||
|
* method, property or signal).
|
||
|
*
|
||
|
* ProxyManager() classes subscribe to the 'changed-availability-system' or 'changed-availability-local' signals,
|
||
|
* which are emitted every time a change in the bus or in the configuration files happen. Then, it can use
|
||
|
* checkIsAvailable() to determine if the desired service is available in the system or not.
|
||
|
*/
|
||
|
constructor() {
|
||
|
this._availableInSystemBus = [];
|
||
|
this._availableInLocalBus = [];
|
||
|
this._pendingLocalSignal = false;
|
||
|
this._pendingSystemSignal = false;
|
||
|
this._signalTimerID = 0;
|
||
|
|
||
|
let interfaceXML = this.getInterface(
|
||
|
'org.freedesktop.DBus',
|
||
|
'/org/freedesktop/DBus',
|
||
|
'org.freedesktop.DBus',
|
||
|
true, // system bus
|
||
|
true); // use DBus Introspection
|
||
|
this._dbusSystemProxy = new Gio.DBusProxy.makeProxyWrapper(interfaceXML)(
|
||
|
Gio.DBus.system,
|
||
|
'org.freedesktop.DBus',
|
||
|
'/org/freedesktop/DBus',
|
||
|
null
|
||
|
);
|
||
|
let ASCinSystemBus = interfaceXML.includes('ActivatableServicesChanged');
|
||
|
|
||
|
// Don't presume that both system and local have the same interface (just in case)
|
||
|
interfaceXML = this.getInterface(
|
||
|
'org.freedesktop.DBus',
|
||
|
'/org/freedesktop/DBus',
|
||
|
'org.freedesktop.DBus',
|
||
|
false, // local bus
|
||
|
true); // use DBus Introspection
|
||
|
this._dbusLocalProxy = new Gio.DBusProxy.makeProxyWrapper(interfaceXML)(
|
||
|
Gio.DBus.session,
|
||
|
'org.freedesktop.DBus',
|
||
|
'/org/freedesktop/DBus',
|
||
|
null
|
||
|
);
|
||
|
let ASCinLocalBus = interfaceXML.includes('ActivatableServicesChanged');
|
||
|
|
||
|
this._updateAllAvailabilities();
|
||
|
this._dbusLocalProxy.connectSignal('NameOwnerChanged', () => {
|
||
|
this._emitChangedSignal(true);
|
||
|
});
|
||
|
if (ASCinLocalBus) {
|
||
|
this._dbusLocalProxy.connectSignal('ActivatableServicesChanged', () => {
|
||
|
this._emitChangedSignal(true);
|
||
|
});
|
||
|
}
|
||
|
this._dbusSystemProxy.connectSignal('NameOwnerChanged', () => {
|
||
|
this._emitChangedSignal(false);
|
||
|
});
|
||
|
if (ASCinSystemBus) {
|
||
|
this._dbusSystemProxy.connectSignal('ActivatableServicesChanged', () => {
|
||
|
this._emitChangedSignal(false);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
interfaceXML = this.getInterface(
|
||
|
'org.freedesktop.Notifications',
|
||
|
'/org/freedesktop/Notifications',
|
||
|
'org.freedesktop.Notifications',
|
||
|
false, // local bus
|
||
|
false); // get interface from local code
|
||
|
this._notifyProxy = new Gio.DBusProxy.makeProxyWrapper(interfaceXML)(
|
||
|
Gio.DBus.session,
|
||
|
'org.freedesktop.Notifications',
|
||
|
'/org/freedesktop/Notifications',
|
||
|
null
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_emitChangedSignal(localDBus) {
|
||
|
if (localDBus) {
|
||
|
this._pendingLocalSignal = true;
|
||
|
} else {
|
||
|
this._pendingSystemSignal = true;
|
||
|
}
|
||
|
if (this._signalTimerID) {
|
||
|
GLib.source_remove(this._signalTimerID);
|
||
|
}
|
||
|
this._signalTimerID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
|
||
|
this._signalTimerID = 0;
|
||
|
this._updateAllAvailabilities();
|
||
|
if (this._pendingLocalSignal) {
|
||
|
this.emit('changed-availability-local');
|
||
|
}
|
||
|
if (this._pendingSystemSignal) {
|
||
|
this.emit('changed-availability-system');
|
||
|
}
|
||
|
this._pendingLocalSignal = false;
|
||
|
this._pendingSystemSignal = false;
|
||
|
return false;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
checkIsAvailable(serviceName, inSystemBus) {
|
||
|
if (inSystemBus) {
|
||
|
return this._availableInSystemBus.includes(serviceName);
|
||
|
} else {
|
||
|
return this._availableInLocalBus.includes(serviceName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_updateAllAvailabilities() {
|
||
|
this._availableInLocalBus = this._updateAvailability(this._dbusLocalProxy);
|
||
|
this._availableInSystemBus = this._updateAvailability(this._dbusSystemProxy);
|
||
|
}
|
||
|
|
||
|
_updateAvailability(proxy) {
|
||
|
// We read both the well-known names actually running and those available as activatables,
|
||
|
// and generate a single list with both. Thus a service will be "enabled" if it is running
|
||
|
// or if it is activatable.
|
||
|
|
||
|
let availableNames = [];
|
||
|
let names = proxy.ListNamesSync();
|
||
|
for (let n of names[0]) {
|
||
|
if (n.startsWith(':')) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!(n in availableNames)) {
|
||
|
availableNames.push(n);
|
||
|
}
|
||
|
}
|
||
|
let names2 = proxy.ListActivatableNamesSync();
|
||
|
for (let n of names2[0]) {
|
||
|
if (n.startsWith(':')) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!(n in availableNames)) {
|
||
|
availableNames.push(n);
|
||
|
}
|
||
|
}
|
||
|
return availableNames;
|
||
|
}
|
||
|
|
||
|
_getNextTag() {
|
||
|
this._xmlIndex++;
|
||
|
let pos = this._xmlData.indexOf('<', this._xmlIndex);
|
||
|
if (pos == -1) {
|
||
|
return null;
|
||
|
}
|
||
|
let pos2 = this._xmlData.indexOf('>', pos);
|
||
|
if (pos2 == -1) {
|
||
|
return null;
|
||
|
}
|
||
|
this._xmlIndex = pos;
|
||
|
return this._xmlData.substring(pos + 1, pos2).trim();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Extracts the XML definition for an interface from the raw data returned by DBus Introspection.
|
||
|
* This is needed because DBus Introspection returns a single XML file with all the interfaces
|
||
|
* supported by an object, while DBusProxyWrapper requires an XML with only the desired interface.
|
||
|
*/
|
||
|
_parseXML(data, interfaceName) {
|
||
|
this._xmlIndex = -1;
|
||
|
this._xmlData = data;
|
||
|
let tag;
|
||
|
while (true) {
|
||
|
tag = this._getNextTag();
|
||
|
if (tag === null) {
|
||
|
return null;
|
||
|
}
|
||
|
if (!tag.startsWith('interface ')) {
|
||
|
continue;
|
||
|
}
|
||
|
if (tag.includes(interfaceName)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
let start = this._xmlIndex;
|
||
|
while (true) {
|
||
|
tag = this._getNextTag();
|
||
|
if (tag === null) {
|
||
|
return null;
|
||
|
}
|
||
|
if (!tag.startsWith('/interface')) {
|
||
|
continue;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return `<node>\n ${data.substring(start, 1 + data.indexOf('>', this._xmlIndex))}\n</node>`;
|
||
|
}
|
||
|
|
||
|
getInterface(serviceName, objectName, interfaceName, inSystemBus, forceIntrospection) {
|
||
|
if ((interfaceName in DBusInterfaces.DBusInterfaces) && !forceIntrospection) {
|
||
|
return DBusInterfaces.DBusInterfaces[interfaceName];
|
||
|
} else {
|
||
|
let data = this.getIntrospectionData(serviceName, objectName, inSystemBus);
|
||
|
if (data == null) {
|
||
|
return null;
|
||
|
} else {
|
||
|
return this._parseXML(data, interfaceName);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
getIntrospectionData(serviceName, objectName, inSystemBus) {
|
||
|
let wraper = new Gio.DBusProxy.makeProxyWrapper(DBusInterfaces.DBusInterfaces['org.freedesktop.DBus.Introspectable'])(
|
||
|
inSystemBus ? Gio.DBus.system : Gio.DBus.session,
|
||
|
serviceName,
|
||
|
objectName,
|
||
|
null
|
||
|
);
|
||
|
let data = null;
|
||
|
try {
|
||
|
data = wraper.IntrospectSync()[0];
|
||
|
} catch (e) {
|
||
|
logError(e, 'Error getting introspection data over Dbus.');
|
||
|
}
|
||
|
if (data == null) {
|
||
|
return null;
|
||
|
}
|
||
|
if (!data.includes('interface')) {
|
||
|
return null; // if it doesn't exist, return null
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
doNotify(header, text) {
|
||
|
/*
|
||
|
* The notification interface in GLib.Application requires a .desktop file, which
|
||
|
* we can't have, so we must use directly the Notification DBus interface
|
||
|
*/
|
||
|
this._notifyProxy.NotifyRemote('', 0, '', header, text, [], {}, -1, () => {});
|
||
|
}
|
||
|
}
|
||
|
Signals.addSignalMethods(DBusManager.prototype);
|
||
|
|
||
|
|
||
|
class DbusOperationsManager {
|
||
|
constructor(freeDesktopFileManager, gnomeNautilusPreview, gnomeArchiveManager) {
|
||
|
this.freeDesktopFileManager = freeDesktopFileManager;
|
||
|
this.gnomeNautilusPreviewManager = gnomeNautilusPreview;
|
||
|
this.gnomeArchiveManager = gnomeArchiveManager;
|
||
|
}
|
||
|
|
||
|
_sendNoProxyError(callback) {
|
||
|
if (callback) {
|
||
|
GLib.idle_add(GLib.PRIORITY_LOW, () => {
|
||
|
callback(null, 'noProxy');
|
||
|
return false;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ShowItemPropertiesRemote(selection, timestamp, callback = null) {
|
||
|
if (!this.freeDesktopFileManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.freeDesktopFileManager.proxy.ShowItemPropertiesRemote(selection,
|
||
|
this._getStartupId(selection, timestamp),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error showing properties: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
ShowItemsRemote(showInFilesList, timestamp, callback = null) {
|
||
|
if (!this.freeDesktopFileManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.freeDesktopFileManager.proxy.ShowItemsRemote(showInFilesList,
|
||
|
this._getStartupId(showInFilesList, timestamp),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error showing file on desktop: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
ShowFileRemote(uri, integer, boolean, callback = null) {
|
||
|
if (!this.gnomeNautilusPreviewManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.gnomeNautilusPreviewManager.proxy.ShowFileRemote(uri, integer, boolean,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error previewing file: ${error.message}`);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ExtractRemote(extractFileItem, folder, boolean, callback = null) {
|
||
|
if (!this.gnomeArchiveManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.gnomeArchiveManager.proxy.ExtractRemote(extractFileItem, folder, true,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error extracting files: ${error.message}`);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
CompressRemote(compressFileItems, folder, boolean, callback = null) {
|
||
|
if (!this.gnomeArchiveManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.gnomeArchiveManager.proxy.CompressRemote(compressFileItems, folder, boolean,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error compressing files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
_getStartupId(fileUris, timestamp) {
|
||
|
if (!timestamp) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
const context = Gdk.Display.get_default().get_app_launch_context();
|
||
|
context.set_timestamp(timestamp);
|
||
|
|
||
|
if (!this._fileManager) {
|
||
|
this._fileManager = Gio.File.new_for_path('/').query_default_handler(null);
|
||
|
}
|
||
|
|
||
|
return context.get_startup_notify_id(this._fileManager,
|
||
|
fileUris.map(uri => Gio.File.new_for_uri(uri)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
class RemoteFileOperationsManager extends DbusOperationsManager {
|
||
|
constructor(fileOperationsManager, freeDesktopFileManager, gnomeNautilusPreview, gnomeArchiveManager) {
|
||
|
super(freeDesktopFileManager, gnomeNautilusPreview, gnomeArchiveManager);
|
||
|
this.fileOperationsManager = fileOperationsManager;
|
||
|
this._createPlatformData();
|
||
|
}
|
||
|
|
||
|
_createPlatformData() {
|
||
|
this.platformData = this.fileOperationsManager.platformData = () => {
|
||
|
let parentWindow = Gtk.get_current_event()?.get_window();
|
||
|
|
||
|
let parentHandle = '';
|
||
|
if (parentWindow) {
|
||
|
try {
|
||
|
imports.gi.versions.GdkX11 = '3.0';
|
||
|
const {GdkX11} = imports.gi;
|
||
|
const topLevel = parentWindow.get_effective_toplevel();
|
||
|
|
||
|
if (topLevel.constructor.$gtype === GdkX11.X11Window.$gtype) {
|
||
|
const xid = GdkX11.X11Window.prototype.get_xid.call(topLevel);
|
||
|
parentHandle = `x11:${xid}`;
|
||
|
} /* else if (topLevel instanceof GdkWayland.Toplevel) {
|
||
|
FIXME: Need Gtk4 to use GdkWayland
|
||
|
const handle = GdkWayland.Toplevel.prototype.export_handle.call(topLevel);
|
||
|
parentHandle = `wayland:${handle}`;
|
||
|
} */
|
||
|
} catch (e) {
|
||
|
logError(e, 'Impossible to determine the parent window');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
'parent-handle': new GLib.Variant('s', parentHandle),
|
||
|
'timestamp': new GLib.Variant('u', Gtk.get_current_event_time()),
|
||
|
'window-position': new GLib.Variant('s', 'center'),
|
||
|
};
|
||
|
};
|
||
|
}
|
||
|
|
||
|
MoveURIsRemote(fileList, uri, callback) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.MoveURIsRemote(
|
||
|
fileList,
|
||
|
uri,
|
||
|
this.platformData(),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error moving files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
CopyURIsRemote(fileList, uri, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.CopyURIsRemote(
|
||
|
fileList,
|
||
|
uri,
|
||
|
this.platformData(),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error copying files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
RenameURIRemote(fileList, uri, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.RenameURIRemote(
|
||
|
fileList,
|
||
|
uri,
|
||
|
this.platformData(),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error copying files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
TrashURIsRemote(fileList, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.TrashURIsRemote(
|
||
|
fileList,
|
||
|
this.platformData(),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error moving files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
DeleteURIsRemote(fileList, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.DeleteURIsRemote(
|
||
|
fileList,
|
||
|
this.platformData(),
|
||
|
(source, error) => {
|
||
|
if (callback) {
|
||
|
callback(source, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error deleting files on the desktop: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
EmptyTrashRemote(askConfirmation, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.EmptyTrashRemote(
|
||
|
askConfirmation,
|
||
|
this.platformData(),
|
||
|
(source, error) => {
|
||
|
if (callback) {
|
||
|
callback(source, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error trashing files on the desktop: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
UndoRemote(callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.UndoRemote(
|
||
|
this.platformData(),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error performing undo: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
RedoRemote(callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.RedoRemote(
|
||
|
this.platformData(),
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error performing redo: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
UndoStatus() {
|
||
|
return this.fileOperationsManager.proxy.UndoStatus;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
class LegacyRemoteFileOperationsManager extends DbusOperationsManager {
|
||
|
constructor(fileOperationsManager, freeDesktopFileManager, gnomeNautilusPreview, gnomeArchiveManager) {
|
||
|
super(freeDesktopFileManager, gnomeNautilusPreview, gnomeArchiveManager);
|
||
|
this.fileOperationsManager = fileOperationsManager;
|
||
|
}
|
||
|
|
||
|
MoveURIsRemote(fileList, uri, callback) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.MoveURIsRemote(
|
||
|
fileList,
|
||
|
uri,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error moving files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
CopyURIsRemote(fileList, uri, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.CopyURIsRemote(
|
||
|
fileList,
|
||
|
uri,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error copying files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
RenameURIRemote(fileList, uri, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.RenameFileRemote(
|
||
|
fileList,
|
||
|
uri,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error renaming files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
TrashURIsRemote(fileList, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.TrashFilesRemote(
|
||
|
fileList,
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error moving files: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
DeleteURIsRemote(fileList, callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.TrashFilesRemote(
|
||
|
fileList,
|
||
|
(source, error) => {
|
||
|
this.EmptyTrashRemote();
|
||
|
if (callback) {
|
||
|
callback(source, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error deleting files on the desktop: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
EmptyTrashRemote(callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.EmptyTrashRemote(
|
||
|
(source, error) => {
|
||
|
if (callback) {
|
||
|
callback(source, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error trashing files on the desktop: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
UndoRemote(callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.UndoRemote(
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error performing undo: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
RedoRemote(callback = null) {
|
||
|
if (!this.fileOperationsManager.proxy) {
|
||
|
this._sendNoProxyError(callback);
|
||
|
return;
|
||
|
}
|
||
|
this.fileOperationsManager.proxy.RedoRemote(
|
||
|
(result, error) => {
|
||
|
if (callback) {
|
||
|
callback(result, error);
|
||
|
}
|
||
|
if (error) {
|
||
|
log(`Error performing redo: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
UndoStatus() {
|
||
|
return this.fileOperationsManager.proxy.UndoStatus;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
function init() {
|
||
|
dbusManagerObject = new DBusManager();
|
||
|
|
||
|
let data = dbusManagerObject.getIntrospectionData(
|
||
|
'org.gnome.Nautilus',
|
||
|
'/org/gnome/Nautilus/FileOperations2',
|
||
|
false);
|
||
|
|
||
|
if (data) {
|
||
|
// NautilusFileOperations2
|
||
|
NautilusFileOperations2 = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'org.gnome.Nautilus',
|
||
|
'/org/gnome/Nautilus/FileOperations2',
|
||
|
'org.gnome.Nautilus.FileOperations2',
|
||
|
false,
|
||
|
'Nautilus'
|
||
|
);
|
||
|
} else {
|
||
|
print('Emulating NautilusFileOperations2 with the old NautilusFileOperations interface');
|
||
|
// Emulate NautilusFileOperations2 with the old interface
|
||
|
NautilusFileOperations2 = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'org.gnome.Nautilus',
|
||
|
'/org/gnome/Nautilus',
|
||
|
'org.gnome.Nautilus.FileOperations',
|
||
|
false,
|
||
|
'Nautilus'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
FreeDesktopFileManager = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'org.freedesktop.FileManager1',
|
||
|
'/org/freedesktop/FileManager1',
|
||
|
'org.freedesktop.FileManager1',
|
||
|
false,
|
||
|
'Nautilus'
|
||
|
);
|
||
|
|
||
|
GnomeNautilusPreview = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'org.gnome.NautilusPreviewer',
|
||
|
'/org/gnome/NautilusPreviewer',
|
||
|
'org.gnome.NautilusPreviewer',
|
||
|
false,
|
||
|
'Nautilus-Sushi'
|
||
|
);
|
||
|
|
||
|
GnomeArchiveManager = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'org.gnome.ArchiveManager1',
|
||
|
'/org/gnome/ArchiveManager1',
|
||
|
'org.gnome.ArchiveManager1',
|
||
|
false,
|
||
|
'File-roller'
|
||
|
);
|
||
|
|
||
|
GtkVfsMetadata = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'org.gtk.vfs.Metadata',
|
||
|
'/org/gtk/vfs/metadata',
|
||
|
'org.gtk.vfs.Metadata',
|
||
|
false,
|
||
|
'Gvfs daemon'
|
||
|
);
|
||
|
|
||
|
SwitcherooControl = new ProxyManager(
|
||
|
dbusManagerObject,
|
||
|
'net.hadess.SwitcherooControl',
|
||
|
'/net/hadess/SwitcherooControl',
|
||
|
'net.hadess.SwitcherooControl',
|
||
|
true,
|
||
|
'Switcheroo control'
|
||
|
);
|
||
|
discreteGpuAvailable = SwitcherooControl.isAvailable;
|
||
|
SwitcherooControl.connect('changed-status', (obj, newStatus) => {
|
||
|
discreteGpuAvailable = newStatus;
|
||
|
});
|
||
|
|
||
|
if (data) {
|
||
|
RemoteFileOperations = new RemoteFileOperationsManager(NautilusFileOperations2, FreeDesktopFileManager, GnomeNautilusPreview, GnomeArchiveManager);
|
||
|
} else {
|
||
|
RemoteFileOperations = new LegacyRemoteFileOperationsManager(NautilusFileOperations2, FreeDesktopFileManager, GnomeNautilusPreview, GnomeArchiveManager);
|
||
|
}
|
||
|
|
||
|
extensionControl = Gio.DBusActionGroup.get(
|
||
|
Gio.DBus.session,
|
||
|
'com.rastersoft.dingextension',
|
||
|
'/com/rastersoft/dingextension/control'
|
||
|
);
|
||
|
|
||
|
return dbusManagerObject;
|
||
|
}
|