1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Signals = imports.signals;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Utils = Me.imports.utils;
const FileManager1Iface = '<node><interface name="org.freedesktop.FileManager1">\
<property name="OpenWindowsWithLocations" type="a{sas}" access="read"/>\
</interface></node>';
const FileManager1Proxy = Gio.DBusProxy.makeProxyWrapper(FileManager1Iface);
const Labels = Object.freeze({
WINDOWS: Symbol('windows'),
});
/**
* This class implements a client for the org.freedesktop.FileManager1 dbus
* interface, and specifically for the OpenWindowsWithLocations property
* which is published by Nautilus, but is not an official part of the interface.
*
* The property is a map from window identifiers to a list of locations open in
* the window.
*/
var FileManager1Client = class DashToDock_FileManager1Client {
constructor() {
this._signalsHandler = new Utils.GlobalSignalsHandler();
this._cancellable = new Gio.Cancellable();
this._windowsByPath = new Map();
this._windowsByLocation = new Map();
this._proxy = new FileManager1Proxy(Gio.DBus.session,
"org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1",
(initable, error) => {
// Use async construction to avoid blocking on errors.
if (error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
global.log(error);
return;
} else {
this._updateWindows();
this._updateLocationMap();
}
}, this._cancellable);
this._signalsHandler.add([
this._proxy,
'g-properties-changed',
this._onPropertyChanged.bind(this)
], [
// We must additionally listen for Screen events to know when to
// rebuild our location map when the set of available windows changes.
global.workspaceManager,
'workspace-added',
() => this._onWindowsChanged(),
], [
global.workspaceManager,
'workspace-removed',
() => this._onWindowsChanged(),
], [
global.display,
'window-entered-monitor',
() => this._onWindowsChanged(),
], [
global.display,
'window-left-monitor',
() => this._onWindowsChanged(),
]);
}
destroy() {
if (this._windowsUpdateIdle) {
GLib.source_remove(this._windowsUpdateIdle);
delete this._windowsUpdateIdle;
}
this._cancellable.cancel();
this._signalsHandler.destroy();
this._windowsByLocation.clear();
this._windowsByPath.clear()
this._proxy = null;
}
/**
* Return an array of windows that are showing a location or
* sub-directories of that location.
*/
getWindows(location) {
if (!location)
return [];
location += location.endsWith('/') ? '' : '/';
const windows = [];
this._windowsByLocation.forEach((wins, l) => {
if (l.startsWith(location))
windows.push(...wins);
});
return [...new Set(windows)];
}
_onPropertyChanged(proxy, changed, invalidated) {
let property = changed.unpack();
if (property &&
('OpenWindowsWithLocations' in property)) {
this._updateLocationMap();
}
}
_updateWindows() {
const oldSize = this._windowsByPath.size;
const oldPaths = this._windowsByPath.keys();
this._windowsByPath = Utils.getWindowsByObjectPath();
if (oldSize != this._windowsByPath.size)
return true;
return [...oldPaths].some(path => !this._windowsByPath.has(path));
}
_onWindowsChanged() {
if (this._windowsUpdateIdle)
return;
this._windowsUpdateIdle = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
if (this._updateWindows())
this._updateLocationMap();
delete this._windowsUpdateIdle;
return GLib.SOURCE_REMOVE;
});
}
_updateLocationMap() {
let properties = this._proxy.get_cached_property_names();
if (properties == null) {
// Nothing to check yet.
return;
}
if (properties.includes('OpenWindowsWithLocations')) {
this._updateFromPaths();
}
}
_locationMapsEquals(mapA, mapB) {
if (mapA.size !== mapB.size)
return false;
const setsEquals = (a, b) => a.size === b.size &&
[...a].every(value => b.has(value));
for (const [key, val] of mapA) {
const windowsSet = mapB.get(key);
if (!windowsSet || !setsEquals(windowsSet, val))
return false;
}
return true;
}
_updateFromPaths() {
const locationsByWindowsPath = this._proxy.OpenWindowsWithLocations;
const windowsByLocation = new Map();
this._signalsHandler.removeWithLabel(Labels.WINDOWS);
Object.entries(locationsByWindowsPath).forEach(([windowPath, locations]) => {
locations.forEach(location => {
const win = this._windowsByPath.get(windowPath);
const windowGroup = win ? [win] : [];
win?.foreach_transient(w => (windowGroup.push(w) || true));
windowGroup.forEach(window => {
location += location.endsWith('/') ? '' : '/';
// Use a set to deduplicate when a window has a
// location open in multiple tabs.
const windows = windowsByLocation.get(location) || new Set();
windows.add(window);
if (windows.size === 1)
windowsByLocation.set(location, windows);
this._signalsHandler.addWithLabel(Labels.WINDOWS, window,
'unmanaged', () => {
const wins = this._windowsByLocation.get(location);
wins.delete(window);
if (!wins.size)
this._windowsByLocation.delete(location);
this.emit('windows-changed');
});
});
});
});
if (!this._locationMapsEquals(this._windowsByLocation, windowsByLocation)) {
this._windowsByLocation = windowsByLocation;
this.emit('windows-changed');
}
}
}
Signals.addSignalMethods(FileManager1Client.prototype);
|