ad52d783bd
g_settings_schema_new() aborts if the requested schema is not found, so the previous approach of handling the case of unstable nautilus not being installed did not work. Instead, remove the use of the setting altogether - the original intent was to not have separate items for Desktop and Home in the places section if the nautilus key was set. As the section has been removed anyway, the impact of always adding the desktop folder is minimal (e.g. searching for "desktop" will match the desktop folder even if it's set to the home folder).
446 lines
15 KiB
JavaScript
446 lines
15 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const GLib = imports.gi.GLib;
|
|
const Gio = imports.gi.Gio;
|
|
const Shell = imports.gi.Shell;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
const Signals = imports.signals;
|
|
const St = imports.gi.St;
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
const _ = Gettext.gettext;
|
|
|
|
const DND = imports.ui.dnd;
|
|
const Main = imports.ui.main;
|
|
const Search = imports.ui.search;
|
|
const Util = imports.misc.util;
|
|
|
|
/**
|
|
* Represents a place object, which is most normally a bookmark entry,
|
|
* a mount/volume, or a special place like the Home Folder, Computer, and Network.
|
|
*
|
|
* @name: String title
|
|
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
|
|
* @launch: A JavaScript callback to launch the entry
|
|
*/
|
|
function PlaceInfo(id, name, iconFactory, launch) {
|
|
this._init(id, name, iconFactory, launch);
|
|
}
|
|
|
|
PlaceInfo.prototype = {
|
|
_init: function(id, name, iconFactory, launch) {
|
|
this.id = id;
|
|
this.name = name;
|
|
this._lowerName = name.toLowerCase();
|
|
this.iconFactory = iconFactory;
|
|
this.launch = launch;
|
|
},
|
|
|
|
matchTerms: function(terms) {
|
|
let mtype = Search.MatchType.NONE;
|
|
for (let i = 0; i < terms.length; i++) {
|
|
let term = terms[i];
|
|
let idx = this._lowerName.indexOf(term);
|
|
if (idx == 0) {
|
|
mtype = Search.MatchType.PREFIX;
|
|
} else if (idx > 0) {
|
|
if (mtype == Search.MatchType.NONE)
|
|
mtype = Search.MatchType.SUBSTRING;
|
|
} else {
|
|
return Search.MatchType.NONE;
|
|
}
|
|
}
|
|
return mtype;
|
|
},
|
|
|
|
isRemovable: function() {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
function PlaceDeviceInfo(mount) {
|
|
this._init(mount);
|
|
}
|
|
|
|
PlaceDeviceInfo.prototype = {
|
|
__proto__: PlaceInfo.prototype,
|
|
|
|
_init: function(mount) {
|
|
this._mount = mount;
|
|
this.name = mount.get_name();
|
|
this._lowerName = this.name.toLowerCase();
|
|
this.id = 'mount:' + mount.get_root().get_uri();
|
|
},
|
|
|
|
iconFactory: function(size) {
|
|
let icon = this._mount.get_icon();
|
|
return St.TextureCache.get_default().load_gicon(null, icon, size);
|
|
},
|
|
|
|
launch: function() {
|
|
Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
|
|
global.create_app_launch_context());
|
|
},
|
|
|
|
isRemovable: function() {
|
|
return this._mount.can_unmount();
|
|
},
|
|
|
|
remove: function() {
|
|
if (!this.isRemovable())
|
|
return;
|
|
|
|
if (this._mount.can_eject())
|
|
this._mount.eject(0, null, Lang.bind(this, this._removeFinish));
|
|
else
|
|
this._mount.unmount(0, null, Lang.bind(this, this._removeFinish));
|
|
},
|
|
|
|
_removeFinish: function(o, res, data) {
|
|
try {
|
|
if (this._mount.can_eject())
|
|
this._mount.eject_finish(res);
|
|
else
|
|
this._mount.unmount_finish(res);
|
|
} catch (e) {
|
|
let message = _("Failed to unmount '%s'").format(o.get_name());
|
|
Main.overview.shellInfo.setMessage(message,
|
|
Lang.bind(this, this.remove),
|
|
_("Retry"));
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
function PlacesManager() {
|
|
this._init();
|
|
}
|
|
|
|
PlacesManager.prototype = {
|
|
_init: function() {
|
|
this._defaultPlaces = [];
|
|
this._mounts = [];
|
|
this._bookmarks = [];
|
|
|
|
let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
|
|
let homeUri = homeFile.get_uri();
|
|
let homeLabel = Shell.util_get_label_for_uri (homeUri);
|
|
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
|
|
this._home = new PlaceInfo('special:home', homeLabel,
|
|
function(size) {
|
|
return St.TextureCache.get_default().load_gicon(null, homeIcon, size);
|
|
},
|
|
function() {
|
|
Gio.app_info_launch_default_for_uri(homeUri, global.create_app_launch_context());
|
|
});
|
|
|
|
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
|
|
let desktopFile = Gio.file_new_for_path (desktopPath);
|
|
let desktopUri = desktopFile.get_uri();
|
|
let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
|
|
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
|
|
this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
|
|
function(size) {
|
|
return St.TextureCache.get_default().load_gicon(null, desktopIcon, size);
|
|
},
|
|
function() {
|
|
Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context());
|
|
});
|
|
|
|
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
|
|
function (size) {
|
|
return new St.Icon({ icon_name: 'applications-internet',
|
|
icon_type: St.IconType.FULLCOLOR,
|
|
icon_size: size });
|
|
},
|
|
function () {
|
|
Util.spawn(['nautilus-connect-server']);
|
|
});
|
|
|
|
let networkApp = null;
|
|
try {
|
|
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('gnome-network-scheme.desktop');
|
|
} catch(e) {
|
|
try {
|
|
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('network-scheme.desktop');
|
|
} catch(e) {
|
|
log('Cannot create "Network" item, .desktop file not found or corrupt.');
|
|
}
|
|
}
|
|
|
|
if (networkApp != null) {
|
|
this._network = new PlaceInfo('special:network', networkApp.get_name(),
|
|
function(size) {
|
|
return networkApp.create_icon_texture(size);
|
|
},
|
|
function () {
|
|
networkApp.launch();
|
|
});
|
|
}
|
|
|
|
this._defaultPlaces.push(this._home);
|
|
this._defaultPlaces.push(this._desktopMenu);
|
|
|
|
if (this._network)
|
|
this._defaultPlaces.push(this._network);
|
|
|
|
this._defaultPlaces.push(this._connect);
|
|
|
|
/*
|
|
* Show devices, code more or less ported from nautilus-places-sidebar.c
|
|
*/
|
|
this._volumeMonitor = Gio.VolumeMonitor.get();
|
|
this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('volume-changed', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('mount-added', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('mount-changed', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._updateDevices));
|
|
this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
|
|
this._updateDevices();
|
|
|
|
this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), '.gtk-bookmarks']);
|
|
this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
|
|
let monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
|
|
this._bookmarkTimeoutId = 0;
|
|
monitor.connect('changed', Lang.bind(this, function () {
|
|
if (this._bookmarkTimeoutId > 0)
|
|
return;
|
|
/* Defensive event compression */
|
|
this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
|
|
this._bookmarkTimeoutId = 0;
|
|
this._reloadBookmarks();
|
|
return false;
|
|
}));
|
|
}));
|
|
|
|
this._reloadBookmarks();
|
|
},
|
|
|
|
_updateDevices: function() {
|
|
this._mounts = [];
|
|
|
|
/* first go through all connected drives */
|
|
let drives = this._volumeMonitor.get_connected_drives();
|
|
for (let i = 0; i < drives.length; i++) {
|
|
let volumes = drives[i].get_volumes();
|
|
for(let j = 0; j < volumes.length; j++) {
|
|
let mount = volumes[j].get_mount();
|
|
if(mount != null) {
|
|
this._addMount(mount);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add all volumes that is not associated with a drive */
|
|
let volumes = this._volumeMonitor.get_volumes();
|
|
for(let i = 0; i < volumes.length; i++) {
|
|
if(volumes[i].get_drive() != null)
|
|
continue;
|
|
|
|
let mount = volumes[i].get_mount();
|
|
if(mount != null) {
|
|
this._addMount(mount);
|
|
}
|
|
}
|
|
|
|
/* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
|
|
let mounts = this._volumeMonitor.get_mounts();
|
|
for(let i = 0; i < mounts.length; i++) {
|
|
if(mounts[i].is_shadowed())
|
|
continue;
|
|
|
|
if(mounts[i].get_volume())
|
|
continue;
|
|
|
|
this._addMount(mounts[i]);
|
|
}
|
|
|
|
/* We emit two signals, one for a generic 'all places' update
|
|
* and the other for one specific to mounts. We do this because
|
|
* clients like PlaceDisplay may only care about places in general
|
|
* being updated while clients like DashPlaceDisplay care which
|
|
* specific type of place got updated.
|
|
*/
|
|
this.emit('mounts-updated');
|
|
this.emit('places-updated');
|
|
|
|
},
|
|
|
|
_reloadBookmarks: function() {
|
|
|
|
this._bookmarks = [];
|
|
|
|
if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
|
|
return;
|
|
|
|
let [success, bookmarksContent, len] = GLib.file_get_contents(this._bookmarksPath);
|
|
|
|
if (!success)
|
|
return;
|
|
|
|
let bookmarks = bookmarksContent.split('\n');
|
|
|
|
let bookmarksToLabel = {};
|
|
let bookmarksOrder = [];
|
|
for (let i = 0; i < bookmarks.length; i++) {
|
|
let bookmarkLine = bookmarks[i];
|
|
let components = bookmarkLine.split(' ');
|
|
let bookmark = components[0];
|
|
if (bookmark in bookmarksToLabel)
|
|
continue;
|
|
let label = null;
|
|
if (components.length > 1)
|
|
label = components.slice(1).join(' ');
|
|
bookmarksToLabel[bookmark] = label;
|
|
bookmarksOrder.push(bookmark);
|
|
}
|
|
|
|
for (let i = 0; i < bookmarksOrder.length; i++) {
|
|
let bookmark = bookmarksOrder[i];
|
|
let label = bookmarksToLabel[bookmark];
|
|
let file = Gio.file_new_for_uri(bookmark);
|
|
if (!file.query_exists(null))
|
|
continue;
|
|
if (label == null)
|
|
label = Shell.util_get_label_for_uri(bookmark);
|
|
if (label == null)
|
|
continue;
|
|
let icon = Shell.util_get_icon_for_uri(bookmark);
|
|
|
|
let item = new PlaceInfo('bookmark:' + bookmark, label,
|
|
function(size) {
|
|
return St.TextureCache.get_default().load_gicon(null, icon, size);
|
|
},
|
|
function() {
|
|
Gio.app_info_launch_default_for_uri(bookmark, global.create_app_launch_context());
|
|
});
|
|
this._bookmarks.push(item);
|
|
}
|
|
|
|
/* See comment in _updateDevices for explanation why there are two signals. */
|
|
this.emit('bookmarks-updated');
|
|
this.emit('places-updated');
|
|
},
|
|
|
|
_addMount: function(mount) {
|
|
let devItem = new PlaceDeviceInfo(mount);
|
|
this._mounts.push(devItem);
|
|
},
|
|
|
|
getAllPlaces: function () {
|
|
return this.getDefaultPlaces().concat(this.getBookmarks(), this.getMounts());
|
|
},
|
|
|
|
getDefaultPlaces: function () {
|
|
return this._defaultPlaces;
|
|
},
|
|
|
|
getBookmarks: function () {
|
|
return this._bookmarks;
|
|
},
|
|
|
|
getMounts: function () {
|
|
return this._mounts;
|
|
},
|
|
|
|
_lookupIndexById: function(sourceArray, id) {
|
|
for (let i = 0; i < sourceArray.length; i++) {
|
|
let place = sourceArray[i];
|
|
if (place.id == id)
|
|
return i;
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
lookupPlaceById: function(id) {
|
|
let colonIdx = id.indexOf(':');
|
|
let type = id.substring(0, colonIdx);
|
|
let sourceArray = null;
|
|
if (type == 'special')
|
|
sourceArray = this._defaultPlaces;
|
|
else if (type == 'mount')
|
|
sourceArray = this._mounts;
|
|
else if (type == 'bookmark')
|
|
sourceArray = this._bookmarks;
|
|
return sourceArray[this._lookupIndexById(sourceArray, id)];
|
|
},
|
|
|
|
_removeById: function(sourceArray, id) {
|
|
sourceArray.splice(this._lookupIndexById(sourceArray, id), 1);
|
|
}
|
|
};
|
|
Signals.addSignalMethods(PlacesManager.prototype);
|
|
|
|
|
|
function PlaceSearchProvider() {
|
|
this._init();
|
|
}
|
|
|
|
PlaceSearchProvider.prototype = {
|
|
__proto__: Search.SearchProvider.prototype,
|
|
|
|
_init: function() {
|
|
Search.SearchProvider.prototype._init.call(this, _("PLACES & DEVICES"));
|
|
},
|
|
|
|
getResultMeta: function(resultId) {
|
|
let placeInfo = Main.placesManager.lookupPlaceById(resultId);
|
|
if (!placeInfo)
|
|
return null;
|
|
return { 'id': resultId,
|
|
'name': placeInfo.name,
|
|
'icon': placeInfo.iconFactory(Search.RESULT_ICON_SIZE) };
|
|
},
|
|
|
|
activateResult: function(id) {
|
|
let placeInfo = Main.placesManager.lookupPlaceById(id);
|
|
placeInfo.launch();
|
|
},
|
|
|
|
_compareResultMeta: function (idA, idB) {
|
|
let infoA = Main.placesManager.lookupPlaceById(idA);
|
|
let infoB = Main.placesManager.lookupPlaceById(idB);
|
|
return infoA.name.localeCompare(infoB.name);
|
|
},
|
|
|
|
_searchPlaces: function(places, terms) {
|
|
let multiplePrefixResults = [];
|
|
let prefixResults = [];
|
|
let multipleSubstringResults = [];
|
|
let substringResults = [];
|
|
|
|
terms = terms.map(String.toLowerCase);
|
|
|
|
for (let i = 0; i < places.length; i++) {
|
|
let place = places[i];
|
|
let mtype = place.matchTerms(terms);
|
|
if (mtype == Search.MatchType.MULTIPLE_PREFIX)
|
|
multiplePrefixResults.push(place.id);
|
|
else if (mtype == Search.MatchType.PREFIX)
|
|
prefixResults.push(place.id);
|
|
else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
|
|
multipleSubstringResults.push(place.id);
|
|
else if (mtype == Search.MatchType.SUBSTRING)
|
|
substringResults.push(place.id);
|
|
}
|
|
multiplePrefixResults.sort(this._compareResultMeta);
|
|
prefixResults.sort(this._compareResultMeta);
|
|
multipleSubstringResults.sort(this._compareResultMeta);
|
|
substringResults.sort(this._compareResultMeta);
|
|
return multiplePrefixResults.concat(prefixResults.concat(multipleSubstringResults.concat(substringResults)));
|
|
},
|
|
|
|
getInitialResultSet: function(terms) {
|
|
let places = Main.placesManager.getAllPlaces();
|
|
return this._searchPlaces(places, terms);
|
|
},
|
|
|
|
getSubsearchResultSet: function(previousResults, terms) {
|
|
let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
|
|
return this._searchPlaces(places, terms);
|
|
}
|
|
};
|