/* DING: Desktop Icons New Generation for GNOME Shell * * Adw/Gtk4 Port Copyright (C) 2025 Sundeep Mediratta (smedius@gmail.com) * * 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 . */ import {Gdk, Gio, GLib, Gtk, DesktopAppInfo} from '../dependencies/gi.js'; import {_} from '../dependencies/gettext.js'; export {DesktopActions}; export {DesktopBackgroundMenu}; const DesktopActions = class { constructor(desktopManager) { this._desktopManager = desktopManager; this._Prefs = desktopManager.Prefs; this._mainApp = desktopManager.mainApp; this._DBusUtils = desktopManager.DBusUtils; this._dbusManager = desktopManager.dbusManager; this._dragManager = desktopManager.dragManager; this._DesktopIconsUtil = desktopManager.DesktopIconsUtil; this._fileItemMenu = desktopManager.fileItemMenu; this._Enums = desktopManager.Enums; this._desktopMonitor = desktopManager.desktopMonitor; this._windowManager = desktopManager.windowManager; this.lastAnchorSelected = null; this._isCut = false; this._clipboardFiles = null; this._intDBusSignalMonitoring(); this._createMenuActionGroup(); } // Create the menu action group // and add the actions to the main app // and set the accelerators // for the actions _createMenuActionGroup() { const newFolder = Gio.SimpleAction.new('doNewFolder', null); newFolder.connect('activate', () => { this._desktopManager.doNewFolder().catch(e => console.error(e)); }); this._mainApp.add_action(newFolder); this.doPasteSimpleAction = Gio.SimpleAction.new('doPaste', null); this.doPasteSimpleAction.connect( 'activate', async () => { try { if (!(this._desktopManager.popupmenu || this._desktopManager.fileItemMenu.popupmenu)) await this._updateClipboard().catch(e => logError(e)); this._doPaste(); } catch (e) { console.error(e, 'Paste action failed'); } } ); this._mainApp.add_action(this.doPasteSimpleAction); this.doUndoSimpleAction = Gio.SimpleAction.new('doUndo', null); this.doUndoSimpleAction.connect( 'activate', () => this._doUndo() ); this._mainApp.add_action(this.doUndoSimpleAction); this.doRedoSimpleAction = Gio.SimpleAction.new('doRedo', null); this.doRedoSimpleAction.connect( 'activate', () => this._doRedo() ); this._mainApp.add_action(this.doRedoSimpleAction); const selectAll = Gio.SimpleAction.new('selectAll', null); selectAll.connect( 'activate', () => this._selectAll() ); this._mainApp.add_action(selectAll); const showDesktopInFiles = Gio.SimpleAction.new('showDesktopInFiles', null); showDesktopInFiles.connect( 'activate', () => this._onOpenDesktopInFilesClicked().catch(e => logError(e)) ); this._mainApp.add_action(showDesktopInFiles); const openDesktopInTerminal = Gio.SimpleAction.new('openDesktopInTerminal', null); openDesktopInTerminal.connect( 'activate', this._onOpenTerminalClicked.bind(this) ); this._mainApp.add_action(openDesktopInTerminal); const changeBackGround = Gio.SimpleAction.new('changeBackGround', null); changeBackGround.connect( 'activate', () => { const desktopFile = DesktopAppInfo.new('gnome-background-panel.desktop'); const context = Gdk.Display.get_default().get_app_launch_context(); context.set_timestamp(Gdk.CURRENT_TIME); desktopFile.launch([], context); } ); this._mainApp.add_action(changeBackGround); const changeDisplaySettings = Gio.SimpleAction.new('changeDisplaySettings', null); changeDisplaySettings.connect( 'activate', () => { const desktopFile = DesktopAppInfo.new('gnome-display-panel.desktop'); const context = Gdk.Display.get_default().get_app_launch_context(); context.set_timestamp(Gdk.CURRENT_TIME); desktopFile.launch([], context); } ); this._mainApp.add_action(changeDisplaySettings); const changeDesktopIconSettings = Gio.SimpleAction.new('changeDesktopIconSettings', null); changeDesktopIconSettings.connect( 'activate', this._showPreferences.bind(this) ); this._mainApp.add_action(changeDesktopIconSettings); const cleanUpIconsAction = Gio.SimpleAction.new('cleanUpIcons', null); cleanUpIconsAction.connect( 'activate', () => this._desktopManager.sortAllFilesFromGridsByPosition() ); this._mainApp.add_action(cleanUpIconsAction); const keepArrangedAction = this._Prefs.desktopSettings.create_action('keep-arranged'); this._mainApp.add_action(keepArrangedAction); this._Prefs.desktopSettings.bind( 'keep-arranged', cleanUpIconsAction, 'enabled', 16 ); this._mainApp.add_action( this._Prefs.desktopSettings.create_action('keep-stacked')); this._mainApp.add_action( this._Prefs.desktopSettings.create_action('sort-special-folders')); const radioArrangeAction = Gio.SimpleAction.new_stateful( 'arrangeaction', GLib.VariantType.new('s'), GLib.Variant.new_string(this._Prefs.desktopSettings.get_string( this._Enums.SortOrder.ORDER)) ); radioArrangeAction.connect('change-state', this._syncArrangeOrder.bind(this)); this._mainApp.add_action(radioArrangeAction); this.arrangeAction = radioArrangeAction; const arrangeByName = Gio.SimpleAction.new('arrangeByName', null); arrangeByName.connect( 'activate', () => this._mainApp.activate_action( 'arrangeaction', new GLib.Variant('s', 'NAME') ) ); this._mainApp.add_action(arrangeByName); const arrangeByDescendingName = Gio.SimpleAction.new('arrangeByDescendingName', null); arrangeByDescendingName.connect( 'activate', () => this._mainApp.activate_action( 'arrangeaction', new GLib.Variant('s', 'DESCENDINGNAME') ) ); this._mainApp.add_action(arrangeByDescendingName); const arrangeByModifiedTime = Gio.SimpleAction.new('arrangeByModifiedTime', null); arrangeByModifiedTime.connect( 'activate', () => this._mainApp.activate_action( 'arrangeaction', new GLib.Variant('s', 'MODIFIEDTIME') ) ); this._mainApp.add_action(arrangeByModifiedTime); const arrangeByKind = Gio.SimpleAction.new('arrangeByKind', null); arrangeByKind.connect( 'activate', () => this._mainApp.activate_action( 'arrangeaction', new GLib.Variant('s', 'KIND') ) ); this._mainApp.add_action(arrangeByKind); const arrangeBySize = Gio.SimpleAction.new('arrangeBySize', null); arrangeBySize.connect( 'activate', () => this._mainApp.activate_action( 'arrangeaction', new GLib.Variant('s', 'SIZE') ) ); this._mainApp.add_action(arrangeBySize); const findFilesAction = Gio.SimpleAction.new('findFiles', null); findFilesAction.connect( 'activate', () => this._desktopManager.findFiles(null) ); this._mainApp.add_action(findFilesAction); const updateDesktop = Gio.SimpleAction.new('updateDesktop', null); updateDesktop.connect( 'activate', async () => { await this._desktopManager.reLoadDesktop().catch(e => { console.log( `Exception while updating desktop after pressing "F5": ${e.message}\n${e.stack}`); }); } ); this._mainApp.add_action(updateDesktop); const showHideHiddenFiles = Gio.SimpleAction.new('showHideHiddenFiles', null); showHideHiddenFiles.connect( 'activate', () => { this._Prefs.gtkSettings.set_boolean('show-hidden', !this._Prefs.showHidden); } ); this._mainApp.add_action(showHideHiddenFiles); const unselectAll = Gio.SimpleAction.new('unselectAll', null); unselectAll.connect( 'activate', () => { this._desktopManager.unselectAll(); if (this.searchString) this.searchString = null; } ); this._mainApp.add_action(unselectAll); const previewAction = Gio.SimpleAction.new('previewAction', null); previewAction.connect('activate', () => { if (this._desktopManager.popupmenu || this._desktopManager.fileItemMenu.popupmenu || !this.activeFileItem) return; const RemoteOperation = this._DBusUtils.RemoteFileOperations; RemoteOperation.ShowFileRemote(this.activeFileItem.uri, 0, true); }); this._mainApp.add_action(previewAction); const toggleKeyboardSelection = Gio.SimpleAction.new('toggleKeyboardSelection', null); toggleKeyboardSelection.connect('activate', () => { this._toggleKeyboardSelection(); }); this._mainApp.add_action(toggleKeyboardSelection); const chooseIconLeft = Gio.SimpleAction.new('chooseIconLeft', null); chooseIconLeft.connect('activate', () => { this._selectFileItemInDirection(Gdk.KEY_Left); }); this._mainApp.add_action(chooseIconLeft); const chooseIconRight = Gio.SimpleAction.new('chooseIconRight', null); chooseIconRight.connect('activate', () => { this._selectFileItemInDirection(Gdk.KEY_Right); }); this._mainApp.add_action(chooseIconRight); const chooseIconUp = Gio.SimpleAction.new('chooseIconUp', null); chooseIconUp.connect('activate', () => { this._selectFileItemInDirection(Gdk.KEY_Up); }); this._mainApp.add_action(chooseIconUp); const chooseIconDown = Gio.SimpleAction.new('chooseIconDown', null); chooseIconDown.connect('activate', () => { this._selectFileItemInDirection(Gdk.KEY_Down); }); this._mainApp.add_action(chooseIconDown); const menuKeyPressed = Gio.SimpleAction.new('menuKeyPressed', null); menuKeyPressed.connect('activate', () => { this._menuKeyPressed(); }); this._mainApp.add_action(menuKeyPressed); const displayShellBackgroundMenu = Gio.SimpleAction.new('displayShellBackgroundMenu', null); displayShellBackgroundMenu.connect('activate', () => { this._DBusUtils.RemoteExtensionControl.showShellBackgroundMenu(); }); this._mainApp.add_action(displayShellBackgroundMenu); const createDesktopShortcut = new Gio.SimpleAction({ name: 'createDesktopShortcut', parameter_type: new GLib.VariantType('a{sv}'), }); createDesktopShortcut.connect('activate', (action, parameter) => { this._createDesktopShortcut(parameter.recursiveUnpack()); }); this._mainApp.add_action(createDesktopShortcut); const textEntryAccelsTurnOff = Gio.SimpleAction.new('textEntryAccelsTurnOff', null); textEntryAccelsTurnOff.connect('activate', () => { this._textEntryAccelsTurnOff(); }); this._mainApp.add_action(textEntryAccelsTurnOff); const newDocument = Gio.SimpleAction.new('newDocument', new GLib.VariantType('s')); newDocument.connect('activate', (action, parameter) => { this._newDocument(parameter.deep_unpack()); }); this._mainApp.add_action(newDocument); const showShortcutViewer = Gio.SimpleAction.new('showShortcutViewer', null); showShortcutViewer.connect('activate', () => { this._showShortcutViewer(); }); this._mainApp.add_action(showShortcutViewer); const toggleVisibility = Gio.SimpleAction.new('toggleVisibility', null); toggleVisibility.connect('activate', () => { this._windowManager.toggleVisibility(); }); this._mainApp.add_action(toggleVisibility); } _updateClipboard() { return new Promise(resolve => { const clipboard = Gdk.Display.get_default().get_clipboard(); this._isCut = false; this._clipboardFiles = null; /* * Before Gnome Shell 40, St API couldn't access binary data in the * clipboard, only text data. Also, the original Desktop Icons was a * pure extension, so it was limited to what Clutter and St offered. * That was the reason why Nautilus accepted a text format for CUT * and COPY operations in the form * * x-special/nautilus-clipboard * OPERATION * FILE_URI * [FILE_URI] * [...] * * In Gnome Shell 40, St was enhanced and now it supports binary * data; that's why Nautilus migrated to a binary format identified * by the atom 'x-special/gnome-copied-files', where the CUT or COPY * operation is shared. * * To maintain compatibility, we first check if there's binary data * in that atom, and if not, we check if there is text data in the * old format. */ let text = null; const textDecoder = new TextDecoder(); if (clipboard.get_formats()) { const mimetypes = clipboard.get_formats().to_string(); if (mimetypes.includes('x-special/gnome-copied-files')) { try { clipboard.read_async(['x-special/gnome-copied-files'], GLib.PRIORITY_DEFAULT, null, (actor, result) => { try { const success = actor.read_finish(result); const bytes = success[0].read_bytes(8192, null); text = textDecoder.decode(bytes.get_data()); text = 'x-special/nautilus-clipboard\n' + `${text}\n`; this._setClipboardContent(text); resolve(true); } catch (e) { console.log( 'Exception while reading clipboard:' + `${e.message}\n${e.stack}` ); this._setClipboardContent(text); resolve(false); } }); } catch (e) { console.log( `Exception while reading clipboard mimetype x-special/gnome-copied-files: ${e.message}\n${e.stack}` ); this._setClipboardContent(text); resolve(false); } } else if (mimetypes.includes('text/plain')) { try { clipboard.read_async(['text/plain'], GLib.PRIORITY_DEFAULT, null, (actor, result) => { try { const success = actor.read_finish(result); const bytes = success[0].read_bytes(8192, null); text = textDecoder.decode(bytes.get_data()); if (text && !text.endsWith('\n')) text += '\n'; this._setClipboardContent(text); resolve(true); } catch (e) { this._setClipboardContent(text); resolve(false); } }); } catch (e) { console.log( 'Exception while reading clipboard media-type ' + `"text/plain": ${e.message}\n${e.stack}` ); this._setClipboardContent(text); resolve(false); } } else { this._setClipboardContent(text); resolve(false); } } else { this._setClipboardContent(text); resolve(false); } }); } _intDBusSignalMonitoring() { const fileOperationsManager = this._DBusUtils.RemoteFileOperations.fileOperationsManager; fileOperationsManager.connectToProxy( 'g-properties-changed', this._undoStatusChanged.bind(this) ); fileOperationsManager.connect('changed-status', (actor, available) => { if (available) this._syncUndoRedo(); else this._syncUndoRedo(true); } ); if (fileOperationsManager.isAvailable) this._syncUndoRedo(); } _setClipboardContent(text) { const [valid, isCut, files] = this._parseClipboardText(text); if (valid) { this._isCut = isCut; this._clipboardFiles = files; } this.doPasteSimpleAction.set_enabled(valid); } _parseClipboardText(text) { if (text === null) return [false, false, null]; const lines = text.split('\n'); const [mime, action, ...files] = lines; if (mime !== 'x-special/nautilus-clipboard') return [false, false, null]; if (!['copy', 'cut'].includes(action)) return [false, false, null]; const isCut = action === 'cut'; /* Last line is empty due to the split */ if (files.length <= 1) return [false, false, null]; /* Remove last line */ files.pop(); return [true, isCut, files]; } _syncUndoRedo(hide = false) { if (hide) { this.doUndoSimpleAction.set_enabled(false); this.doRedoSimpleAction.set_enabled(false); return; } switch (this._DBusUtils.RemoteFileOperations.UndoStatus()) { case this._Enums.UndoStatus.UNDO: this.doUndoSimpleAction.set_enabled(true); this.doRedoSimpleAction.set_enabled(false); break; case this._Enums.UndoStatus.REDO: this.doUndoSimpleAction.set_enabled(false); this.doRedoSimpleAction.set_enabled(true); break; default: this.doUndoSimpleAction.set_enabled(false); this.doRedoSimpleAction.set_enabled(false); break; } } _undoStatusChanged(proxy, properties) { if ('UndoStatus' in properties.deep_unpack()) this._syncUndoRedo(); } _doUndo() { this._DBusUtils.RemoteFileOperations.UndoRemote(); } _doRedo() { this._DBusUtils.RemoteFileOperations.RedoRemote(); } _doPaste() { if (this._clipboardFiles === null) return; if (!this._clickX && !this._clickY) return; const pasteCoordinates = [this._clickX, this._clickY]; const desktopDir = this._desktopDir.get_uri(); const remoteOperations = this._DBusUtils.RemoteFileOperations; if (this._isCut) { // This pops up GNOME Files error dialog, which is what we want. remoteOperations.MoveURIsRemote(this._clipboardFiles, desktopDir); } else { this._dragManager.clearFileCoordinates( this._clipboardFiles, pasteCoordinates, {doCopy: true} ); remoteOperations.CopyURIsRemote(this._clipboardFiles, desktopDir); } } _selectAll() { for (let fileItem of this._displayList) { if (fileItem.isAllSelectable) fileItem.setSelected(); } } async _onOpenDesktopInFilesClicked() { const context = Gdk.Display.get_default().get_app_launch_context(); context.set_timestamp(Gdk.CURRENT_TIME); try { await Gio.AppInfo.launch_default_for_uri_async( this._desktopDir.get_uri(), context, null); } catch (e) { if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) { const header = _('Unable to open Desktop in Gnome Files'); const text = _(`Desktop Folder ${this._desktopDir.get_path()} does not exist`); this._dbusManager.doNotify(header, text); return; } console.error( e, `Error opening desktop in GNOME Files: ${e.message}` ); } } _onOpenTerminalClicked() { this._desktopManager.fileItemActions.launchTerminal(); } _showPreferences() { if (this.preferencesWindow) { this._dbusManager.doNotify( _('Preferences Window is Open'), _('This Window is open. Please switch to the active window.') ); return; } this.preferencesWindow = this._Prefs.getAdwPreferencesWindow(); this.preferencesWindow.connect('close-request', () => { this.preferencesWindow = null; }); this.preferencesWindow.set_title(_('Settings')); // Do not make modal or skip-taskbar as we have a .desktop icon // showing up in the dock for the window to assist navigation. // const modal = true; // this._DesktopIconsUtil.windowHidePagerTaskbarModal( // this.preferencesWindow, modal); this.preferencesWindow.show(); } _syncArrangeOrder(action, newValue) { if (!action.enabled) return; const currentSetting = this._Prefs.desktopSettings.get_string( this._Enums.SortOrder.ORDER); const newValueString = newValue.deep_unpack(); if (currentSetting !== newValueString) { action.set_enabled(false); this._Prefs.desktopSettings.set_string( this._Enums.SortOrder.ORDER, newValueString); action.set_enabled(true); } const currentState = action.get_state().deep_unpack(); if (currentState !== newValueString) action.set_state(newValue); this._desktopManager.onSortOrderChanged(); } _selectFileItemInDirection(symbol) { // Anchoring var index; var multiplier; const previousSelection = this.currentSelection; let selection = previousSelection; if (!selection || selection.length === 0) { if (this.activeFileItem && this.activeFileItem.isStackMarker) selection = [this.activeFileItem]; else selection = this._displayList; } if (!selection || selection.length === 0) return false; if (!this.keyboardSelected) this._setKeyboardSelected(selection[0]); let selected = this.keyboardSelected; if (!selected) return false; let selectedCoordinates = selected.getCoordinates(); // Navigation switch (symbol) { case Gdk.KEY_Left: index = 0; multiplier = -1; break; case Gdk.KEY_Right: index = 0; multiplier = 1; break; case Gdk.KEY_Up: index = 1; multiplier = -1; break; case Gdk.KEY_Down: index = 1; multiplier = 1; break; default: return false; } const selectedCenterX = (selectedCoordinates[0] + selectedCoordinates[2]) / 2; const selectedCenterY = (selectedCoordinates[1] + selectedCoordinates[3]) / 2; let bestScore = null; let newItem = null; let wrapItem = null; let wrapExtreme = null; let wrapSecondary = null; for (let item of this._displayList) { if (item === selected) continue; let itemCoordinates = item.getCoordinates(); const itemCenterX = (itemCoordinates[0] + itemCoordinates[2]) / 2; const itemCenterY = (itemCoordinates[1] + itemCoordinates[3]) / 2; const deltaX = itemCenterX - selectedCenterX; const deltaY = itemCenterY - selectedCenterY; const primary = index === 0 ? deltaX : deltaY; const secondary = index === 0 ? Math.abs(deltaY) : Math.abs(deltaX); // Forward candidates (in the requested direction). if (primary * multiplier > 0) { const score = Math.abs(primary) + secondary; if ((bestScore === null) || (score < bestScore)) { bestScore = score; newItem = item; } continue; } // Wrap candidate: farthest in the opposite direction, // with row/col bias. if (wrapExtreme === null || (multiplier > 0 ? primary < wrapExtreme : primary > wrapExtreme ) || (primary === wrapExtreme && secondary < wrapSecondary) ) { wrapExtreme = primary; wrapSecondary = secondary; wrapItem = item; } } if (newItem === null) newItem = wrapItem || selected; // Selection logic const {ctrl, shift} = this._desktopManager.modifierMode; const keptSelection = previousSelection || []; if (shift && selected) { // Shift: select everything in the rectangle between anchor and focus, // extending (not clearing) the existing selection. const anchor = this.lastAnchorSelected && this._displayList.includes(this.lastAnchorSelected) ? this.lastAnchorSelected : selected; const focusItem = anchor !== selected ? selected : newItem; const sRect = anchor.iconRectangle; const nRect = focusItem.iconRectangle; const minX = Math.min(sRect.x, nRect.x); const maxX = Math.max( sRect.x + sRect.width, nRect.x + nRect.width ); const minY = Math.min(sRect.y, nRect.y); const maxY = Math.max( sRect.y + sRect.height, nRect.y + nRect.height ); this._displayList.forEach(item => { const rect = item.iconRectangle; const withinX = rect.x <= maxX && rect.x + rect.width >= minX; const withinY = rect.y <= maxY && rect.y + rect.height >= minY; if (withinX && withinY) item.setSelected(); }); // Keep any prior selection intact keptSelection.forEach(item => item.setSelected()); focusItem.setSelected(); this.lastAnchorSelected = focusItem; if (anchor !== selected) { // Cancel navigation when extending from a previous anchor this._setKeyboardSelected(selected); this._desktopManager.fileItemMenu.activeFileItem = selected; this.activeFileItem = selected; return true; } } else if (ctrl) { // Ctrl: do not alter existing selection if (newItem.isSelected) this.lastAnchorSelected = newItem; } else { // Default: move selection to the new item only this._desktopManager.unselectAll(); newItem.setSelected(); this.lastAnchorSelected = newItem; } // Always keyboard-focus the new item this._setKeyboardSelected(newItem); this._desktopManager.fileItemMenu.activeFileItem = newItem; this.activeFileItem = newItem; return true; } _toggleKeyboardSelection() { const item = this.keyboardSelected; if (!item) return; if (item.isSelected) item.unsetSelected(); else item.setSelected(); } _setKeyboardSelected(fileItem) { this._displayList.forEach(f => f.keyboardUnSelected()); fileItem.keyboardSelected(); } _menuKeyPressed() { const selection = this.currentSelection; if (selection) { const fileItem = selection[0]; const X = fileItem.iconRectangle.x + fileItem.iconRectangle.width / 2; const Y = fileItem.iconRectangle.y + fileItem.iconRectangle.height / 2; this._fileItemMenu.showMenu(fileItem, 3, 0, 0, X, Y, false, false); } else { const grid = this._desktops.filter(f => f.coordinatesBelongToThisGrid(this._clickX, this._clickY)); if (!grid) return; this._desktopManager.onPressButton( null, null, this._clickX, this._clickY, 3, false, false, grid[0] ); } } async _newDocument(template) { if (!template) return; const file = Gio.File.new_for_path(template); const finalName = this._desktopMonitor.getDesktopUniqueFileName(file.get_basename()); const destination = this._desktopDir.get_child(finalName); try { await file.copy(destination, Gio.FileCopyFlags.NONE, null, null); try { const info = new Gio.FileInfo(); info.set_attribute_string( 'metadata::nautilus-drop-position', `${this._clickX},${this._clickY}` ); info.set_attribute_string( 'metadata::desktop-icon-position', '' ); info.set_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE, 0o600); await destination.set_attributes_async( info, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null ); } catch (e) { console.error( e, `Failed to set template metadata ${e.message}`); } } catch (e) { if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) this._desktopManager._performSanityChecks(); else console.error(e, `Failed to create template ${e.message}`); const header = _('Template Creation Error'); const text = _('Could not create document'); this._dbusManager.doNotify(header, text); } } async _createDesktopShortcut(shortcutinfo) { const fileList = [shortcutinfo.uri]; const X = parseInt(shortcutinfo.X); const Y = parseInt(shortcutinfo.Y); await this._dragManager.clearFileCoordinates( fileList, [X, Y], {doCopy: true} ); await this._DesktopIconsUtil.copyDesktopFileToDesktop( shortcutinfo.uri, [X, Y] ); } async updateClipboard() { await this._updateClipboard() .catch(e => console.error(e, 'Error updating Clipboard')); } get currentSelection() { return this._desktopManager.getCurrentSelection(); } get activeFileItem() { return this._fileItemMenu.activeFileItem; } get keyboardSelected() { let keyboardSelectedItem = null; for (let fileItem of this._displayList) { if (fileItem.KeyboardSelected) { keyboardSelectedItem = fileItem; break; } } return keyboardSelectedItem; } set activeFileItem(fileItem) { this._fileItemMenu.activeFileItem = fileItem; } get _displayList() { return this._desktopManager._displayList; } get _clickX() { return this._desktopManager._clickX; } get _clickY() { return this._desktopManager._clickY; } get _desktopDir() { return this._desktopMonitor.desktopDir; } get _desktops() { return this._desktopManager._desktops; } }; const DesktopBackgroundMenu = class { constructor(desktopManager) { this._desktopManager = desktopManager; this._mainApp = desktopManager.mainApp; this._Prefs = desktopManager.Prefs; this._desktopActions = desktopManager.desktopActions; this._waitDelayMs = desktopManager.DesktopIconsUtil.waitDelayMs; this._Prefs = desktopManager.Prefs; this._desktopIconsUtil = desktopManager.DesktopIconsUtil; this._templatesScriptsManager = desktopManager.templatesScriptsManager; this._FileUtils = desktopManager.FileUtils; this._Enums = desktopManager.Enums; this._startMonitoringTemplatesDir(); } _startMonitoringTemplatesDir() { this._templatesMonitor = new this._templatesScriptsManager.TemplatesScriptsManager( this._desktopIconsUtil.getTemplatesDir(), this._templatesDirSelectionFilter.bind(this), { appName: 'app.newDocument', FileUtils: this._FileUtils, Enums: this._Enums, } ); } _templatesDirSelectionFilter(fileinfo) { const name = this._desktopIconsUtil.getFileExtensionOffset( fileinfo.get_name()).basename; const hiddenfile = name.substring(0, 1) === '.'; if (!this._Prefs.showHidden && hiddenfile) return null; return name; } _createDesktopBackgroundGioMenu() { this.desktopBackgroundGioMenu = Gio.Menu.new(); const sortingRadioMenu = Gio.Menu.new(); sortingRadioMenu.append( _('Name'), 'app.arrangeaction::NAME'); sortingRadioMenu.append( _('Name Z-A'), 'app.arrangeaction::DESCENDINGNAME'); sortingRadioMenu.append( _('Modified Time'), 'app.arrangeaction::MODIFIEDTIME'); sortingRadioMenu.append( _('Type'), 'app.arrangeaction::KIND'); sortingRadioMenu.append( _('Size'), 'app.arrangeaction::SIZE'); const sortingSubMenu = Gio.Menu.new(); this._keepArrangedMenuItem = Gio.MenuItem.new( _('Keep Arranged…'), 'app.keep-arranged'); if (!this._Prefs.keepStacked) sortingSubMenu.append_item(this._keepArrangedMenuItem); sortingSubMenu.append( _('Keep Stacked by Type…'), 'app.keep-stacked'); sortingSubMenu.append( _('Sort Home/Drives/Trash…'), 'app.sort-special-folders'); sortingSubMenu.append_section(null, sortingRadioMenu); const settingSubMenu = Gio.Menu.new(); settingSubMenu.append( _('Change Desktop'), 'app.changeDesktop'); const restoreDefaultDesktop = this._mainApp.lookup_action('restoreDefaultDesktop'); if (restoreDefaultDesktop.get_enabled()) { settingSubMenu.append( _('Restore Default Desktop'), 'app.restoreDefaultDesktop' ); } settingSubMenu.append( _('Desktop Icon Settings'), 'app.changeDesktopIconSettings'); settingSubMenu.append( _('Show Shortcuts'), 'app.showShortcutViewer'); this.desktopBackgroundGioMenu.append( _('New Folder'), 'app.doNewFolder'); const templatesmenu = this._templatesMonitor.getGioMenu(); if (!(templatesmenu === null)) { this.desktopBackgroundGioMenu.append_submenu( _('New Document'), templatesmenu); } const pasteUndoRedoMenu = Gio.Menu.new(); if (this._mainApp.lookup_action('doPaste').get_enabled()) pasteUndoRedoMenu.append(_('Paste'), 'app.doPaste'); if (this._mainApp.lookup_action('doUndo').get_enabled()) pasteUndoRedoMenu.append(_('Undo'), 'app.doUndo'); if (this._mainApp.lookup_action('doRedo').get_enabled()) pasteUndoRedoMenu.append(_('Redo'), 'app.doRedo'); if (pasteUndoRedoMenu.get_n_items()) this.desktopBackgroundGioMenu.append_section(null, pasteUndoRedoMenu); const selectAllMenu = Gio.Menu.new(); selectAllMenu.append(_('Select All'), 'app.selectAll'); this.desktopBackgroundGioMenu.append_section(null, selectAllMenu); const sortingMenu = Gio.Menu.new(); if (!this._Prefs.keepStacked) { const cleanUpMenuItem = Gio.MenuItem.new( _('Arrange Icons'), 'app.cleanUpIcons'); sortingMenu.append_item(cleanUpMenuItem); } const arrangeSubMenuItem = Gio.MenuItem.new_submenu( _('Arrange By…'), sortingSubMenu); sortingMenu.append_item(arrangeSubMenuItem); this.desktopBackgroundGioMenu.append_section(null, sortingMenu); const desktopTerminalMenu = Gio.Menu.new(); const nautilusName = this._Prefs.NautilusName; desktopTerminalMenu.append( _('Show Desktop In {0}').replace('{0}', nautilusName), 'app.showDesktopInFiles' ); const terminalString = this._Prefs.TerminalName; desktopTerminalMenu.append( _('Open In {0}').replace('{0}', terminalString), 'app.openDesktopInTerminal' ); this.desktopBackgroundGioMenu.append_section( null, desktopTerminalMenu); const settingsMenu = Gio.Menu.new(); const settingSubMenuItem = Gio.MenuItem.new_submenu( _('Settings'), settingSubMenu); settingsMenu.append_item(settingSubMenuItem); this.desktopBackgroundGioMenu.append_section(null, settingsMenu); if (this._Prefs.showDesktopWidgets) { const widgetLayerMenu = Gio.Menu.new(); widgetLayerMenu.append( _('Edit Widgets…'), 'app.toggleWidgetLayer'); this.desktopBackgroundGioMenu.append_section(null, widgetLayerMenu); } const backgroundMenu = Gio.Menu.new(); backgroundMenu.append( _('Shell Menu…'), 'app.displayShellBackgroundMenu'); // Following deprectiated, Shell Menu has these options anyway // this.backgroundMenu.append( // _('Change Background…'), 'app.changeBackGround'); // this.backgroundMenu.append( // _('Display Settings'), 'app.changeDisplaySettings'); this.desktopBackgroundGioMenu.append_section(null, backgroundMenu); } updateTemplates() { this._templatesMonitor.updateEntries(); } menuclosed = () => { return new Promise(resolve => { this.popupmenuclosed = resolve; }); }; async showDesktopMenu(x, y, grid) { await this._desktopActions.updateClipboard() .catch(e => console.error(e, 'Error updating clipboard')); this._createDesktopBackgroundGioMenu(); this.popupmenu = Gtk.PopoverMenu.new_from_model(this.desktopBackgroundGioMenu); this.popupmenu.set_parent(grid._container); const menuLocation = new Gdk.Rectangle({x, y, width: 1, height: 1}); this.popupmenu.set_pointing_to(menuLocation); const menuGtkPosition = grid.getIntelligentPosition(menuLocation); if (menuGtkPosition !== null) this.popupmenu.set_position(menuGtkPosition); this.popupmenu.set_has_arrow(false); this.popupmenu.popup(); this.popupmenu.connect('closed', () => { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { this.popupmenu.unparent(); this.popupmenu = null; if (this.popupmenuclosed) this.popupmenuclosed(true); this.popupmenuclosed = null; return GLib.SOURCE_REMOVE; }); }); } };