218 lines
7.1 KiB
JavaScript
218 lines
7.1 KiB
JavaScript
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||
|
|
||
|
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="XUbuntuOpenLocationsXids" type="a{uas}" access="read"/>\
|
||
|
<property name="OpenWindowsWithLocations" type="a{sas}" access="read"/>\
|
||
|
</interface></node>';
|
||
|
|
||
|
const FileManager1Proxy = Gio.DBusProxy.makeProxyWrapper(FileManager1Iface);
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*
|
||
|
* While OpeWindowsWithLocations is part of upstream Nautilus, for many years
|
||
|
* prior, Ubuntu patched Nautilus to publish XUbuntuOpenLocationsXids, which is
|
||
|
* similar but uses Xids as the window identifiers instead of gtk window paths.
|
||
|
*
|
||
|
* When an old or unpatched Nautilus is running, we will observe the properties
|
||
|
* to always be empty arrays, but there will not be any correctness issues.
|
||
|
*/
|
||
|
var FileManager1Client = class DashToDock_FileManager1Client {
|
||
|
|
||
|
constructor() {
|
||
|
this._signalsHandler = new Utils.GlobalSignalsHandler();
|
||
|
|
||
|
this._locationMap = 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) {
|
||
|
global.log(error);
|
||
|
} else {
|
||
|
this._updateLocationMap();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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.workspace_manager,
|
||
|
'workspace-switched',
|
||
|
this._updateLocationMap.bind(this)
|
||
|
], [
|
||
|
global.display,
|
||
|
'window-entered-monitor',
|
||
|
this._updateLocationMap.bind(this)
|
||
|
], [
|
||
|
global.display,
|
||
|
'window-left-monitor',
|
||
|
this._updateLocationMap.bind(this)
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
destroy() {
|
||
|
this._signalsHandler.destroy();
|
||
|
this._proxy.run_dispose();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return an array of windows that are showing a location or
|
||
|
* sub-directories of that location.
|
||
|
*/
|
||
|
getWindows(location) {
|
||
|
let ret = new Set();
|
||
|
for (let [k,v] of this._locationMap) {
|
||
|
if (k.startsWith(location)) {
|
||
|
for (let l of v) {
|
||
|
ret.add(l);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return Array.from(ret);
|
||
|
}
|
||
|
|
||
|
_onPropertyChanged(proxy, changed, invalidated) {
|
||
|
let property = changed.unpack();
|
||
|
if (property &&
|
||
|
('XUbuntuOpenLocationsXids' in property ||
|
||
|
'OpenWindowsWithLocations' in property)) {
|
||
|
this._updateLocationMap();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_updateLocationMap() {
|
||
|
let properties = this._proxy.get_cached_property_names();
|
||
|
if (properties == null) {
|
||
|
// Nothing to check yet.
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (properties.includes('OpenWindowsWithLocations')) {
|
||
|
this._updateFromPaths();
|
||
|
} else if (properties.includes('XUbuntuOpenLocationsXids')) {
|
||
|
this._updateFromXids();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_updateFromPaths() {
|
||
|
let pathToLocations = this._proxy.OpenWindowsWithLocations;
|
||
|
let pathToWindow = getPathToWindow();
|
||
|
|
||
|
let locationToWindow = new Map();
|
||
|
for (let path in pathToLocations) {
|
||
|
let locations = pathToLocations[path];
|
||
|
for (let i = 0; i < locations.length; i++) {
|
||
|
let l = locations[i];
|
||
|
// Use a set to deduplicate when a window has a
|
||
|
// location open in multiple tabs.
|
||
|
if (!locationToWindow.has(l)) {
|
||
|
locationToWindow.set(l, new Set());
|
||
|
}
|
||
|
let window = pathToWindow.get(path);
|
||
|
if (window != null) {
|
||
|
locationToWindow.get(l).add(window);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this._locationMap = locationToWindow;
|
||
|
this.emit('windows-changed');
|
||
|
}
|
||
|
|
||
|
_updateFromXids() {
|
||
|
let xidToLocations = this._proxy.XUbuntuOpenLocationsXids;
|
||
|
let xidToWindow = getXidToWindow();
|
||
|
|
||
|
let locationToWindow = new Map();
|
||
|
for (let xid in xidToLocations) {
|
||
|
let locations = xidToLocations[xid];
|
||
|
for (let i = 0; i < locations.length; i++) {
|
||
|
let l = locations[i];
|
||
|
// Use a set to deduplicate when a window has a
|
||
|
// location open in multiple tabs.
|
||
|
if (!locationToWindow.has(l)) {
|
||
|
locationToWindow.set(l, new Set());
|
||
|
}
|
||
|
let window = xidToWindow.get(parseInt(xid));
|
||
|
if (window != null) {
|
||
|
locationToWindow.get(l).add(window);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this._locationMap = locationToWindow;
|
||
|
this.emit('windows-changed');
|
||
|
}
|
||
|
}
|
||
|
Signals.addSignalMethods(FileManager1Client.prototype);
|
||
|
|
||
|
/**
|
||
|
* Construct a map of gtk application window object paths to MetaWindows.
|
||
|
*/
|
||
|
function getPathToWindow() {
|
||
|
let pathToWindow = new Map();
|
||
|
|
||
|
for (let i = 0; i < global.workspace_manager.n_workspaces; i++) {
|
||
|
let ws = global.workspace_manager.get_workspace_by_index(i);
|
||
|
ws.list_windows().map(function(w) {
|
||
|
let path = w.get_gtk_window_object_path();
|
||
|
if (path != null) {
|
||
|
pathToWindow.set(path, w);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return pathToWindow;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct a map of XIDs to MetaWindows.
|
||
|
*
|
||
|
* This is somewhat annoying as you cannot lookup a window by
|
||
|
* XID in any way, and must iterate through all of them looking
|
||
|
* for a match.
|
||
|
*/
|
||
|
function getXidToWindow() {
|
||
|
let xidToWindow = new Map();
|
||
|
|
||
|
for (let i = 0; i < global.workspace_manager.n_workspaces; i++) {
|
||
|
let ws = global.workspace_manager.get_workspace_by_index(i);
|
||
|
ws.list_windows().map(function(w) {
|
||
|
let xid = guessWindowXID(w);
|
||
|
if (xid != null) {
|
||
|
xidToWindow.set(parseInt(xid), w);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return xidToWindow;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Guesses the X ID of a window.
|
||
|
*
|
||
|
* This is the basic implementation that is sufficient for Nautilus
|
||
|
* windows. The pixel-saver extension has a much more complex
|
||
|
* implementation if we ever need it.
|
||
|
*/
|
||
|
function guessWindowXID(win) {
|
||
|
try {
|
||
|
return win.get_description().match(/0x[0-9a-f]+/)[0];
|
||
|
} catch (err) {
|
||
|
return null;
|
||
|
}
|
||
|
}
|