desktopManager.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. /* Desktop Icons GNOME Shell extension
  2. *
  3. * Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. const Gtk = imports.gi.Gtk;
  19. const Clutter = imports.gi.Clutter;
  20. const GObject = imports.gi.GObject;
  21. const Gio = imports.gi.Gio;
  22. const GLib = imports.gi.GLib;
  23. const St = imports.gi.St;
  24. const Mainloop = imports.mainloop;
  25. const Meta = imports.gi.Meta;
  26. const Animation = imports.ui.animation;
  27. const Background = imports.ui.background;
  28. const DND = imports.ui.dnd;
  29. const Main = imports.ui.main;
  30. const GrabHelper = imports.ui.grabHelper;
  31. const ExtensionUtils = imports.misc.extensionUtils;
  32. const Me = ExtensionUtils.getCurrentExtension();
  33. const Extension = Me.imports.extension;
  34. const DesktopGrid = Me.imports.desktopGrid;
  35. const FileItem = Me.imports.fileItem;
  36. const Prefs = Me.imports.prefs;
  37. const DBusUtils = Me.imports.dbusUtils;
  38. const DesktopIconsUtil = Me.imports.desktopIconsUtil;
  39. const { loadInterfaceXML } = imports.misc.fileUtils;
  40. const Clipboard = St.Clipboard.get_default();
  41. const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
  42. var S_IWOTH = 0x00002;
  43. function getDpy() {
  44. return global.screen || global.display;
  45. }
  46. function findMonitorIndexForPos(x, y) {
  47. return getDpy().get_monitor_index_for_rect(new Meta.Rectangle({x, y}));
  48. }
  49. var DesktopManager = GObject.registerClass({
  50. Properties: {
  51. 'writable-by-others': GObject.ParamSpec.boolean(
  52. 'writable-by-others',
  53. 'WritableByOthers',
  54. 'Whether the desktop\'s directory can be written by others (o+w unix permission)',
  55. GObject.ParamFlags.READABLE,
  56. false
  57. )
  58. }
  59. }, class DesktopManager extends GObject.Object {
  60. _init(params) {
  61. super._init(params);
  62. this._layoutChildrenId = 0;
  63. this._deleteChildrenId = 0;
  64. this._monitorDesktopDir = null;
  65. this._desktopMonitorCancellable = null;
  66. this._desktopGrids = {};
  67. this._fileItemHandlers = new Map();
  68. this._fileItems = new Map();
  69. this._dragCancelled = false;
  70. this._queryFileInfoCancellable = null;
  71. this._unixMode = null;
  72. this._writableByOthers = null;
  73. this._discreteGpuAvailable = false;
  74. this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons());
  75. this._rubberBand = new St.Widget({ style_class: 'rubber-band' });
  76. this._rubberBand.hide();
  77. Main.layoutManager._backgroundGroup.add_child(this._rubberBand);
  78. this._grabHelper = new GrabHelper.GrabHelper(global.stage);
  79. this._mountMonitor = Gio.VolumeMonitor.get();
  80. this._mountAddedId = this._mountMonitor.connect('mount-added', (monitor, mount) => {
  81. this._recreateDesktopIcons(); });
  82. this._mountRemovedId = this._mountMonitor.connect('mount-removed', (monitor, mount) => {
  83. this._recreateDesktopIcons(); });
  84. this._addDesktopIcons();
  85. this._monitorDesktopFolder();
  86. this.settingsId = Prefs.settings.connect('changed', () => this._recreateDesktopIcons());
  87. this.gtkSettingsId = Prefs.gtkSettings.connect('changed', (obj, key) => {
  88. if (key == 'show-hidden')
  89. this._recreateDesktopIcons();
  90. });
  91. this.nautilusSettingsId = Prefs.nautilusSettings.connect('changed', (obj, key) => {
  92. if (key == 'show-image-thumbnails')
  93. this._recreateDesktopIcons();
  94. });
  95. this._selection = new Set();
  96. this._currentSelection = new Set();
  97. this._inDrag = false;
  98. this._dragXStart = Number.POSITIVE_INFINITY;
  99. this._dragYStart = Number.POSITIVE_INFINITY;
  100. this._switcherooNotifyId = global.connect('notify::switcheroo-control',
  101. () => this._updateDiscreteGpuAvailable());
  102. this._updateDiscreteGpuAvailable();
  103. }
  104. _updateDiscreteGpuAvailable() {
  105. this._switcherooProxy = global.get_switcheroo_control();
  106. if (this._switcherooProxy) {
  107. let prop = this._switcherooProxy.get_cached_property('HasDualGpu');
  108. this._discreteGpuAvailable = prop ? prop.unpack() : false;
  109. } else {
  110. this._discreteGpuAvailable = false;
  111. }
  112. }
  113. startRubberBand(x, y) {
  114. this._rubberBandInitialX = x;
  115. this._rubberBandInitialY = y;
  116. this._initRubberBandColor();
  117. this._updateRubberBand(x, y);
  118. this._rubberBand.show();
  119. this._grabHelper.grab({ actor: global.stage });
  120. Extension.lockActivitiesButton = true;
  121. this._stageReleaseEventId = global.stage.connect('button-release-event', (actor, event) => {
  122. this.endRubberBand();
  123. });
  124. this._rubberBandId = global.stage.connect('motion-event', (actor, event) => {
  125. /* In some cases, when the user starts a rubberband selection and ends it
  126. * (by releasing the left button) over a window instead of doing it over
  127. * the desktop, the stage doesn't receive the "button-release" event.
  128. * This happens currently with, at least, Dash to Dock extension, but
  129. * it probably also happens with other applications or extensions.
  130. * To fix this, we also end the rubberband selection if we detect mouse
  131. * motion in the stage without the left button pressed during a
  132. * rubberband selection.
  133. * */
  134. let button = event.get_state();
  135. if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
  136. this.endRubberBand();
  137. return;
  138. }
  139. [x, y] = event.get_coords();
  140. this._updateRubberBand(x, y);
  141. let x0, y0, x1, y1;
  142. if (x >= this._rubberBandInitialX) {
  143. x0 = this._rubberBandInitialX;
  144. x1 = x;
  145. } else {
  146. x1 = this._rubberBandInitialX;
  147. x0 = x;
  148. }
  149. if (y >= this._rubberBandInitialY) {
  150. y0 = this._rubberBandInitialY;
  151. y1 = y;
  152. } else {
  153. y1 = this._rubberBandInitialY;
  154. y0 = y;
  155. }
  156. for (let [fileUri, fileItem] of this._fileItems) {
  157. fileItem.emit('selected', true, true,
  158. fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
  159. }
  160. });
  161. }
  162. endRubberBand() {
  163. this._rubberBand.hide();
  164. Extension.lockActivitiesButton = false;
  165. this._grabHelper.ungrab();
  166. global.stage.disconnect(this._rubberBandId);
  167. global.stage.disconnect(this._stageReleaseEventId);
  168. this._rubberBandId = 0;
  169. this._stageReleaseEventId = 0;
  170. this._selection = new Set([...this._selection, ...this._currentSelection]);
  171. this._currentSelection.clear();
  172. }
  173. _updateRubberBand(currentX, currentY) {
  174. let x = this._rubberBandInitialX < currentX ? this._rubberBandInitialX
  175. : currentX;
  176. let y = this._rubberBandInitialY < currentY ? this._rubberBandInitialY
  177. : currentY;
  178. let width = Math.abs(this._rubberBandInitialX - currentX);
  179. let height = Math.abs(this._rubberBandInitialY - currentY);
  180. /* TODO: Convert to gobject.set for 3.30 */
  181. this._rubberBand.set_position(x, y);
  182. this._rubberBand.set_size(width, height);
  183. }
  184. _recreateDesktopIcons() {
  185. this.clearSelection();
  186. this._destroyDesktopIcons();
  187. this._addDesktopIcons();
  188. }
  189. _addDesktopIcons() {
  190. let first_element = true;
  191. forEachBackgroundManager(bgManager => {
  192. let newGrid = new DesktopGrid.DesktopGrid(bgManager);
  193. newGrid.connect('destroy', (actor) => {
  194. // if a grid loses its actor, remove it from the grid list
  195. for (let grid in this._desktopGrids)
  196. if (this._desktopGrids[grid] == actor) {
  197. delete this._desktopGrids[grid];
  198. break;
  199. }
  200. });
  201. newGrid.isFirst = first_element;
  202. first_element = false;
  203. this._desktopGrids[bgManager._monitorIndex] = newGrid;
  204. });
  205. this._scanFiles();
  206. }
  207. _destroyDesktopIcons() {
  208. Object.values(this._desktopGrids).forEach(grid => grid.destroy());
  209. this._desktopGrids = {};
  210. }
  211. /**
  212. * Initialize rubberband color from the GTK rubberband class
  213. * */
  214. _initRubberBandColor() {
  215. let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('rubberband', Gtk.StateFlags.NORMAL);
  216. let background_color =
  217. 'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.4)';
  218. this._rubberBand.set_style('background-color: ' + background_color);
  219. }
  220. async _scanFiles() {
  221. for (let [fileItem, fileItemHandler] of this._fileItemHandlers)
  222. Object.values(fileItemHandler).forEach(id => fileItem.disconnect(id));
  223. this._fileItemHandlers = new Map();
  224. if (!this._unixMode) {
  225. let desktopDir = DesktopIconsUtil.getDesktopDir();
  226. let fileInfo = desktopDir.query_info(Gio.FILE_ATTRIBUTE_UNIX_MODE,
  227. Gio.FileQueryInfoFlags.NONE,
  228. null);
  229. this._unixMode = fileInfo.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE);
  230. this._setWritableByOthers((this._unixMode & S_IWOTH) != 0);
  231. }
  232. try {
  233. let items = [];
  234. for (let item of await this._enumerateDesktop())
  235. items.push(item);
  236. for (let item of this._getMounts())
  237. items.push(item);
  238. let tmpFileItems = new Map();
  239. for (let [file, info, extra] of items) {
  240. let fileItem = new FileItem.FileItem(file, info, extra);
  241. tmpFileItems.set(fileItem.file.get_uri(), fileItem);
  242. let fileItemHandler = {}
  243. fileItemHandler.selectedId = fileItem.connect('selected',
  244. this._onFileItemSelected.bind(this));
  245. fileItemHandler.destroyId = fileItem.connect('destroy', () => {
  246. this._fileItemHandlers.delete(fileItem);
  247. });
  248. this._fileItemHandlers.set(fileItem, fileItemHandler);
  249. }
  250. this._fileItems = tmpFileItems;
  251. } catch (e) {
  252. if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
  253. log(`Error loading desktop files ${e.message}`, logError(e));
  254. return;
  255. }
  256. this.scheduleReLayoutChildren();
  257. }
  258. getDesktopFileNames () {
  259. let fileList = [];
  260. for (let [uri, item] of this._fileItems) {
  261. fileList.push(item.fileName);
  262. }
  263. return fileList;
  264. }
  265. _enumerateDesktop() {
  266. return new Promise((resolve, reject) => {
  267. if (this._desktopEnumerateCancellable)
  268. this._desktopEnumerateCancellable.cancel();
  269. this._desktopEnumerateCancellable = new Gio.Cancellable();
  270. let desktopDir = DesktopIconsUtil.getDesktopDir();
  271. desktopDir.enumerate_children_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
  272. Gio.FileQueryInfoFlags.NONE,
  273. GLib.PRIORITY_DEFAULT,
  274. this._desktopEnumerateCancellable,
  275. (source, result) => {
  276. try {
  277. let fileEnum = source.enumerate_children_finish(result);
  278. let resultGenerator = function *() {
  279. let info;
  280. for (let [newFolder, extras] of DesktopIconsUtil.getExtraFolders()) {
  281. yield [newFolder, newFolder.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, this._desktopEnumerateCancellable), extras];
  282. }
  283. while ((info = fileEnum.next_file(null)))
  284. yield [fileEnum.get_child(info), info, Prefs.FileType.NONE];
  285. }.bind(this);
  286. resolve(resultGenerator());
  287. } catch (e) {
  288. reject(e);
  289. }
  290. });
  291. });
  292. }
  293. _monitorDesktopFolder() {
  294. if (this._monitorDesktopDir) {
  295. this._monitorDesktopDir.cancel();
  296. this._monitorDesktopDir = null;
  297. }
  298. let desktopDir = DesktopIconsUtil.getDesktopDir();
  299. this._monitorDesktopDir = desktopDir.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null);
  300. this._monitorDesktopDir.set_rate_limit(1000);
  301. this._monitorDesktopDir.connect('changed', (obj, file, otherFile, eventType) => this._updateDesktopIfChanged(file, otherFile, eventType));
  302. }
  303. _getMounts() {
  304. let files = [];
  305. if (!Prefs.settings.get_boolean('show-mount'))
  306. return files;
  307. this._mountMonitor.get_mounts().forEach( mount => {
  308. if (this._isNetworkMount(mount))
  309. return;
  310. let file = mount.get_root();
  311. let info = file.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES,
  312. Gio.FileQueryInfoFlags.NONE,
  313. null);
  314. files.push([file, info, Prefs.FileType.MOUNT_DISK]);
  315. });
  316. return files;
  317. }
  318. _isNetworkMount(mount) {
  319. let volume = mount.get_volume();
  320. if (!volume)
  321. return true;
  322. return volume.get_identifier('class') == 'network';
  323. }
  324. checkIfSpecialFilesAreSelected() {
  325. for (let fileItem of this._selection) {
  326. if (fileItem.isSpecial)
  327. return true;
  328. }
  329. return false;
  330. }
  331. getNumberOfSelectedItems() {
  332. return this._selection.size;
  333. }
  334. get writableByOthers() {
  335. return this._writableByOthers;
  336. }
  337. _setWritableByOthers(value) {
  338. if (value == this._writableByOthers)
  339. return;
  340. this._writableByOthers = value
  341. this.notify('writable-by-others');
  342. }
  343. get discreteGpuAvailable() {
  344. return this._discreteGpuAvailable;
  345. }
  346. get switcherooProxyGPUs() {
  347. if (!this._switcherooProxy) {
  348. log('Could not apply discrete GPU environment, switcheroo-control not available');
  349. return null;
  350. }
  351. let prop = this._switcherooProxy.get_cached_property("GPUs");
  352. return prop ? prop.unpack() : null;
  353. }
  354. _updateDesktopIfChanged (file, otherFile, eventType) {
  355. let {
  356. DELETED, MOVED_IN, MOVED_OUT, CREATED, RENAMED, CHANGES_DONE_HINT, ATTRIBUTE_CHANGED
  357. } = Gio.FileMonitorEvent;
  358. let fileUri = file.get_uri();
  359. let fileItem = this._fileItems.get(fileUri);
  360. switch(eventType) {
  361. case RENAMED:
  362. this._fileItems.delete(fileUri);
  363. this._fileItems.set(otherFile.get_uri(), fileItem);
  364. fileItem.onFileRenamed(otherFile);
  365. return;
  366. case CHANGES_DONE_HINT:
  367. case ATTRIBUTE_CHANGED:
  368. /* a file changed, rather than the desktop itself */
  369. let desktopDir = DesktopIconsUtil.getDesktopDir();
  370. if (fileItem && file.get_uri() != desktopDir.get_uri()) {
  371. fileItem.onAttributeChanged();
  372. return;
  373. }
  374. if (this._queryFileInfoCancellable)
  375. this._queryFileInfoCancellable.cancel();
  376. file.query_info_async(Gio.FILE_ATTRIBUTE_UNIX_MODE,
  377. Gio.FileQueryInfoFlags.NONE,
  378. GLib.PRIORITY_DEFAULT,
  379. this._queryFileInfoCancellable,
  380. (source, result) => {
  381. try {
  382. let info = source.query_info_finish(result);
  383. this._queryFileInfoCancellable = null;
  384. this._unixMode = info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE);
  385. this._setWritableByOthers((this._unixMode & S_IWOTH) != 0);
  386. if (this._writableByOthers)
  387. log(`desktop-icons: Desktop is writable by others - will not allow launching any desktop files`);
  388. } catch(error) {
  389. if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
  390. global.log('Error getting desktop unix mode: ' + error);
  391. }
  392. });
  393. return;
  394. }
  395. // Only get a subset of events we are interested in.
  396. // Note that CREATED will emit a CHANGES_DONE_HINT
  397. if (![DELETED, MOVED_IN, MOVED_OUT, CREATED].includes(eventType))
  398. return;
  399. this._recreateDesktopIcons();
  400. }
  401. _setupDnD() {
  402. this._draggableContainer = new St.Widget({
  403. visible: true,
  404. width: 1,
  405. height: 1,
  406. x: 0,
  407. y: 0,
  408. style_class: 'draggable'
  409. });
  410. this._draggableContainer._delegate = this;
  411. this._draggable = DND.makeDraggable(this._draggableContainer,
  412. {
  413. manualMode: true,
  414. dragActorOpacity: 100
  415. });
  416. this._draggable.connect('drag-cancelled', () => this._onDragCancelled());
  417. this._draggable.connect('drag-end', () => this._onDragEnd());
  418. this._draggable._dragActorDropped = event => this._dragActorDropped(event);
  419. }
  420. dragStart() {
  421. if (this._inDrag) {
  422. return;
  423. }
  424. this._setupDnD();
  425. let event = Clutter.get_current_event();
  426. let [x, y] = event.get_coords();
  427. [this._dragXStart, this._dragYStart] = event.get_coords();
  428. this._inDrag = true;
  429. for (let fileItem of this._selection) {
  430. let clone = new Clutter.Clone({
  431. source: fileItem,
  432. reactive: false
  433. });
  434. clone.x = fileItem.get_transformed_position()[0];
  435. clone.y = fileItem.get_transformed_position()[1];
  436. this._draggableContainer.add_child(clone);
  437. }
  438. Main.layoutManager.uiGroup.add_child(this._draggableContainer);
  439. if (this._draggableContainer.get_fixed_position !== undefined)
  440. this._draggableContainer.allocate_preferred_size(0, 0);
  441. this._draggable.startDrag(x, y, global.get_current_time(), event.get_event_sequence());
  442. }
  443. _onDragCancelled() {
  444. let event = Clutter.get_current_event();
  445. let [x, y] = event.get_coords();
  446. this._dragCancelled = true;
  447. }
  448. _onDragEnd() {
  449. this._inDrag = false;
  450. Main.layoutManager.uiGroup.remove_child(this._draggableContainer);
  451. }
  452. _dragActorDropped(event) {
  453. let [dropX, dropY] = event.get_coords();
  454. let target = this._draggable._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
  455. dropX, dropY);
  456. // We call observers only once per motion with the innermost
  457. // target actor. If necessary, the observer can walk the
  458. // parent itself.
  459. let dropEvent = {
  460. dropActor: this._draggable._dragActor,
  461. targetActor: target,
  462. clutterEvent: event
  463. };
  464. for (let dragMonitor of DND.dragMonitors) {
  465. let dropFunc = dragMonitor.dragDrop;
  466. if (dropFunc)
  467. switch (dropFunc(dropEvent)) {
  468. case DragDropResult.FAILURE:
  469. case DragDropResult.SUCCESS:
  470. return true;
  471. case DragDropResult.CONTINUE:
  472. continue;
  473. }
  474. }
  475. // At this point it is too late to cancel a drag by destroying
  476. // the actor, the fate of which is decided by acceptDrop and its
  477. // side-effects
  478. this._draggable._dragCancellable = false;
  479. let destroyActor = false;
  480. while (target) {
  481. if (target._delegate && target._delegate.acceptDrop) {
  482. let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
  483. if (target._delegate.acceptDrop(this._draggable.actor._delegate,
  484. this._draggable._dragActor,
  485. targX,
  486. targY,
  487. event.get_time())) {
  488. // If it accepted the drop without taking the actor,
  489. // handle it ourselves.
  490. if (this._draggable._dragActor.get_parent() == Main.uiGroup) {
  491. if (this._draggable._restoreOnSuccess) {
  492. this._draggable._restoreDragActor(event.get_time());
  493. return true;
  494. }
  495. else {
  496. // We need this in order to make sure drag-end is fired
  497. destroyActor = true;
  498. }
  499. }
  500. this._draggable._dragInProgress = false;
  501. getDpy().set_cursor(Meta.Cursor.DEFAULT);
  502. this._draggable.emit('drag-end', event.get_time(), true);
  503. if (destroyActor) {
  504. this._draggable._dragActor.destroy();
  505. }
  506. this._draggable._dragComplete();
  507. return true;
  508. }
  509. }
  510. target = target.get_parent();
  511. }
  512. this._draggable._cancelDrag(event.get_time());
  513. return true;
  514. }
  515. acceptDrop(xEnd, yEnd) {
  516. let savedCoordinates = new Map();
  517. let [xDiff, yDiff] = [xEnd - this._dragXStart, yEnd - this._dragYStart];
  518. /* Remove all items before dropping new ones, so we can freely reposition
  519. * them.
  520. */
  521. for (let item of this._selection) {
  522. let [itemX, itemY] = item.get_transformed_position();
  523. let monitorIndex = findMonitorIndexForPos(itemX, itemY);
  524. savedCoordinates.set(item, [itemX, itemY]);
  525. this._desktopGrids[monitorIndex].removeFileItem(item);
  526. }
  527. for (let item of this._selection) {
  528. let [itemX, itemY] = savedCoordinates.get(item);
  529. /* Set the new ideal position where the item drop should happen */
  530. let newFileX = Math.round(xDiff + itemX);
  531. let newFileY = Math.round(yDiff + itemY);
  532. let monitorIndex = findMonitorIndexForPos(newFileX, newFileY);
  533. this._desktopGrids[monitorIndex].addFileItemCloseTo(item, newFileX, newFileY, DesktopGrid.StoredCoordinates.OVERWRITE);
  534. }
  535. return true;
  536. }
  537. selectionDropOnFileItem (fileItemDestination) {
  538. if (!fileItemDestination.isDirectory)
  539. return false;
  540. let droppedUris = [];
  541. for (let fileItem of this._selection) {
  542. if (fileItem.isSpecial)
  543. return false;
  544. if (fileItemDestination.file.get_uri() == fileItem.file.get_uri())
  545. return false;
  546. droppedUris.push(fileItem.file.get_uri());
  547. }
  548. if (droppedUris.length == 0)
  549. return true;
  550. DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(droppedUris,
  551. fileItemDestination.file.get_uri(),
  552. (result, error) => {
  553. if (error)
  554. throw new Error('Error moving files: ' + error.message);
  555. }
  556. );
  557. for (let fileItem of this._selection) {
  558. fileItem.state = FileItem.State.GONE;
  559. }
  560. this._recreateDesktopIcons();
  561. return true;
  562. }
  563. _resetGridsAndScheduleLayout() {
  564. this._deleteChildrenId = 0;
  565. Object.values(this._desktopGrids).forEach((grid) => grid.reset());
  566. if (!this._layoutChildrenId)
  567. this._layoutChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._layoutChildren());
  568. return GLib.SOURCE_REMOVE;
  569. }
  570. scheduleReLayoutChildren() {
  571. if (this._deleteChildrenId != 0)
  572. return;
  573. if (this._layoutChildrenId != 0) {
  574. GLib.source_remove(this._layoutChildrenId);
  575. this._layoutChildrenId = 0;
  576. }
  577. this._deleteChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._resetGridsAndScheduleLayout());
  578. }
  579. _addFileItemCloseTo(item) {
  580. let coordinates;
  581. let x = 0;
  582. let y = 0;
  583. let coordinatesAction = DesktopGrid.StoredCoordinates.ASSIGN;
  584. if (item.savedCoordinates != null) {
  585. [x, y] = item.savedCoordinates;
  586. coordinatesAction = DesktopGrid.StoredCoordinates.PRESERVE;
  587. }
  588. let monitorIndex = findMonitorIndexForPos(x, y);
  589. let desktopGrid = this._desktopGrids[monitorIndex];
  590. try {
  591. desktopGrid.addFileItemCloseTo(item, x, y, coordinatesAction);
  592. } catch (e) {
  593. log(`Error adding children to desktop: ${e.message}`);
  594. }
  595. }
  596. _layoutChildren() {
  597. let showHidden = Prefs.gtkSettings.get_boolean('show-hidden');
  598. /*
  599. * Paint the icons in two passes:
  600. * * first pass paints those that have their coordinates defined in the metadata
  601. * * second pass paints those new files that still don't have their definitive coordinates
  602. */
  603. for (let [fileUri, fileItem] of this._fileItems) {
  604. if (fileItem.savedCoordinates == null)
  605. continue;
  606. if (fileItem.state != FileItem.State.NORMAL)
  607. continue;
  608. if (!showHidden && fileItem.isHidden)
  609. continue;
  610. this._addFileItemCloseTo(fileItem);
  611. }
  612. for (let [fileUri, fileItem] of this._fileItems) {
  613. if (fileItem.savedCoordinates !== null)
  614. continue;
  615. if (fileItem.state != FileItem.State.NORMAL)
  616. continue;
  617. if (!showHidden && fileItem.isHidden)
  618. continue;
  619. this._addFileItemCloseTo(fileItem);
  620. }
  621. this._layoutChildrenId = 0;
  622. return GLib.SOURCE_REMOVE;
  623. }
  624. doRename() {
  625. if (this._selection.size != 1)
  626. return;
  627. let item = [...this._selection][0];
  628. if (item.canRename())
  629. item.doRename();
  630. }
  631. doPreview() {
  632. if (this._selection.size == 0)
  633. return;
  634. let item = [...this._selection][0];
  635. DBusUtils.GnomeNautilusPreviewProxy.ShowFileRemote(item.file.get_uri(), 0, false);
  636. }
  637. doOpen() {
  638. for (let fileItem of this._selection)
  639. fileItem.doOpen();
  640. }
  641. doTrash() {
  642. DBusUtils.NautilusFileOperationsProxy.TrashFilesRemote([...this._selection].map((x) => { return x.file.get_uri(); }),
  643. (source, error) => {
  644. if (error)
  645. throw new Error('Error trashing files on the desktop: ' + error.message);
  646. }
  647. );
  648. }
  649. doEmptyTrash() {
  650. DBusUtils.NautilusFileOperationsProxy.EmptyTrashRemote( (source, error) => {
  651. if (error)
  652. throw new Error('Error trashing files on the desktop: ' + error.message);
  653. });
  654. }
  655. _onFileItemSelected(fileItem, keepCurrentSelection, rubberBandSelection, addToSelection) {
  656. if (!keepCurrentSelection && !this._inDrag)
  657. this.clearSelection();
  658. let selection = keepCurrentSelection && rubberBandSelection ? this._currentSelection : this._selection;
  659. if (addToSelection)
  660. selection.add(fileItem);
  661. else
  662. selection.delete(fileItem);
  663. for (let [fileUri, fileItem] of this._fileItems)
  664. fileItem.isSelected = this._currentSelection.has(fileItem) || this._selection.has(fileItem);
  665. }
  666. clearSelection() {
  667. for (let [fileUri, fileItem] of this._fileItems)
  668. fileItem.isSelected = false;
  669. this._selection = new Set();
  670. this._currentSelection = new Set();
  671. }
  672. _getClipboardText(isCopy) {
  673. let action = isCopy ? 'copy' : 'cut';
  674. let text = `x-special/nautilus-clipboard\n${action}\n${
  675. [...this._selection].map(s => s.file.get_uri()).join('\n')
  676. }\n`;
  677. return text;
  678. }
  679. doCopy() {
  680. Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(true));
  681. }
  682. doCut() {
  683. Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(false));
  684. }
  685. destroy() {
  686. if (this._monitorDesktopDir)
  687. this._monitorDesktopDir.cancel();
  688. this._monitorDesktopDir = null;
  689. if (this._mountAddedId)
  690. this._mountMonitor.disconnect(this._mountAddedId);
  691. this._mountAddedId = 0;
  692. if (this._mountRemovedId)
  693. this._mountMonitor.disconnect(this._mountRemovedId);
  694. this._mountRemovedId = 0;
  695. if (this.settingsId)
  696. Prefs.settings.disconnect(this.settingsId);
  697. this.settingsId = 0;
  698. if (this.gtkSettingsId)
  699. Prefs.gtkSettings.disconnect(this.gtkSettingsId);
  700. this.gtkSettingsId = 0;
  701. if (this.nautilusSettingsId)
  702. Prefs.nautilusSettings.disconnect(this.nautilusSettingsId);
  703. this.nautilusSettingsId = 0;
  704. if (this._layoutChildrenId)
  705. GLib.source_remove(this._layoutChildrenId);
  706. this._layoutChildrenId = 0;
  707. if (this._deleteChildrenId)
  708. GLib.source_remove(this._deleteChildrenId);
  709. this._deleteChildrenId = 0;
  710. if (this._monitorsChangedId)
  711. Main.layoutManager.disconnect(this._monitorsChangedId);
  712. this._monitorsChangedId = 0;
  713. if (this._stageReleaseEventId)
  714. global.stage.disconnect(this._stageReleaseEventId);
  715. this._stageReleaseEventId = 0;
  716. if (this._rubberBandId)
  717. global.stage.disconnect(this._rubberBandId);
  718. this._rubberBandId = 0;
  719. this._rubberBand.destroy();
  720. if (this._queryFileInfoCancellable)
  721. this._queryFileInfoCancellable.cancel();
  722. Object.values(this._desktopGrids).forEach(grid => grid.destroy());
  723. this._desktopGrids = {}
  724. }
  725. });
  726. function forEachBackgroundManager(func) {
  727. Main.layoutManager._bgManagers.forEach(func);
  728. }