Files
system-taskbar/ding/app/fileItemMenu.js

1491 lines
51 KiB
JavaScript

/* DING: Desktop Icons New Generation for GNOME Shell
*
* Gtk4 Port Copyright (C) 2022 - 2025 Sundeep Mediratta (smedius@gmail.com)
* Copyright (C) 2021 Sergio Costas (rastersoft@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 <http://www.gnu.org/licenses/>.
*/
import {Gtk, Gdk, Gio, GLib} from '../dependencies/gi.js';
import {_, Gettext} from '../dependencies/gettext.js';
export {FileItemMenu};
export {FileItemActions};
const FileItemMenu = class {
constructor(desktopManager) {
this._desktopManager = desktopManager;
this._codePath = this._desktopManager.codePath;
this._mainApp = this._desktopManager.mainApp;
this._Prefs = this._desktopManager.Prefs;
this._Enums = this._desktopManager.Enums;
this._DesktopIconsUtil = this._desktopManager.DesktopIconsUtil;
this._DBusUtils = desktopManager.DBusUtils;
this._Enums = desktopManager.Enums;
this._dragManager = desktopManager.dragManager;
this._templatesScriptsManager =
this._desktopManager.templatesScriptsManager;
this._monitorScripts();
this._activeFileItem = null;
}
_monitorScripts() {
this.scriptsMonitor =
new this._templatesScriptsManager.TemplatesScriptsManager(
this._DesktopIconsUtil.getScriptsDir(),
this._scriptsDirSelectionFilter.bind(this),
{
appName: 'app.onScriptClicked',
FileUtils: this._desktopManager.FileUtils,
Enums: this._Enums,
}
);
}
_scriptsDirSelectionFilter(fileinfo) {
let name = fileinfo.get_name();
let hidden = name.substring(0, 1) === '.';
let executable = fileinfo.get_attribute_boolean('access::can-execute');
if (!hidden && executable)
return name;
else
return null;
}
/* Shows all possible values that can be assigned to this function */
showMenu(
fileItem,
_button = null,
X = null,
_Y = null,
x = null,
y = null,
_shiftSelected = false,
_controlSelected = false
) {
this.activeFileItem = fileItem;
const selectedItemsNum =
this._desktopManager.getNumberOfSelectedItems();
const scriptsSubmenu = this.scriptsMonitor.getGioMenu();
const menulocation = X
? new Gdk.Rectangle({x, y, width: 1, height: 1})
: fileItem._grid.getGlobaltoLocalRectangle(fileItem.iconRectangle);
this._menu = Gio.Menu.new();
const makeFolderMenu = Gio.Menu.new();
const openMenu = Gio.Menu.new();
const runAsProgram = Gio.Menu.new();
const cutCopyPasteMenu = Gio.Menu.new();
const trashMenu = Gio.Menu.new();
const allowLaunchingMenu = Gio.Menu.new();
const emptyTrashMenu = Gio.Menu.new();
const driveMenu = Gio.Menu.new();
const propertiesMenu = Gio.Menu.new();
const showInFilesMenu = Gio.Menu.new();
const openInTerminalMenu = Gio.Menu.new();
if (fileItem.isAllSelectable &&
!this._desktopManager.checkIfSpecialFilesAreSelected() &&
(selectedItemsNum >= 2)) {
makeFolderMenu.append(
Gettext
.ngettext(
'New Folder with {0} item',
'New Folder with {0} items',
selectedItemsNum)
.replace('{0}', selectedItemsNum),
'app.newfolderfromselection'
);
}
if (!this.activeFileItem.isStackMarker) {
if (selectedItemsNum > 1) {
openMenu.append(_('Open All...'), 'app.openMultipleFileAction');
} else {
let app;
let menuLabel;
if (this.activeFileItem.executableContentType &&
this.activeFileItem.isExecutable &&
this.activeFileItem.trustedDesktopFile)
menuLabel = _('Launch');
const canOpenUri = true;
const type = this.activeFileItem.attributeContentType;
const defaultApp = Gio.AppInfo.get_default_for_type;
const recommendedApp = Gio.AppInfo.get_recommended_for_type;
app = defaultApp(type, !canOpenUri)?.get_name();
if (!app)
app = recommendedApp(type)[0]?.get_name();
if (!app)
app = defaultApp(type, canOpenUri)?.get_name();
if (!this.activeFileItem.isDesktopFile && app)
menuLabel = _('Open with {foo}');
if (!menuLabel || this.activeFileItem.isAppImageFile)
menuLabel = _('Open');
if (menuLabel) {
openMenu.append(
menuLabel.replace('{foo}', app),
'app.openOneFileAction'
);
}
}
}
if (fileItem.isAllSelectable &&
!this._desktopManager.checkIfSpecialFilesAreSelected() &&
(selectedItemsNum >= 1)
) {
let addedExtractHere = false;
if (this._getExtractableAutoAr()) {
addedExtractHere = true;
openMenu.append(_('Extract Here'), 'app.extractautoar');
}
if (selectedItemsNum === 1 && this._getExtractable()) {
if (!addedExtractHere)
openMenu.append(_('Extract Here'), 'app.extracthere');
openMenu.append(_('Extract To...'), 'app.extractto');
}
}
if (fileItem.isDirectory &&
selectedItemsNum === 1 &&
!fileItem.isDrive &&
!fileItem.isTrash)
openMenu.append(_('Open With...'), 'app.doopenwith');
if (!this.activeFileItem.isStackMarker &&
!fileItem.isDirectory) {
openMenu.append(
selectedItemsNum > 1
? _('Open All With Other Application...')
: _('Open With...'),
'app.doopenwith'
);
if (this._DBusUtils.discreteGpuAvailable &&
fileItem.trustedDesktopFile) {
openMenu.append(
_('Launch using Integrated Graphics Card'),
'app.graphicslaunch'
);
}
}
const keepStacked =
this._Prefs.desktopSettings.get_boolean('keep-stacked');
if (keepStacked &&
!fileItem.stackUnique) {
if (!fileItem.isSpecial &&
!fileItem.isDirectory &&
!fileItem.isValidDesktopFile
) {
const contentType = fileItem.attributeContentType;
const typeInList =
this._Prefs.UnstackList.includes(contentType);
const menuitem =
Gio.MenuItem.new(
typeInList
? _('Stack This Type')
: _('Unstack This Type'),
null
);
const variant = GLib.Variant.new('s', contentType);
menuitem.set_action_and_target_value(
'app.stackunstack',
variant
);
openMenu.append_item(menuitem);
}
}
// fileExtra == NONE
if (fileItem.isAllSelectable && !fileItem.isStackMarker) {
const contentType = fileItem.attributeContentType;
if (fileItem.attributeCanExecute &&
!fileItem.isDirectory &&
!fileItem.isDesktopFile &&
!fileItem.isAppImageFile &&
fileItem.execLine &&
Gio.content_type_can_be_executable(contentType))
runAsProgram.append(_('Run as a Program'), 'app.runasaprogram');
if (scriptsSubmenu !== null)
openMenu.append_submenu(_('Scripts'), scriptsSubmenu);
const allowCutCopyTrash =
!this._desktopManager.checkIfSpecialFilesAreSelected();
if (allowCutCopyTrash) {
cutCopyPasteMenu.append(_('Cut'), 'app.docut');
cutCopyPasteMenu.append(_('Copy'), 'app.docopy');
}
if (!this._desktopManager.checkIfSpecialFilesAreSelected()) {
cutCopyPasteMenu.append(_('Move to...'), 'app.bulkMove');
cutCopyPasteMenu.append(_('Copy to...'), 'app.bulkCopy');
}
if (fileItem.canRename && (selectedItemsNum === 1))
trashMenu.append(_('Rename…'), 'app.dorename');
if (fileItem.isAllSelectable &&
!this._desktopManager.checkIfSpecialFilesAreSelected() &&
(selectedItemsNum >= 1)) {
trashMenu.append(_('Create Link...'), 'app.makeLinks');
if (this._desktopManager
.getCurrentSelection()
?.every(f => f.isDirectory)) {
trashMenu.append(
Gettext.ngettext(
'Compress {0} folder',
'Compress {0} folders', selectedItemsNum)
.replace('{0}', selectedItemsNum),
'app.compressfiles'
);
} else {
trashMenu.append(
Gettext.ngettext(
'Compress {0} file',
'Compress {0} files', selectedItemsNum)
.replace('{0}', selectedItemsNum),
'app.compressfiles'
);
}
trashMenu.append(_('Email to...'), 'app.sendto');
if (!this._desktopManager.checkIfDirectoryIsSelected()) {
const sel = this._desktopManager.getCurrentSelection();
const remoteoperation =
this._DBusUtils.RemoteSendFileOperations;
const gsconnectsubmenu =
remoteoperation.create_gsconnect_menu(sel);
if (gsconnectsubmenu) {
trashMenu.append_submenu(
_('Send to Mobile Device'),
gsconnectsubmenu
);
}
}
}
if (allowCutCopyTrash) {
trashMenu.append(_('Move to Trash'), 'app.movetotrash');
const showDeletePermanently =
this._Prefs.nautilusSettings
.get_boolean('show-delete-permanently');
if (showDeletePermanently) {
trashMenu.append(
_('Delete permanently'),
'app.deletepermanantly'
);
}
}
if ((fileItem.isValidDesktopFile || fileItem.isAppImageFile) &&
!this._desktopManager.writableByOthers &&
!fileItem.writableByOthers &&
(selectedItemsNum === 1)) {
if (fileItem.isDesktopFile) {
allowLaunchingMenu.append(
fileItem.trustedDesktopFile
? _("Don't Allow Launching")
: _('Allow Launching'),
'app.allowdisallowlaunching');
if (fileItem.hasActions) {
const actionItem =
Gio.MenuItem.new_section(
null,
fileItem.getMenu()
);
allowLaunchingMenu.append_item(actionItem);
}
} else if (fileItem.isAppImageFile) {
allowLaunchingMenu.append(
fileItem.trustedAppImageFile
? _("Don't Allow Launching")
: _('Allow Launching'),
'app.allowdisallowlaunching');
}
}
}
// fileExtra == TRASH
if (fileItem.isTrash)
emptyTrashMenu.append(_('Empty Trash'), 'app.emptytrash');
// fileExtra == EXTERNAL_DRIVE
if (fileItem.isDrive) {
if (fileItem.canEject)
driveMenu.append(_('Eject'), 'app.eject');
if (fileItem.canUnmount)
driveMenu.append(_('Unmount'), 'app.unmount');
}
if (!fileItem.isStackMarker) {
propertiesMenu.append(
selectedItemsNum > 1
? _('Common Properties')
: _('Properties'),
'app.properties'
);
const nautilusName = this._Prefs.NautilusName;
showInFilesMenu.append(
selectedItemsNum > 1
? _('Show All in {0}').replace('{0}', nautilusName)
: _('Show in {0}').replace('{0}', nautilusName),
'app.showinfiles'
);
}
if (fileItem.isDirectory &&
(fileItem.path !== null) &&
(selectedItemsNum === 1)) {
const terminalstring = this._Prefs.TerminalName;
const menuitem = Gio.MenuItem.new(
_('Open in {0}').replace('{0}', terminalstring),
null
);
menuitem.set_action_and_target_value(
'app.openinterminal',
null
);
openInTerminalMenu.append_item(menuitem);
}
this._menu.append_section(null, makeFolderMenu);
this._menu.append_section(null, openMenu);
this._menu.append_section(null, runAsProgram);
this._menu.append_section(null, cutCopyPasteMenu);
this._menu.append_section(null, trashMenu);
this._menu.append_section(null, allowLaunchingMenu);
this._menu.append_section(null, emptyTrashMenu);
if (fileItem.canEject || fileItem.canUnmount)
this._menu.append_section(null, driveMenu);
this._menu.append_section(null, showInFilesMenu);
this._menu.append_section(null, openInTerminalMenu);
this._menu.append_section(null, propertiesMenu);
this.popupmenu = Gtk.PopoverMenu.new_from_model(this._menu);
this.popupmenu.set_parent(fileItem._grid._container);
this.popupmenu.set_pointing_to(menulocation);
const menuGtkPosition =
fileItem._grid.getIntelligentPosition(menulocation);
if (menuGtkPosition !== null)
this.popupmenu.set_position(menuGtkPosition);
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;
});
});
}
menuclosed = () => {
return new Promise(resolve => {
this.popupmenuclosed = resolve;
});
};
showToolTip(fileItem) {
if (this._toolTipPopup)
return;
if (this.popupmenu && (fileItem.uri === this.activeFileItem.uri))
return;
this._toolTipPopup = Gtk.Popover.new();
this._toolTipPopup.set_pointing_to(fileItem.iconLocalWindowRectangle);
this._toolTipPopup.set_autohide(false);
this._toolTipLabel = Gtk.Label.new(fileItem._currentFileName);
this._toolTipPopup.set_child(this._toolTipLabel);
this._toolTipPopup.set_parent(fileItem._grid._window);
const popupLocation =
new Gdk.Rectangle({
x: fileItem.iconLocalWindowRectangle.x,
y: fileItem.iconLocalWindowRectangle.y,
width: 1,
height: 1,
});
const popupGtkPosition =
fileItem._grid.getIntelligentPosition(popupLocation);
if (popupGtkPosition !== null)
this._toolTipPopup.set_position(popupGtkPosition);
this._toolTipPopup.popup();
this._toolTipPopup.connect(
'closed',
() => {
this._toolTipPopup.unparent();
this._toolTipPopup = null;
}
);
}
hideToolTip() {
if (this._toolTipPopup)
this._toolTipPopup.popdown();
}
_getExtractableAutoAr() {
const fileList = this._desktopManager.getCurrentSelection();
const remotecompress = this._DBusUtils.GnomeArchiveManager;
if (remotecompress.isAvailable && (fileList.length === 1))
return false;
for (let item of fileList) {
if (!this._desktopManager.autoAr.fileIsCompressed(item.fileName))
return false;
}
return true;
}
_getExtractable() {
const item = this._desktopManager.getCurrentSelection()[0];
if (!item)
return false;
const contentType = item.attributeContentType;
const decompressibleTypes =
this._DBusUtils.RemoteFileOperations.decompressibleTypes;
return decompressibleTypes.includes(contentType);
}
set activeFileItem(fileItem) {
this._activeFileItem = fileItem;
}
get activeFileItem() {
return this._activeFileItem;
}
};
const FileItemActions = class {
constructor(desktopManager) {
this._desktopManager = desktopManager;
this._mainApp = this._desktopManager.mainApp;
this._codePath = this._desktopManager.codePath;
this._Prefs = this._desktopManager.Prefs;
this._DBusUtils = this._desktopManager.DBusUtils;
this._DesktopIconsUtil = this._desktopManager.DesktopIconsUtil;
this._Enums = this._desktopManager.Enums;
this._appChooser = this._desktopManager.appChooser;
this._dbusManager = this._desktopManager.dbusManager;
this._dragManager = this._desktopManager.dragManager;
this._createFileItemMenuActions();
}
_createFileItemMenuActions() {
const openMultipleFileAction =
Gio.SimpleAction.new('openMultipleFileAction', null);
openMultipleFileAction.connect(
'activate',
this._doMultiOpen.bind(this)
);
this._mainApp.add_action(openMultipleFileAction);
const openOneFileAction =
Gio.SimpleAction.new('openOneFileAction', null);
openOneFileAction.connect(
'activate',
() => {
if (this.activeFileItem) {
if (this.activeFileItem.isStackMarker) {
this._onToggleStackUnstackThisTypeClicked(
this.activeFileItem.attributeContentType);
} else {
this.activeFileItem.doOpen();
}
}
}
);
openOneFileAction.set_state_hint(GLib.Variant.new('s', _('Open Item')));
this._mainApp.add_action(openOneFileAction);
const stackunstack =
Gio.SimpleAction.new('stackunstack', GLib.VariantType.new('s'));
stackunstack.connect(
'activate',
(_action, paramenter) => {
this._onToggleStackUnstackThisTypeClicked(paramenter.unpack());
});
this._mainApp.add_action(stackunstack);
const doopenwith = Gio.SimpleAction.new('doopenwith', null);
doopenwith.connect('activate', () => {
this._doOpenWith().catch(e => logError(e));
});
this._mainApp.add_action(doopenwith);
const graphicslaunch = Gio.SimpleAction.new('graphicslaunch', null);
graphicslaunch.connect('activate', () => {
if (!this.activeFileItem)
return;
this.activeFileItem._doDiscreteGpu();
});
this._mainApp.add_action(graphicslaunch);
const runasaprogram = Gio.SimpleAction.new('runasaprogram', null);
runasaprogram.connect(
'activate',
this._runExecutableScript.bind(this)
);
this._mainApp.add_action(runasaprogram);
this._docut = Gio.SimpleAction.new('docut', null);
this._docut.connect(
'activate',
this._doCut.bind(this)
);
this._mainApp.add_action(this._docut);
this._docopy = Gio.SimpleAction.new('docopy', null);
this._docopy.connect(
'activate',
this._doCopy.bind(this)
);
this._mainApp.add_action(this._docopy);
const dorename = Gio.SimpleAction.new('dorename', null);
dorename.connect('activate', () => {
this._desktopManager
.doRename(this.activeFileItem, false)
.catch(e => logError(e));
});
this._mainApp.add_action(dorename);
this.moveToTrash = Gio.SimpleAction.new('movetotrash', null);
this.moveToTrash.connect(
'activate',
() => this.doTrash()
);
this._mainApp.add_action(this.moveToTrash);
this.deletePermanantly =
Gio.SimpleAction.new('deletepermanantly', null);
this.deletePermanantly.connect(
'activate',
() => this.doDeletePermanently()
);
this._mainApp.add_action(this.deletePermanantly);
const emptytrash = Gio.SimpleAction.new('emptytrash', null);
emptytrash.connect(
'activate',
() => this.doEmptyTrash()
);
this._mainApp.add_action(emptytrash);
const allowdisallowlaunching = Gio.SimpleAction.new(
'allowdisallowlaunching', null);
allowdisallowlaunching.connect('activate', () => {
if (!this.activeFileItem)
return;
this.activeFileItem
.onAllowDisallowLaunchingClicked()
.catch(e => console.error(e));
});
this._mainApp.add_action(allowdisallowlaunching);
const eject = Gio.SimpleAction.new('eject', null);
eject.connect('activate', () => {
this.activeFileItem.eject().catch(e => console.error(e));
});
this._mainApp.add_action(eject);
const unmount = Gio.SimpleAction.new('unmount', null);
unmount.connect('activate', () => {
this.activeFileItem.unmount().catch(e => console.error(e));
});
this._mainApp.add_action(unmount);
const extractautoar = Gio.SimpleAction.new('extractautoar', null);
extractautoar.connect(
'activate',
() => {
this._desktopManager.getCurrentSelection()
?.forEach(
f => this._desktopManager.autoAr.extractFile(f.fileName));
}
);
this._mainApp.add_action(extractautoar);
const extracthere = Gio.SimpleAction.new('extracthere', null);
extracthere.connect(
'activate',
this._extractFileFromSelection.bind(this, true)
);
this._mainApp.add_action(extracthere);
const extractto = Gio.SimpleAction.new('extractto', null);
extractto.connect(
'activate',
this._extractFileFromSelection.bind(this, false)
);
this._mainApp.add_action(extractto);
const sendto = Gio.SimpleAction.new('sendto', null);
sendto.connect(
'activate',
this._mailFilesFromSelection.bind(this, null)
);
this._mainApp.add_action(sendto);
const compressfiles = Gio.SimpleAction.new('compressfiles', null);
compressfiles.connect(
'activate',
this._doCompressFilesFromSelection.bind(this, null)
);
this._mainApp.add_action(compressfiles);
const newfolderfromselection =
Gio.SimpleAction.new('newfolderfromselection', null);
newfolderfromselection.connect(
'activate',
this._newFolderFromSelection.bind(this)
);
this._mainApp.add_action(newfolderfromselection);
const properties = Gio.SimpleAction.new('properties', null);
properties.connect(
'activate',
this._onPropertiesClicked.bind(this)
);
this._mainApp.add_action(properties);
const showinfiles = Gio.SimpleAction.new('showinfiles', null);
showinfiles.connect(
'activate',
this._onShowInFilesClicked.bind(this)
);
this._mainApp.add_action(showinfiles);
const openinterminal = Gio.SimpleAction.new('openinterminal', null);
openinterminal.connect(
'activate',
this._openInTerminal.bind(this)
);
this._mainApp.add_action(openinterminal);
const makeLinks = Gio.SimpleAction.new('makeLinks', null);
makeLinks.connect(
'activate',
this._makeLinks.bind(this)
);
this._mainApp.add_action(makeLinks);
const bulkCopy = Gio.SimpleAction.new('bulkCopy', null);
bulkCopy.connect(
'activate',
this._bulkCopy.bind(this)
);
this._mainApp.add_action(bulkCopy);
const bulkMove = Gio.SimpleAction.new('bulkMove', null);
bulkMove.connect(
'activate',
this._bulkMove.bind(this)
);
this._mainApp.add_action(bulkMove);
const onScriptClicked =
Gio.SimpleAction.new('onScriptClicked', GLib.VariantType.new('s'));
onScriptClicked.connect(
'activate',
(_action, parameter) => {
this._onScriptClicked(parameter.unpack());
}
);
this._mainApp.add_action(onScriptClicked);
const desktopAction =
Gio.SimpleAction.new('desktopAction', GLib.VariantType.new('as'));
desktopAction.connect(
'activate',
(_action, parameter) => {
const [path, actionName, action] = parameter.deepUnpack();
this._desktopFileAction(path, actionName, action);
}
);
this._mainApp.add_action(desktopAction);
}
_doMultiOpen() {
for (let fileItem of this._desktopManager.getCurrentSelection()) {
fileItem.unsetSelected();
fileItem.doOpen();
}
}
_onToggleStackUnstackThisTypeClicked(
type, typeInList = null, unstackList = null) {
if (!unstackList) {
unstackList = this._Prefs.UnstackList;
typeInList = unstackList.includes(type);
}
if (typeInList) {
let index = unstackList.indexOf(type);
unstackList.splice(index, 1);
} else {
unstackList.push(type);
}
this._Prefs.UnstackList = unstackList;
}
async _doOpenWith() {
const fileItems = this._desktopManager.getCurrentSelection();
if (!this.activeFileItem)
this.activeFileItem = fileItems[0];
if (fileItems) {
const context = Gdk.Display.get_default().get_app_launch_context();
context.set_timestamp(Gdk.CURRENT_TIME);
let chooser =
new this._appChooser.AppChooserDialog(
fileItems,
this.activeFileItem,
this._dbusManager,
this._DesktopIconsUtil
);
this._mainApp.activate_action('textEntryAccelsTurnOff', null);
chooser.show();
const appInfo =
await chooser.getApplicationSelected()
.catch(e => console.error(e));
if (appInfo) {
const fileList = [];
for (let item of fileItems)
fileList.push(item.file);
appInfo.launch(fileList, context);
}
this._mainApp.activate_action('textEntryAccelsTurnOn', null);
chooser.hide();
chooser.finalize();
chooser = null;
}
}
_runExecutableScript() {
if (!this.activeFileItem)
return;
this._DesktopIconsUtil.trySpawn(this._desktopDir.get_path(),
[this.activeFileItem.path], null);
}
async _extractFileFromSelection(extractHere) {
let extractFileItemURI;
let extractFolderName;
let position;
const header = _('Extraction Cancelled');
const text = _('Unable to extract File, no destination folder');
const remOp = this._DBusUtils.RemoteFileOperations;
for (let fileItem of this._desktopManager.getCurrentSelection()) {
extractFileItemURI = fileItem.file.get_uri();
extractFolderName = fileItem.fileName;
position = fileItem.getCoordinates().slice(0, 2);
fileItem.unsetSelected();
}
if (extractHere) {
extractFolderName =
this._DesktopIconsUtil
.getFileExtensionOffset(extractFolderName).basename;
const targetURI =
await this._desktopManager
.doNewFolder(position, extractFolderName, {rename: false});
if (targetURI)
remOp.ExtractRemote(extractFileItemURI, targetURI, true);
else
this._desktopManager.dbusManager.doNotify(header, text);
return;
}
const folder = await this._getSelectedFolderGio()
.catch(e => console.error(e));
if (folder)
remOp.ExtractRemote(extractFileItemURI, folder.get_uri(), true);
else
this._desktopManager.dbusManager.doNotify(header, text);
}
async _getSelectedFolderGio(dialogTitle = null, selectionText = null) {
let result;
try {
result =
await this._getSelectedFolderGioNewMethod(
dialogTitle,
selectionText
);
} catch (e) {
console.log('Reverting to old method of selecting');
result =
await this._getSelectedFolderGioOldMethod(
dialogTitle,
selectionText
);
}
return result;
}
_getSelectedFolderGioNewMethod(dialogTitle = null, selectionText = null) {
return new Promise(resolve => {
if (!dialogTitle)
dialogTitle = _('Select Destination');
const window = this._mainApp.get_active_window();
if (!selectionText)
selectionText = _('Select');
const dialog = new Gtk.FileDialog({
title: dialogTitle,
accept_label: selectionText,
modal: true,
initial_folder: this._desktopDir,
});
dialog.select_folder(window, null, (actor, gioasyncresponse) => {
let folder;
try {
folder = actor.select_folder_finish(gioasyncresponse);
} catch (e) {
resolve(false);
}
if (folder)
resolve(folder);
else
resolve(false);
});
});
}
_getSelectedFolderGioOldMethod(dialogTitle = null, selectionText = null) {
return new Promise(resolve => {
if (!dialogTitle)
dialogTitle = _('Select Destination');
if (!selectionText)
selectionText = _('Select');
let returnValue = null;
const window = this._mainApp.get_active_window();
const dialog = new Gtk.FileChooserDialog({title: dialogTitle});
dialog.set_action(Gtk.FileChooserAction.SELECT_FOLDER);
dialog.set_create_folders(true);
dialog.set_current_folder(this._desktopDir);
dialog.add_button(_('Cancel'), Gtk.ResponseType.CANCEL);
dialog.add_button(selectionText, Gtk.ResponseType.ACCEPT);
dialog.set_transient_for(window);
const modal = true;
dialog.set_modal(modal);
this._DesktopIconsUtil.windowHidePagerTaskbarModal(dialog, modal);
this._mainApp.activate_action('textEntryAccelsTurnOff', null);
dialog.show();
dialog.present_with_time(Gdk.CURRENT_TIME);
dialog.connect('close', () => {
dialog.response(Gtk.ResponseType.CANCEL);
});
dialog.connect(
'response',
(_actor, response) => {
if (response === Gtk.ResponseType.ACCEPT) {
const folder = dialog.get_file();
if (folder)
returnValue = folder;
else
returnValue = false;
}
this._mainApp
.activate_action('textEntryAccelsTurnOn', null);
dialog.destroy();
resolve(returnValue);
}
);
});
}
_mailFilesFromSelection() {
if (this._desktopManager.checkIfSpecialFilesAreSelected())
return;
const pathnameArray = this._desktopManager.getCurrentSelection()
.map(f => f.path);
if (this._desktopManager.checkIfDirectoryIsSelected()) {
this._mailzippedFilesFromSelection(pathnameArray)
.catch(e => console.error(e));
return;
}
this._xdgEmailFiles(pathnameArray);
this._desktopManager.unselectAll();
}
async _mailzippedFilesFromSelection(pathnameArray) {
this._mainApp.activate_action('textEntryAccelsTurnOff', null);
const chooser = new Gtk.AlertDialog();
chooser.set_message(_('Can not email a Directory'));
chooser.set_detail(
_('Selection includes a Directory, compress to a .zip file first?')
);
chooser.buttons = [_('Cancel'), _('OK')];
chooser.set_modal(true);
chooser.set_cancel_button(0);
chooser.set_default_button(1);
await chooser.choose(
this.activeFileItem._grid._window,
null,
(actor, choice) => {
const buttonpress = actor.choose_finish(choice);
if (buttonpress === 1) {
const archive = this._makezippedArchive(pathnameArray);
if (archive)
this._xdgEmailFiles([archive]);
}
this._desktopManager.unselectAll();
}
);
this._mainApp.activate_action('textEntryAccelsTurnOn', null);
}
_makezippedArchive(pathnameArray) {
const zipCommand = GLib.find_program_in_path(this._Enums.ZIP_CMD);
if (!zipCommand) {
console.log('zip command not installed, cannot send email');
const header = _('Mail Error');
const text =
_('Unable to find zip command, please install the program');
this._dbusManager.doNotify(header, text);
return null;
}
// Translators - basename for a zipped archive created for mailing
const archiveName = _('Archive.zip');
let archiveFile;
let checkDir;
// Create a random directory in /tmp
do {
const randomString = GLib.uuid_string_random().slice(0, 5);
const dir = `/tmp/gtk4-ding-${randomString}`;
archiveFile = `${dir}/${archiveName}`;
checkDir = Gio.File.new_for_commandline_arg(dir);
} while (!checkDir.make_directory(null));
const args = [zipCommand, this._Enums.ZIP_CMD_OPTIONS, archiveFile];
try {
const async = false;
const env = null;
const workdir = this._desktopDir.get_path();
const relativePathArray =
pathnameArray.map(f => GLib.path_get_basename(f));
const spawncommand = this._DesktopIconsUtil.trySpawn;
spawncommand(workdir, args.concat(relativePathArray), env, async);
} catch (e) {
console.log(`Error Zipping Files, ${e}`);
const header = _('Mail Error');
const text = _('There was an error in creating a zip archive');
this._dbusManager.doNotify(header, text);
}
if (Gio.File.new_for_commandline_arg(archiveFile).query_exists(null))
return archiveFile;
else
return null;
}
_xdgEmailFiles(pathnameArray) {
const xdgEmailCommand =
GLib.find_program_in_path(this._Enums.XDG_EMAIL_CMD);
if (!xdgEmailCommand) {
console.log('xdg-email command not installed, cannot send email');
const header = _('Mail Error');
const text =
_('Unable to find xdg-email, please install the program');
this._dbusManager.doNotify(header, text);
return;
}
const args = [xdgEmailCommand];
try {
const newPathNameArray = [];
pathnameArray.forEach(f => {
newPathNameArray.push(this._Enums.XDG_EMAIL_CMD_OPTIONS);
newPathNameArray.push(f);
});
const spawncommand = this._DesktopIconsUtil.trySpawn;
spawncommand(null, args.concat(newPathNameArray));
} catch (e) {
console.log(`Error emailing Files, ${e}`);
const header = _('Mail Error');
const text = _('There was an error in emailing Files');
this._dbusManager.doNotify(header, text);
}
}
_doCompressFilesFromSelection() {
const desktopFolder = this._desktopDir;
if (!desktopFolder)
return;
const toCompress = this._desktopManager.getCurrentSelection();
if (!toCompress)
return;
const uriListtoCompress = toCompress?.map(f => f.uri);
const remotecompressAvailable =
this._DBusUtils.GnomeArchiveManager.isAvailable;
if (remotecompressAvailable) {
this._DBusUtils.RemoteFileOperations.CompressRemote(
uriListtoCompress,
desktopFolder.get_uri(),
true
);
} else {
this._desktopManager.autoAr.compressFileItems(
toCompress,
desktopFolder.get_path()
);
}
this._desktopManager.unselectAll();
}
async _newFolderFromSelection() {
const event = {
'parentWindow': this.activeFileItem._grid._window,
'timestamp': Gdk.CURRENT_TIME,
};
await this._doNewFolderFromSelection(
this.activeFileItem.savedCoordinates,
this.activeFileItem,
event
).catch(e => console.error(e));
}
async _doNewFolderFromSelection(newposition = null, clickedItem, event) {
if (!clickedItem)
return;
const newFolderFileItems =
this._desktopManager.getCurrentSelectionAsUri();
if (!newFolderFileItems)
return;
const position = newposition
? newposition
: clickedItem.savedCoordinates;
const newFolder = await this._desktopManager.doNewFolder(position);
if (!newFolder)
return;
this._desktopManager.unselectAll();
clickedItem.removeFromGrid({callOnDestroy: false});
this._DBusUtils.RemoteFileOperations.pushEvent(event);
const remoteOp = this._DBusUtils.RemoteFileOperations;
remoteOp.MoveURIsRemote(newFolderFileItems, newFolder);
}
_onPropertiesClicked() {
const propList = this._desktopManager.getCurrentSelectionAsUri();
if (!propList)
return;
const timestamp = Gdk.CURRENT_TIME;
this._desktopManager
.DBusUtils
.RemoteFileOperations
.ShowItemPropertiesRemote(
propList,
timestamp
);
}
_onShowInFilesClicked() {
const showInFilesList = this._desktopManager.getCurrentSelectionAsUri();
if (!showInFilesList)
return;
const timestamp = Gdk.CURRENT_TIME;
this._desktopManager
.DBusUtils
.RemoteFileOperations
.ShowItemsRemote(
showInFilesList,
timestamp
);
}
_openInTerminal() {
if (!this.activeFileItem || !this.activeFileItem.isDirectory)
return;
this.launchTerminal(this.activeFileItem.path, null);
}
launchTerminal(fileItemPath = null, commandLine = null) {
let workingdir =
fileItemPath ? fileItemPath : this._desktopDir.get_path();
if (!GLib.file_test(workingdir, GLib.FileTest.EXISTS)) {
const header = _('Can Not open the Working Directory');
const text = _(`${workingdir} does not exist`);
this._dbusManager.doNotify(header, text);
return;
}
let success = false;
const xdgTerminalExec =
GLib.find_program_in_path(this._Enums.XDG_TERMINAL_EXEC);
if (xdgTerminalExec) {
try {
commandLine = commandLine ? commandLine : '';
const [args] =
GLib.shell_parse_argv(`${xdgTerminalExec} ${commandLine}`)
.slice(1);
this._DesktopIconsUtil.trySpawn(workingdir, args, null);
console.log('Executed xdg-terminal-exec');
success = true;
} catch (e) {
console.log(`Error opening xdg-terminal-exec ${e}`);
success = false;
}
}
if (success)
return;
if (this._Prefs.Terminal) {
this._Prefs.TerminalGioList.some(t => {
let exec =
t.get_string(this._Enums.DESKTOPFILE_TERMINAL_EXEC_KEY);
if (exec === 'ptyxis')
exec = `${exec} --new-window -d ${workingdir}`;
let execswitch =
t.get_string(this._Enums.DESKTOPFILE_TERMINAL_EXEC_SWITCH);
execswitch = execswitch ? execswitch : '-e';
commandLine = commandLine ? `${execswitch} ${commandLine}` : '';
const [args] =
GLib.shell_parse_argv(`${exec} ${commandLine}`)
.slice(1);
try {
this._DesktopIconsUtil.trySpawn(workingdir, args, null);
success = true;
} catch (e) {
console.log(`{Error opening ${t.get_string('Name')}, ${e}`);
success = false;
}
return success;
});
} else {
const header = _('Unable to Open in Gnome Console');
const text =
_('Please Install Gnome Console or other Terminal Program');
this._dbusManager.doNotify(header, text);
}
if (success)
return;
const header =
_('Unable to Open {0}').replace('{0}', this._Prefs.TerminalName);
const text =
_('Please Install {0}').replace('{0}', this._Prefs.TerminalName);
this._dbusManager.doNotify(header, text);
}
_makeLinks() {
const toLink = this._desktopManager.getCurrentSelectionAsUri();
if (!toLink || this._desktopManager.checkIfSpecialFilesAreSelected())
return;
const desktopFolderUri = this._desktopDir.get_uri();
const [X, Y] = this.activeFileItem.getCoordinates().slice(0, 2);
this._dragManager.makeLinks(toLink, desktopFolderUri, X, Y)
.catch(e => logError(e));
}
_doCopy() {
const copy = true;
this._manageCutCopy(copy);
}
_doCut() {
const cut = false;
this._manageCutCopy(cut);
}
async _bulkCopy() {
if (this._desktopManager.checkIfSpecialFilesAreSelected())
return;
const copyList = this._desktopManager.getCurrentSelectionAsUri();
if (!copyList)
return;
const folder = await this._getSelectedFolderGio()
.catch(e => console.error(e));
if (folder) {
this._DBusUtils
.RemoteFileOperations
.CopyURIsRemote(copyList, folder.get_uri());
return;
}
const header = _('Copy Cancelled');
const text = _('Unable to copy Files, no destination folder');
this._dbusManager.doNotify(header, text);
}
async _bulkMove() {
if (this._desktopManager.checkIfSpecialFilesAreSelected())
return;
const moveList = this._desktopManager.getCurrentSelectionAsUri();
if (!moveList)
return;
const folder = await this._getSelectedFolderGio()
.catch(e => console.error(e));
if (folder) {
this._dragManager.saveCurrentFileCoordinatesForUndo();
this._DBusUtils
.RemoteFileOperations
.MoveURIsRemote(moveList, folder.get_uri());
return;
}
const header = _('Move Cancelled');
const text = _('Unable to move Files, no destination folder');
this._dbusManager.doNotify(header, text);
}
_desktopFileAction(path, actionName, action) {
if (!this.activeFileItem)
return;
if (path === this.activeFileItem.path &&
this.activeFileItem.actionMap.has(actionName)
) {
let context = new Gio.AppLaunchContext();
this.activeFileItem.desktopAppInfo.launch_action(action, context);
}
}
doTrash(localDrag = false, event = null) {
const currentSelection = this._desktopManager.getCurrentSelection();
if (!currentSelection)
return;
const selectionItems = currentSelection.filter(i => !i.isSpecial);
if (!selectionItems || !selectionItems.length)
return;
if (!localDrag)
this._dragManager.saveCurrentFileCoordinatesForUndo(selectionItems);
const selectionURIs = [];
selectionItems.forEach(f => {
selectionURIs.push(f.file.get_uri());
});
if (event)
this._DBusUtils.RemoteFileOperations.pushEvent(event);
this._DBusUtils.RemoteFileOperations.TrashURIsRemote(selectionURIs);
}
doDeletePermanently() {
const currentSelection = this._desktopManager.getCurrentSelection();
if (!currentSelection)
return;
const toDelete =
currentSelection
.filter(i => !i.isSpecial)
.map(i => i.file.get_uri());
if (!toDelete || !toDelete.length) {
if (this._desktopManager.getCurrentSelection()
.some(i => i.isTrash)
)
this.doEmptyTrash();
return;
}
this._DBusUtils.RemoteFileOperations.DeleteURIsRemote(toDelete);
}
doEmptyTrash(askConfirmation = true) {
this._DBusUtils.RemoteFileOperations.EmptyTrashRemote(askConfirmation);
}
_onScriptClicked(menuItemPath) {
let pathList = 'NAUTILUS_SCRIPT_SELECTED_FILE_PATHS=';
let uriList = 'NAUTILUS_SCRIPT_SELECTED_URIS=';
let currentUri =
`NAUTILUS_SCRIPT_CURRENT_URI=${this._desktopDir.get_uri()}`;
let params = [menuItemPath];
for (let item of this._desktopManager.getCurrentSelection()) {
if (!item.isSpecial) {
pathList += `${item.file.get_path()}\n`;
uriList += `${item.file.get_uri()}\n`;
params.push(item.file.get_path());
}
}
let environ = this._DesktopIconsUtil.getFilteredEnviron();
environ.push(pathList);
environ.push(uriList);
environ.push(currentUri);
this._DesktopIconsUtil.trySpawn(null, params, environ);
}
// Clipboard management
// ************************************************************************ */
/*
* 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, in the past, we checked the current Gnome Shell
* version and, based on that, set the binary or the text clipboards.
*
* With the newer versions of gtk4-ding, we only set the binary version and add
* other composite providers for the plain text versions like the newer
* Nautilus/Files.
*/
_manageCutCopy(action) {
const uriList =
this._dragManager
.fillDragDataGet(this._Enums.DndTargetInfo.TEXT_URI_LIST);
if (!uriList?.length)
return;
const pathList =
this._dragManager
.fillDragDataGet(this._Enums.DndTargetInfo.TEXT_PLAIN);
const clipboard = Gdk.Display.get_default().get_clipboard();
const textCoder = new TextEncoder();
let content = action ? 'copy\n' : 'cut\n';
content += uriList?.replaceAll('\r', '').trim();
const encodedUriList = textCoder.encode(uriList);
const encodedPathList = textCoder.encode(pathList);
const gnomeContentProvider =
Gdk.ContentProvider.new_for_bytes(
'x-special/gnome-copied-files',
textCoder.encode(content)
);
const textUriListContentProvider =
Gdk.ContentProvider
.new_for_bytes(
this._Enums.DndTargetInfo.URI_LIST,
encodedUriList
);
const textListContentProvider =
Gdk.ContentProvider
.new_for_bytes(
this._Enums.DndTargetInfo.TEXT_PLAIN,
encodedPathList
);
const textUtf8ListContentProvider =
Gdk.ContentProvider
.new_for_bytes(
this._Enums.DndTargetInfo.TEXT_PLAIN_UTF8,
encodedPathList
);
const clipboardContentProvider =
Gdk.ContentProvider.new_union([
gnomeContentProvider,
textUriListContentProvider,
textListContentProvider,
textUtf8ListContentProvider,
]);
clipboard.set_content(clipboardContentProvider);
}
get _desktopDir() {
return this._desktopManager._desktopDir;
}
get activeFileItem() {
return this._desktopManager.activeFileItem;
}
};