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

988 lines
32 KiB
JavaScript

/* 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 <http://www.gnu.org/licenses/>.
*/
import {Gtk, Gdk, Gio, GLib} from '../dependencies/gi.js';
import {_} from '../dependencies/gettext.js';
export {DragManager};
const DragManager = class {
constructor(desktopManager) {
this._desktopManager = desktopManager;
this._mainApp = this._desktopManager.mainApp;
this._FileUtils = desktopManager.FileUtils;
this._DesktopIconsUtil = desktopManager.DesktopIconsUtil;
this._DBusUtils = desktopManager.DBusUtils;
this._Prefs = desktopManager.Prefs;
this._GnomeShellDragDrop = this._desktopManager.GnomeShellDragDrop;
this._Enums = this._desktopManager.Enums;
this._dbusManager = this._desktopManager.dbusManager;
this._pendingDropFiles = {};
this._pendingSelfCopyFiles = {};
this.pointerX = 0;
this.pointerY = 0;
this._dragList = null;
this.dragItem = null;
this.rubberBand = false;
this.localDragOffset = [0, 0];
}
// Drag and Drop local Methods
_saveCurrentFileCoordinatesForUndo(fileList = null) {
if (this._Prefs.keepArranged || this._Prefs.keepStacked)
return;
this._pendingDropFiles = {};
this._pendingSelfCopyFiles = {};
fileList = fileList ? fileList : this.currentSelection;
if (!fileList)
return;
fileList.forEach(f => {
const savedCoordinates = [
...f.savedCoordinates,
...f.normalCoordinates,
f.monitorIndex,
];
this._pendingSelfCopyFiles[f.fileName] = savedCoordinates;
});
}
_makeFileSystemLinks(fileList, destination) {
let gioDestination = Gio.File.new_for_uri(destination);
fileList.forEach(file => {
const fileGio = Gio.File.new_for_uri(file);
const baseNameParts =
this._DesktopIconsUtil.getFileExtensionOffset(
fileGio.get_basename()
);
let i = 0;
let newSymlinkName = fileGio.get_basename();
let checkSymlinkGio;
do {
checkSymlinkGio =
Gio.File.new_build_filenamev(
[gioDestination.get_path(), newSymlinkName]
);
try {
checkSymlinkGio.make_symbolic_link(
GLib.build_filenamev([fileGio.get_path()]),
null
);
break;
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS)) {
i += 1;
newSymlinkName =
`${baseNameParts.basename}` +
`${i}${baseNameParts.extension}`;
} else {
console.error(e, 'Error making file-system links');
const header = _('Making SymLink Failed');
const text = _('Could not create symbolic link');
this._dbusManager.doNotify(header, text);
break;
}
}
} while (true);
});
}
async _detectURLorText(dropData, dropCoordinates) {
/**
* Checks to see if a string is a URL
*
* @param {string} str A text URL
* @returns {boolean} if the string is a URL
*/
function isValidURL(str) {
var pattern = new RegExp('^(https|http|ftp|rtsp|mms)?:\\/\\/?' +
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
'((\\d{1,3}\\.){3}\\d{1,3}))' +
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
'(\\?[;&a-z\\d%_.~+=-]*)?' +
'(\\#[-a-z\\d_]*)?$', 'i');
return !!pattern.test(str);
}
const text = dropData.toString();
if (text === '')
return;
if (isValidURL(text)) {
await this._writeURLlinktoDesktop(text, dropCoordinates);
} else {
let filename = 'Dragged Text';
const now = Date().valueOf().split(' ').join('').replace(/:/g, '-');
filename = `${filename}-${now}`;
await this._DesktopIconsUtil.writeTextFileToPath(
text,
this._desktopDir,
filename,
dropCoordinates
);
}
}
async _writeURLlinktoDesktop(link, dropCoordinates) {
let filename = link.split('?')[0];
filename = filename.split('//')[1];
filename = filename.split('/')[0];
const now = Date().valueOf().split(' ').join('').replace(/:/g, '-');
filename = `${filename}-${now}`;
await this._writeHTMLTypeLink(filename, link, dropCoordinates);
}
async _writeHTMLTypeLink(filename, link, dropCoordinates) {
filename += '.html';
let body = [
'<html>',
' <head>',
` <meta http-equiv="refresh" content="0; url=${link}" />`,
' </head>',
' <body>',
' </body>',
'</html>',
];
body = body.join('\n');
await this._DesktopIconsUtil.writeTextFileToPath(
body,
this._desktopDir,
filename,
dropCoordinates
);
}
_startGnomeShellDrag() {
if (!this.localDrag &&
this.dragItem &&
!this.gnomeShellDrag
) {
this.gnomeShellDrag =
new this._GnomeShellDragDrop
.GnomeShellDrag(this._desktopManager);
}
}
_stopGnomeShellDrag() {
this.gnomeShellDrag?.destroy();
this.gnomeShellDrag = null;
}
_localDrag() {
let localDrag = false;
this._desktops.forEach(d => {
if (d.localDrag)
localDrag = true;
});
return localDrag;
}
_positiveOffsetGridAim(xGlobalDestination, yGlobalDestination) {
// Find the grid where the destination lies and aim towards the positive
// side, middle of grid to ensure drop in the grid
let xbias = 0;
let ybias = 0;
for (let desktop of this._desktops) {
if (desktop.coordinatesBelongToThisGrid(
xGlobalDestination,
yGlobalDestination)) {
xbias = desktop._elementWidth / 2;
ybias = desktop._elementHeight / 2;
break;
}
}
return [xGlobalDestination + xbias, yGlobalDestination + ybias];
}
async _getFsId(file) {
/**
* Returns filesystem id of file or null if file does not exist
*
* @param {file} Gio.File
* @returns {str} filesystem ID of file or null
*/
const info =
await file.query_info_async(
'id::filesystem',
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
null
).catch(
e => {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
return null;
throw e;
}
);
if (info == null)
return null;
return info.get_attribute_string('id::filesystem');
}
async _desktopFsId() {
const desktopFsId = await this._getFsId(this._desktopDir);
return desktopFsId;
}
async _fileIsOnDesktopFileSystem(file) {
/**
* Checks to see if file is on the same filesystem as the Desktop Folder
* Consider trash:// URI to be in the same folder as the Desktop
* This forces a move from Trash instead of copy
*
* @param {file} Gio.File
* @returns {boolean} if the file is on the same filesystem as Desktop
* @returns {null} if the file does not exist
*/
const fileSystemID = await this._getFsId(file);
if (fileSystemID == null)
return null;
if (fileSystemID.startsWith('trash'))
return true;
const desktopFileSystemID = await this._desktopFsId();
if (fileSystemID === desktopFileSystemID)
return true;
return false;
}
_drawRubberBand() {
for (let grid of this._desktops)
grid.updateOverlay();
}
// Global Methods
// *******************************************************
// Drag Preperation
fillDragDataGet(target) {
const fileList = this.currentSelection;
if (!fileList)
return null;
let uriList = '';
let pathList = '';
switch (target) {
case this._Enums.DndTargetInfo.GNOME_ICON_LIST:
for (let fileItem of fileList) {
uriList += fileItem.uri;
const coordinates = fileItem.getCoordinates();
if (coordinates !== null) {
uriList += `\r
${coordinates[0]}:
${coordinates[1]}:
${coordinates[2] - coordinates[0] + 1}:
${coordinates[3] - coordinates[1] + 1}`;
}
uriList += '\r\n';
}
return uriList;
case this._Enums.DndTargetInfo.DING_ICON_LIST:
case this._Enums.DndTargetInfo.TEXT_URI_LIST:
uriList = fileList.map(f => f.uri).join('\r\n');
uriList += '\r\n';
return uriList;
case this._Enums.DndTargetInfo.TEXT_PLAIN:
pathList = fileList.map(f => f.path).join('n');
pathList += '\n';
return pathList;
}
return null;
}
makeFileListFromSelection(dropData, acceptFormat) {
if (!dropData)
return null;
if (acceptFormat === this._Enums.DndTargetInfo.TEXT_PLAIN)
return null;
let fileList;
if (acceptFormat === this._Enums.DndTargetInfo.GNOME_ICON_LIST) {
fileList = GLib.Uri.list_extract_uris(dropData);
} else if (acceptFormat === this._Enums.DndTargetInfo.DING_ICON_LIST) {
fileList = dropData.get_files().map(f => f.get_uri());
} else {
fileList = dropData.split('\n').map(f => {
if (GLib.Uri.peek_scheme(f))
return f;
else
return GLib.filename_to_uri(f, null);
});
}
// filename_to_uri can return null
fileList = fileList.filter(f => {
if (!f)
return false;
return true;
});
if (fileList && fileList.length)
return fileList;
else
return null;
}
saveCurrentFileCoordinatesForUndo(fileList) {
this._saveCurrentFileCoordinatesForUndo(fileList);
}
async clearFileCoordinates(fileList,
dropCoordinates,
opts = {doCopy: false}) {
if (this._Prefs.keepArranged || this._Prefs.keepStacked)
return;
this.pendingDropFiles = {};
this.pendingSelfCopyFiles = {};
await Promise.all(fileList.map(async element => {
const file = Gio.File.new_for_uri(element);
if (!file.is_native()) {
this.setPendingDropCoordinates(file, dropCoordinates);
return;
}
const info = new Gio.FileInfo();
info.set_attribute_string('metadata::desktop-icon-position', '');
if (dropCoordinates !== null) {
if (!opts.doCopy) {
info.set_attribute_string(
'metadata::nautilus-drop-position',
`${dropCoordinates[0]},${dropCoordinates[1]}`
);
} else {
this.setPendingDropCoordinates(file, dropCoordinates);
return;
}
}
try {
await file.set_attributes_async(info,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_LOW,
null);
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
this.setPendingDropCoordinates(file, dropCoordinates);
}
}));
}
setPendingDropCoordinates(file, dropCoordinates) {
if (!dropCoordinates)
return;
const basename = file.get_basename();
this._pendingDropFiles = {};
this._pendingSelfCopyFiles = {};
let selfCopy = false;
this.currentWorkingList.forEach(fileItem => {
if (fileItem.fileName === basename) {
this._pendingDropFiles[`${basename}COPYEXPECTED`] =
dropCoordinates;
this._pendingSelfCopyFiles[basename] =
fileItem.savedCoordinates;
selfCopy = true;
}
});
if (!selfCopy)
this._pendingDropFiles[basename] = dropCoordinates;
}
// Drag Methods
startRubberband(X, Y) {
this.rubberBandInitX = X;
this.rubberBandInitY = Y;
this.rubberBand = true;
for (let item of this._displayList)
item.touchedByRubberband = false;
}
onDragBegin(item) {
this._saveCurrentFileCoordinatesForUndo();
this.dragItem = item;
this._stopGnomeShellDrag();
}
onDragMotion(X, Y) {
if (this.dragItem === null) {
for (let desktop of this._desktops)
desktop.refreshDrag([[0, 0]], X, Y);
return;
}
if (this._dragList === null) {
const itemList = this._desktopManager.getCurrentSelection();
if (!itemList)
return;
let [x1, y1] = this.dragItem.getCoordinates().slice(0, 3);
let oX = x1;
let oY = y1;
this._dragList = [];
for (let item of itemList) {
[x1, y1] = item.getCoordinates().slice(0, 3);
this._dragList.push([x1 - oX, y1 - oY]);
}
}
for (let desktop of this._desktops)
desktop.refreshDrag(this._dragList, X, Y);
this._stopGnomeShellDrag();
this.dragItem.setHighLighted();
}
onDragLeave() {
this._dragList = null;
for (let desktop of this._desktops)
desktop.refreshDrag(null, 0, 0);
// Synthesise, extrapolate drag motion on a shell actor
this._startGnomeShellDrag();
}
onDragEnd() {
this.dragItem = null;
this._stopGnomeShellDrag();
}
async onDragDataReceived(
xGlobalDestination,
yGlobalDestination,
xlocalDestination,
ylocalDestination,
dropData,
acceptFormat,
gdkDropAction,
localDrop,
event,
dragItem
) {
this.onDragLeave();
let dropCoordinates;
let xOrigin;
let yOrigin;
const forceCopy = gdkDropAction === Gdk.DragAction.COPY;
const fileList = this.makeFileListFromSelection(dropData, acceptFormat);
if (!this._Prefs.freePositionIcons) {
[xGlobalDestination, yGlobalDestination] =
this._positiveOffsetGridAim(
xGlobalDestination,
yGlobalDestination
);
}
let returnAction;
switch (acceptFormat) {
case this._Enums.DndTargetInfo.DING_ICON_LIST:
[xOrigin, yOrigin] = dragItem.getCoordinates().slice(0, 3);
if (gdkDropAction === Gdk.DragAction.MOVE) {
this.doMoveWithDragAndDrop(
xOrigin,
yOrigin,
xGlobalDestination,
yGlobalDestination
);
returnAction = Gdk.DragAction.MOVE;
break;
}
// eslint-disable-next-line no-fallthrough
case this._Enums.DndTargetInfo.GNOME_ICON_LIST:
case this._Enums.DndTargetInfo.URI_LIST:
if (!fileList)
return;
if (gdkDropAction === Gdk.DragAction.MOVE ||
gdkDropAction === Gdk.DragAction.COPY) {
try {
if (!localDrop) {
await this.clearFileCoordinates(
fileList,
[xGlobalDestination, yGlobalDestination],
{doCopy: forceCopy}
);
}
returnAction = await this.copyOrMoveUris(
fileList,
this._desktopDir.get_uri(),
event,
{forceCopy}
);
} catch (e) {
console.error(e);
}
} else {
if (gdkDropAction >= Gdk.DragAction.LINK)
returnAction = Gdk.DragAction.LINK;
else
returnAction = Gdk.DragAction.COPY;
this.askWhatToDoWithFiles(
fileList,
this._desktopDir.get_uri(),
xGlobalDestination,
yGlobalDestination,
xlocalDestination,
ylocalDestination,
event
)
.catch(e => logError(e));
}
break;
case this._Enums.DndTargetInfo.TEXT_PLAIN:
returnAction = Gdk.DragAction.COPY;
dropCoordinates = [xGlobalDestination, yGlobalDestination];
this._detectURLorText(dropData, dropCoordinates);
break;
default:
returnAction = Gdk.DragAction.COPY;
}
// eslint-disable-next-line consistent-return
return returnAction;
}
doMoveWithDragAndDrop(xOrigin, yOrigin, xDestination, yDestination) {
const keepArranged =
this._Prefs.keepArranged || this._Prefs.keepStacked;
if (this._Prefs.sortSpecialFolders && keepArranged)
return;
let deltaX;
let deltaY;
if (!this._Prefs.freePositionIcons) {
deltaX = xDestination - xOrigin;
deltaY = yDestination - yOrigin;
} else {
deltaX = xDestination - xOrigin - this.localDragOffset[0] * 2;
deltaY = yDestination - yOrigin - this.localDragOffset[1];
}
const fileItems = [];
this._displayList.filter(item => item.isSelected).forEach(item => {
if (!keepArranged || item.isSpecial) {
fileItems.push(item);
item.removeFromGrid({callOnDestroy: false});
let [x, y] = item.getCoordinates().slice(0, 3);
item.temporarySavedPosition = [x + deltaX, y + deltaY];
}
});
// force to store the new coordinates
this._desktopManager._addFilesToDesktop(fileItems,
this._Enums.StoredCoordinates.OVERWRITE);
if (keepArranged) {
this._desktopManager.redrawDesktop().catch(e => {
console.log(
'Exception while doing move with drag and drop and' +
`"Keep arranged…": ${e.message}\n${e.stack}`);
});
}
}
onTextDrop(dropData, [xGlobalDestination, yGlobalDestination]) {
this._detectURLorText(
dropData,
[xGlobalDestination, yGlobalDestination]
);
}
// Drag Motion
onMotion(X, Y) {
this.pointerX = X;
this.pointerY = Y;
if (this.rubberBand) {
this.x1 = Math.min(X, this.rubberBandInitX);
this.x2 = Math.max(X, this.rubberBandInitX);
this.y1 = Math.min(Y, this.rubberBandInitY);
this.y2 = Math.max(Y, this.rubberBandInitY);
this.selectionRectangle =
new Gdk.Rectangle({
'x': this.x1,
'y': this.y1,
'width': this.x2 - this.x1,
'height': this.y2 - this.y1,
});
this._drawRubberBand();
for (let item of this._displayList) {
const labelintersect =
item.labelRectangle.intersect(this.selectionRectangle)[0];
const iconintersect =
item.iconRectangle.intersect(this.selectionRectangle)[0];
if (labelintersect || iconintersect) {
item.setSelected();
item.touchedByRubberband = true;
} else if (item.touchedByRubberband) {
item.unsetSelected();
}
}
}
}
onReleaseButton() {
if (this.rubberBand) {
this.rubberBand = false;
this.selectionRectangle = null;
}
for (let grid of this._desktops)
grid.updateOverlay();
return false;
}
// Selection HighLighting
unHighLightDropTarget() {
this._displayList.forEach(item => item.unHighLightDropTarget());
}
selected(fileItem, action) {
this._clearKeyboardSelection();
switch (action) {
case this._Enums.Selection.ALONE:
if (!fileItem.isSelected) {
for (let item of this._displayList) {
if (item === fileItem)
item.setSelected();
else
item.unsetSelected();
}
}
break;
case this._Enums.Selection.WITH_SHIFT:
fileItem.toggleSelected();
break;
case this._Enums.Selection.RIGHT_BUTTON:
if (!fileItem.isSelected) {
for (let item of this._displayList) {
if (item === fileItem)
item.setSelected();
else
item.unsetSelected();
}
}
break;
case this._Enums.Selection.ENTER:
if (this.rubberBand)
fileItem.setSelected();
break;
case this._Enums.Selection.RELEASE:
for (let item of this._displayList) {
if (item === fileItem) {
if (item.isSelected)
item.setSelected();
else
item.unsetSelected();
}
}
break;
}
}
_clearKeyboardSelection() {
this._displayList.forEach(item => item.keyboardUnSelected());
this._desktopManager.desktopActions.lastAnchorSelected = null;
}
// File Copy Move Link Methods
async copyOrMoveUris(uriList, destinationUri, event, params = {}) {
if (params.forceCopy) {
this._DBusUtils.RemoteFileOperations.pushEvent(event);
this._DBusUtils.RemoteFileOperations.CopyURIsRemote(
uriList,
destinationUri
);
return Gdk.DragAction.COPY;
}
const moveFiles = [];
const copyFiles = [];
await Promise.all(uriList.map(async uri => {
const f = Gio.File.new_for_uri(uri);
const localFile = await this._fileIsOnDesktopFileSystem(f);
// localFile is null if it does not exist, false if on different
// fileystem, true if on the same filesystem as the Desktop Folder
if (localFile == null) {
console.error(`Cannot Copy/Move, ${uri} does not exist`);
const header = _('Copy/Move Failed');
const text = _('{0} Does not exist').replace('{0}', uri);
this._dbusManager.doNotify(header, text);
return;
}
if (localFile)
moveFiles.push(uri);
else
copyFiles.push(uri);
}));
if (moveFiles.length) {
this._DBusUtils.RemoteFileOperations.pushEvent(event);
this._DBusUtils.RemoteFileOperations.MoveURIsRemote(
moveFiles,
destinationUri
);
}
if (copyFiles.length) {
this._DBusUtils.RemoteFileOperations.pushEvent(event);
this._DBusUtils.RemoteFileOperations.CopyURIsRemote(
copyFiles,
destinationUri
);
}
return moveFiles.length ? Gdk.DragAction.MOVE : Gdk.DragAction.COPY;
}
async askWhatToDoWithFiles(
fileList,
destinationuri,
X,
Y,
x,
y,
event,
opts = {desktopactions: true}
) {
const window = this._mainApp.get_active_window();
this._mainApp.activate_action('textEntryAccelsTurnOff', null);
const chooser = new Gtk.AlertDialog();
chooser.set_message(_('Choose Action for Files'));
chooser.buttons = [_('Move'), _('Copy'), _('Link'), _('Cancel')];
chooser.set_modal(false);
chooser.set_cancel_button(3);
chooser.set_default_button(3);
const cancellable = Gio.Cancellable.new();
if (this.dialogCancellable)
this.dialogCancellable.cancel();
this.dialogCancellable = cancellable;
const showdialog = new Promise(resolve => {
chooser.choose(window, cancellable, async (actor, choice) => {
let retval = Gtk.ResponseType.CANCEL;
try {
const buttonpress = actor.choose_finish(choice);
switch (buttonpress) {
case 0:
retval = Gdk.DragAction.MOVE;
try {
if (opts.desktopactions) {
await this.clearFileCoordinates(
fileList,
[X, Y]
);
}
let forceCopy = false;
await this.copyOrMoveUris(
fileList,
destinationuri,
event,
{forceCopy}
);
} catch {
console.error('Error moving files');
}
break;
case 1:
retval = Gdk.DragAction.COPY;
try {
if (opts.desktopactions) {
await this.clearFileCoordinates(
fileList,
[X, Y],
{dopCopy: true}
);
}
let forceCopy = true;
await this.copyOrMoveUris(fileList,
destinationuri, event, {forceCopy});
} catch {
console.error('Error copying files');
}
break;
case 2:
retval = Gdk.DragAction.LINK;
try {
if (opts.desktopactions) {
await this.makeLinks(
fileList,
destinationuri,
X,
Y
);
} else {
this._makeFileSystemLinks(
fileList,
destinationuri
);
}
} catch {
console.error('Error making links');
}
break;
default:
retval = Gtk.ResponseType.CANCEL;
}
resolve(retval);
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
console.error(
e,
'Error asking choosing what to do with Files ' +
`${e.message}`
);
}
resolve(retval);
}
});
});
const retval = await showdialog.catch(e => logError(e));
this.dialogCancellable = null;
this._mainApp.activate_action('textEntryAccelsTurnOn', null);
return retval;
}
async makeLinks(fileList, destination, X, Y) {
const gioDestination = Gio.File.new_for_uri(destination);
await Promise.all(fileList.map(async file => {
const fileGio = Gio.File.new_for_uri(file);
const newSymlinkName =
this._desktopManager.desktopMonitor
.getDesktopUniqueFileName(
fileGio.get_basename()
);
const symlinkGio =
Gio.File.new_build_filenamev(
[gioDestination.get_path(), newSymlinkName]
);
try {
const linkMade =
symlinkGio.make_symbolic_link(
GLib.build_filenamev([fileGio.get_path()]),
null
);
if (linkMade) {
const info = new Gio.FileInfo();
info.set_attribute_string(
'metadata::nautilus-drop-position',
`${X},${Y}`
);
info.set_attribute_string(
'metadata::desktop-icon-position',
''
);
try {
await symlinkGio.set_attributes_async(
info,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_LOW,
null
);
} catch (e) {
console.error(e, 'Error setting link FileInfo');
}
}
} catch {
console.error('Error making desktop links');
const header = _('Making SymLink Failed');
const text = _('Could not create symbolic link');
this._dbusManager.doNotify(header, text);
}
}));
}
// Getters and Setters
get pendingDropFiles() {
return this._pendingDropFiles;
}
set pendingDropFiles(object) {
this._pendingDropFiles = object;
}
get pendingSelfCopyFiles() {
return this._pendingSelfCopyFiles;
}
set pendingSelfCopyFiles(object) {
this._pendingSelfCopyFiles = object;
}
get currentSelection() {
return this._desktopManager.getCurrentSelection();
}
get currentWorkingList() {
return this._desktopManager.currentWorkingList;
}
get _displayList() {
return this._desktopManager._displayList;
}
get _desktops() {
return this._desktopManager._desktops;
}
get _desktopDir() {
return this._desktopManager._desktopDir;
}
get localDrag() {
return this._localDrag();
}
};