__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

import {
    Gio,
    GLib,
    GObject,
    Shell,
    St,
} from './dependencies/gi.js';

import {ShellMountOperation} from './dependencies/shell/ui.js';

import {
    Docking,
    Utils,
} from './imports.js';

import {Extension} from './dependencies/shell/extensions/extension.js';

// Use __ () and N__() for the extension gettext domain, and reuse
// the shell domain with the default _() and N_()
const {gettext: __} = Extension;

const {signals: Signals} = imports;

const FALLBACK_REMOVABLE_MEDIA_ICON = 'drive-removable-media';
const FALLBACK_TRASH_ICON = 'user-trash';
const FILE_MANAGER_DESKTOP_APP_ID = 'org.gnome.Nautilus.desktop';
const ATTRIBUTE_METADATA_CUSTOM_ICON = 'metadata::custom-icon';
const TRASH_URI = 'trash://';
const UPDATE_TRASH_DELAY = 1000;
const LAUNCH_HANDLER_MAX_WAIT = 200;

const NautilusFileOperations2Interface = '<node>\
    <interface name="org.gnome.Nautilus.FileOperations2">\
        <method name="EmptyTrash">\
            <arg type="b" name="ask_confirmation" direction="in"/>\
            <arg type="a{sv}" name="platform_data" direction="in"/>\
        </method>\
    </interface>\
</node>';

const NautilusFileOperations2ProxyInterface =
    Gio.DBusProxy.makeProxyWrapper(NautilusFileOperations2Interface);

const Labels = Object.freeze({
    LOCATION_WINDOWS: Symbol('location-windows'),
    WINDOWS_CHANGED: Symbol('windows-changed'),
});

const GJS_SUPPORTS_FILE_IFACE_PROMISES = imports.system.version >= 17101;

if (GJS_SUPPORTS_FILE_IFACE_PROMISES) {
    Gio._promisify(Gio.File.prototype, 'query_info_async');
    Gio._promisify(Gio.File.prototype, 'query_default_handler_async');
}


/**
 *
 */
function makeNautilusFileOperationsProxy() {
    const proxy = new NautilusFileOperations2ProxyInterface(
        Gio.DBus.session,
        'org.gnome.Nautilus',
        '/org/gnome/Nautilus/FileOperations2', (_p, error) => {
            if (error)
                logError(error, 'Error connecting to Nautilus');
        }
    );

    proxy.platformData = params => {
        const defaultParams = {
            parentHandle: '',
            timestamp: global.get_current_time(),
            windowPosition: 'center',
        };
        const {parentHandle, timestamp, windowPosition} = {
            ...defaultParams,
            ...params,
        };

        return {
            'parent-handle': new GLib.Variant('s', parentHandle),
            'timestamp': new GLib.Variant('u', timestamp),
            'window-position': new GLib.Variant('s', windowPosition),
        };
    };

    return proxy;
}

export const LocationAppInfo = GObject.registerClass({
    Implements: [Gio.AppInfo],
    Properties: {
        'location': GObject.ParamSpec.object(
            'location', 'location', 'location',
            GObject.ParamFlags.READWRITE,
            Gio.File.$gtype),
        'name': GObject.ParamSpec.string(
            'name', 'name', 'name',
            GObject.ParamFlags.READWRITE,
            null),
        'icon': GObject.ParamSpec.object(
            'icon', 'icon', 'icon',
            GObject.ParamFlags.READWRITE,
            Gio.Icon.$gtype),
        'cancellable': GObject.ParamSpec.object(
            'cancellable', 'cancellable', 'cancellable',
            GObject.ParamFlags.READWRITE,
            Gio.Cancellable.$gtype),
    },
}, class LocationAppInfo extends Gio.DesktopAppInfo {
    static get GJS_BINARY_PATH() {
        if (!this._gjsBinaryPath)
            this._gjsBinaryPath = GLib.find_program_in_path('gjs');

        return this._gjsBinaryPath;
    }

    list_actions() {
        return [];
    }

    get_action_name() {
        return null;
    }

    get_boolean() {
        return false;
    }

    vfunc_dup() {
        return new LocationAppInfo({
            location: this.location,
            name: this.name,
            icon: this.icon,
            cancellable: this.cancellable,
        });
    }

    vfunc_equal(other) {
        if (this.location)
            return this.location.equal(other?.location);

        return this.name === other.name &&
            (this.icon ? this.icon.equal(other?.icon) : !other?.icon);
    }

    vfunc_get_id() {
        return 'location:%s'.format(this.location?.get_uri());
    }

    vfunc_get_name() {
        return this.name;
    }

    vfunc_get_description() {
        return null;
    }

    vfunc_get_executable() {
        return null;
    }

    vfunc_get_icon() {
        return this.icon;
    }

    vfunc_launch(files, context) {
        if (files?.length) {
            throw new GLib.Error(Gio.IOErrorEnum,
                Gio.IOErrorEnum.NOT_SUPPORTED, 'Launching with files not supported');
        }

        return this.getHandlerApp().launch_uris([this.location.get_uri()], context);
    }

    vfunc_supports_uris() {
        return false;
    }

    vfunc_supports_files() {
        return false;
    }

    vfunc_launch_uris(uris, context) {
        return this.launch(uris, context);
    }

    vfunc_should_show() {
        return true;
    }

    vfunc_set_as_default_for_type() {
        throw new GLib.Error(Gio.IOErrorEnum,
            Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported');
    }

    vfunc_set_as_default_for_extension() {
        throw new GLib.Error(Gio.IOErrorEnum,
            Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported');
    }

    vfunc_add_supports_type() {
        throw new GLib.Error(Gio.IOErrorEnum,
            Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported');
    }

    vfunc_can_remove_supports_type() {
        return false;
    }

    vfunc_remove_supports_type() {
        return false;
    }

    vfunc_can_delete() {
        return false;
    }

    vfunc_do_delete() {
        return false;
    }

    vfunc_get_commandline() {
        try {
            return this.getHandlerApp().get_commandline();
        } catch {
            return this._getFallbackCommandLine();
        }
    }

    vfunc_get_display_name() {
        return this.name;
    }

    vfunc_set_as_last_used_for_type() {
        throw new GLib.Error(Gio.IOErrorEnum,
            Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported');
    }

    vfunc_get_supported_types() {
        return [];
    }

    _getFallbackCommandLine() {
        return `gio open ${this.location?.get_uri()}`;
    }

    async _queryLocationIcons(params) {
        const icons = {standard: null, custom: null};
        if (!this.location)
            return icons;

        const cancellable = params.cancellable ?? this.cancellable;
        const iconsQuery = [];
        if (params?.standard)
            iconsQuery.push(Gio.FILE_ATTRIBUTE_STANDARD_ICON);

        if (params?.custom)
            iconsQuery.push(ATTRIBUTE_METADATA_CUSTOM_ICON);

        if (!iconsQuery.length)
            throw new Error('Invalid Query Location Icons parameters');

        let info;
        try {
            if (!GJS_SUPPORTS_FILE_IFACE_PROMISES) {
                Gio._promisify(this.location.constructor.prototype,
                    'query_info_async', 'query_info_finish');
            }
            info = await this.location.query_info_async(
                iconsQuery.join(','),
                Gio.FileQueryInfoFlags.NONE,
                GLib.PRIORITY_LOW, cancellable);
            if (info.has_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON))
                icons.standard = info.get_icon();
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND) ||
                e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED))
                return icons;
            throw e;
        }

        const customIcon = info.get_attribute_string(ATTRIBUTE_METADATA_CUSTOM_ICON);
        if (customIcon) {
            const customIconFile = GLib.uri_parse_scheme(customIcon)
                ? Gio.File.new_for_uri(customIcon) : Gio.File.new_for_path(customIcon);
            const iconFileInfo = await customIconFile.query_info_async(
                Gio.FILE_ATTRIBUTE_STANDARD_TYPE,
                Gio.FileQueryInfoFlags.NONE,
                GLib.PRIORITY_LOW, cancellable);

            if (iconFileInfo.get_file_type() === Gio.FileType.REGULAR)
                icons.custom = Gio.FileIcon.new(customIconFile);
        }

        return icons;
    }

    async _updateLocationIcon(params = {standard: true, custom: true}) {
        const cancellable = new Utils.CancellableChild(this.cancellable);

        try {
            this._updateIconCancellable?.cancel();
            this._updateIconCancellable = cancellable;

            const icons = await this._queryLocationIcons({cancellable, ...params});
            const icon = icons.custom ?? icons.standard;

            if (icon && !icon.equal(this.icon))
                this.icon = icon;
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e, 'Impossible to update icon for %s'.format(this.get_id()));
        } finally {
            cancellable.cancel();
            if (this._updateIconCancellable === cancellable)
                delete this._updateIconCancellable;
        }
    }

    async _getHandlerAppAsync(cancellable) {
        if (!this.location)
            return null;

        try {
            if (!GJS_SUPPORTS_FILE_IFACE_PROMISES) {
                Gio._promisify(this.location.constructor.prototype,
                    'query_default_handler_async',
                    'query_default_handler_finish');
            }

            return await this.location.query_default_handler_async(
                GLib.PRIORITY_DEFAULT, cancellable);
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED))
                return getFileManagerApp()?.appInfo;

            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                logError(e, 'Impossible to find an URI handler for %s'.format(
                    this.get_id()));
            }

            throw e;
        }
    }

    _getHandlerAppFromWorker(cancellable) {
        const locationsWorker = GLib.build_filenamev([
            Docking.DockManager.extension.path,
            'locationsWorker.js',
        ]);
        const locationsWorkerArgs = [LocationAppInfo.GJS_BINARY_PATH, '-m',
            locationsWorker, 'handler', this.location.get_uri(),
            '--timeout', `${LAUNCH_HANDLER_MAX_WAIT}`];
        const subProcess = Gio.Subprocess.new(locationsWorkerArgs,
            Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE);

        try {
            const [, stdOut, stdErr] = subProcess.communicate(null, cancellable);
            subProcess.wait(cancellable);
            const errorCode = subProcess.get_exit_status();
            const textDecoder = new TextDecoder();

            if (errorCode) {
                const errorLines = textDecoder.decode(stdErr.toArray()).split('\n');
                const error = new GLib.Error(Gio.IOErrorEnum,
                    errorCode === GLib.MAXUINT8 ? 0 : errorCode, errorLines[0]);
                error.stack = `${errorLines.slice(3).join('\n')}${error.stack}`;
                throw error;
            }

            const desktopId = textDecoder.decode(stdOut.toArray()).trim();
            const handlerApp = Shell.AppSystem.get_default().lookup_app(desktopId)?.appInfo;
            return handlerApp;
        } finally {
            subProcess.force_exit();
        }
    }

    getHandlerApp() {
        if (this._handlerApp)
            return this._handlerApp;

        if (!this.location)
            return null;

        const cancellable = new Utils.CancellableChild(this.cancellable);

        try {
            if (LocationAppInfo.GJS_BINARY_PATH)
                this._handlerApp = this._getHandlerAppFromWorker(cancellable);
            else
                this._handlerApp = this.location.query_default_handler(cancellable);

            if (!this._handlerApp) {
                throw new GLib.Error(Gio.IOErrorEnum,
                    Gio.IOErrorEnum.NOT_FOUND, `Handler for ${this.location} not found`);
            }
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED))
                return getFileManagerApp()?.appInfo;

            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                logError(e, 'Impossible to find an URI handler for %s'.format(
                    this.get_id()));
            }

            throw e;
        }

        return this._handlerApp;
    }

    destroy() {
        this.location = null;
        this.icon = null;
        this.name = null;
        this._handlerApp = null;
        this.cancellable?.cancel();
    }
});

const MountableVolumeAppInfo = GObject.registerClass({
    Implements: [Gio.AppInfo],
    Properties: {
        'volume': GObject.ParamSpec.object(
            'volume', 'volume', 'volume',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            Gio.Volume.$gtype),
        'mount': GObject.ParamSpec.object(
            'mount', 'mount', 'mount',
            GObject.ParamFlags.READWRITE,
            Gio.Mount.$gtype),
        'busy': GObject.ParamSpec.boolean(
            'busy', 'busy', 'busy',
            GObject.ParamFlags.READWRITE,
            false),
    },
},
class MountableVolumeAppInfo extends LocationAppInfo {
    _init(volume, cancellable = null) {
        super._init({
            volume,
            cancellable,
        });

        this._signalsHandler = new Utils.GlobalSignalsHandler();

        const updateAndMonitor = () => {
            this._update();
            this._monitorChanges();
        };
        updateAndMonitor();
        this._mountChanged = this.connect('notify::mount', updateAndMonitor);

        if (!this.mount && this.volume.get_identifier('class') === 'network') {
            // For some devices the mount point isn't advertised promptly
            // even if it's already existing, and there's no signaling about
            this._lazyUpdater = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, () => {
                this._update();
                delete this._lazyUpdater;
                return GLib.SOURCE_REMOVE;
            });
        }
    }

    get busy() {
        return !!this._currentAction;
    }

    get currentAction() {
        return this._currentAction;
    }

    destroy() {
        if (this._lazyUpdater) {
            GLib.source_remove(this._lazyUpdater);
            delete this._lazyUpdater;
        }
        this.disconnect(this._mountChanged);
        this.mount = null;
        this._signalsHandler.destroy();

        super.destroy();
    }

    vfunc_dup() {
        return new MountableVolumeAppInfo({
            volume: this.volume,
            cancellable: this.cancellable,
        });
    }

    vfunc_get_id() {
        const uuid = this.mount?.get_uuid() ?? this.volume.get_uuid();
        return uuid ? 'mountable-volume:%s'.format(uuid) : super.vfunc_get_id();
    }

    vfunc_equal(other) {
        if (this.volume === other?.volume && this.mount === other?.mount)
            return true;

        return this.get_id() === other?.get_id();
    }

    list_actions() {
        const actions = [];
        const {mount} = this;

        if (mount) {
            if (this.mount.can_unmount())
                actions.push('unmount');
            if (this.mount.can_eject())
                actions.push('eject');

            return actions;
        }

        if (this.volume.can_mount())
            actions.push('mount');
        if (this.volume.can_eject())
            actions.push('eject');

        return actions;
    }

    get_action_name(action) {
        switch (action) {
        case 'mount':
            return __('Mount');
        case 'unmount':
            return __('Unmount');
        case 'eject':
            return __('Eject');
        default:
            return null;
        }
    }

    vfunc_launch(files, context) {
        if (this.mount || files?.length)
            return super.vfunc_launch(files, context);

        this.mountAndLaunch(files, context);
        return true;
    }

    _update() {
        this.mount = this.volume.get_mount();

        const removable = this.mount ?? this.volume;
        this.name = removable.get_name();
        this.icon = removable.get_icon();

        this.location = this.mount?.get_default_location() ??
            this.volume.get_activation_root();

        this._updateLocationIcon({custom: true});
    }

    _monitorChanges() {
        this._signalsHandler.destroy();

        const removable = this.mount ?? this.volume;
        this._signalsHandler.add(removable, 'changed', () => this._update());

        if (this.mount) {
            this._signalsHandler.add(this.mount, 'pre-unmount', () => this._update());
            this._signalsHandler.add(this.mount, 'unmounted', () => this._update());
        }
    }

    async mountAndLaunch(files, context) {
        if (this.mount)
            return super.vfunc_launch(files, context);

        try {
            await this.launchAction('mount');
            if (!this.mount) {
                throw new Error('No mounted location to open for %s'.format(
                    this.get_id()));
            }

            return super.vfunc_launch(files, context);
        } catch (e) {
            logError(e, 'Mount and launch %s'.format(this.get_id()));
            return false;
        }
    }

    _notifyActionError(action, message) {
        if (action === 'mount') {
            global.notify_error(__('Failed to mount “%s”'.format(
                this.get_name())), message);
        } else if (action === 'unmount') {
            global.notify_error(__('Failed to umount “%s”'.format(
                this.get_name())), message);
        } else if (action === 'eject') {
            global.notify_error(__('Failed to eject “%s”'.format(
                this.get_name())), message);
        }
    }

    async launchAction(action) {
        if (!this.list_actions().includes(action))
            throw new Error('Action %s is not supported by %s', action, this);

        if (this._currentAction) {
            if (this._currentAction === 'mount') {
                this._notifyActionError(action,
                    __('Mount operation already in progress'));
            } else if (this._currentAction === 'unmount') {
                this._notifyActionError(action,
                    __('Umount operation already in progress'));
            } else if (this._currentAction === 'eject') {
                this._notifyActionError(action,
                    __('Eject operation already in progress'));
            }

            throw new Error('Another action %s is being performed in %s'.format(
                this._currentAction, this));
        }

        this._currentAction = action;
        this.notify('busy');
        const removable = this.mount ?? this.volume;
        const operation = new ShellMountOperation.ShellMountOperation(removable);
        try {
            if (action === 'mount') {
                await this.volume.mount(Gio.MountMountFlags.NONE, operation.mountOp,
                    this.cancellable);
            } else if (action === 'unmount') {
                await this.mount.unmount_with_operation(Gio.MountUnmountFlags.FORCE,
                    operation.mountOp, this.cancellable);
            } else if (action === 'eject') {
                await removable.eject_with_operation(Gio.MountUnmountFlags.FORCE,
                    operation.mountOp, this.cancellable);
            } else {
                logError(new Error(), 'No action %s on removable %s'.format(action,
                    removable.get_name()));
                return false;
            }

            return true;
        } catch (e) {
            if (action === 'mount' &&
                e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.ALREADY_MOUNTED))
                return true;
            else if (action === 'umount' &&
                     e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED))
                return true;

            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED))
                this._notifyActionError(action, e.message);

            if (action === 'mount' && this._isEncryptedMountError(e)) {
                delete this._currentAction;
                operation.close();
                return this.launchAction(action);
            }

            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                logError(e, 'Impossible to %s removable %s'.format(action,
                    removable.get_name()));
            }

            return false;
        } finally {
            delete this._currentAction;
            this.notify('busy');
            this._update();
            operation.close();
        }
    }

    _isEncryptedMountError(error) {
        // FIXME: we will always get G_IO_ERROR_FAILED from the gvfs udisks
        // backend, see https://bugs.freedesktop.org/show_bug.cgi?id=51271

        if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED))
            return false;

        // cryptsetup
        if (error.message.includes('No key available with this passphrase'))
            return true;

        // udisks (no password)
        if (error.message.includes('No key available to unlock device'))
            return true;

        // libblockdev wrong password opening LUKS device
        if (error.message.includes('Failed to activate device: Incorrect passphrase'))
            return true;

        // cryptsetup returns EINVAL in many cases, including wrong TCRYPT password/parameters
        if (error.message.includes('Failed to load device\'s parameters: Invalid argument') ||
            error.message.includes(`Failed to load device's parameters: ${GLib.strerror(22 /* EINVAL */)}`))
            return true;

        // cryptsetup returns EPERM when the TCRYPT header can't be decrypted
        // with the provided password/parameters.
        if (error.message.includes('Failed to load device\'s parameters: Operation not permitted') ||
            error.message.includes(`Failed to load device's parameters: ${GLib.strerror(1 /* EPERM */)}`))
            return true;

        return false;
    }
});

const TrashAppInfo = GObject.registerClass({
    Implements: [Gio.AppInfo],
    Properties: {
        'empty': GObject.ParamSpec.boolean(
            'empty', 'empty', 'empty',
            GObject.ParamFlags.READWRITE,
            true),
    },
},
class TrashAppInfo extends LocationAppInfo {
    static initPromises(file) {
        if (TrashAppInfo._promisified)
            return;

        const trashProto = file.constructor.prototype;
        Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish');
        Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async', 'next_files_finish');
        Gio._promisify(trashProto, 'enumerate_children_async', 'enumerate_children_finish');
        Gio._promisify(trashProto, 'query_info_async', 'query_info_finish');
        TrashAppInfo._promisified = true;
    }

    _init(cancellable = null) {
        super._init({
            location: Gio.file_new_for_uri(TRASH_URI),
            name: __('Trash'),
            icon: Gio.ThemedIcon.new(FALLBACK_TRASH_ICON),
            cancellable,
        });
        TrashAppInfo.initPromises(this.location);

        try {
            this._monitor = this.location.monitor_directory(0, this.cancellable);
            this._schedUpdateId = 0;
            this._monitorChangedId = this._monitor.connect('changed', () =>
                this._onTrashChange());
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;
            logError(e, 'Impossible to monitor trash');
        }
        this._updateTrash();

        this.connect('notify::empty', () => this._updateLocationIcon());
        this.notify('empty');
    }

    destroy() {
        if (this._schedUpdateId) {
            GLib.source_remove(this._schedUpdateId);
            this._schedUpdateId = 0;
        }
        this._updateTrashCancellable?.cancel();
        this._monitor?.disconnect(this._monitorChangedId);
        this._monitor = null;

        super.destroy();
    }

    list_actions() {
        return this.empty ? [] : ['empty-trash'];
    }

    get_action_name(action) {
        switch (action) {
        case 'empty-trash':
            return __('Empty Trash');
        default:
            return null;
        }
    }

    _onTrashChange() {
        if (this._schedUpdateId) {
            GLib.source_remove(this._schedUpdateId);
            this._schedUpdateId = 0;
        }

        if (this._monitor.is_cancelled())
            return;

        this._schedUpdateId = GLib.timeout_add(GLib.PRIORITY_LOW,
            UPDATE_TRASH_DELAY, () => {
                this._schedUpdateId = 0;
                this._updateTrash();
                return GLib.SOURCE_REMOVE;
            });
    }

    async _updateTrash() {
        const priority = GLib.PRIORITY_LOW;
        this._updateTrashCancellable?.cancel();
        const cancellable = new Utils.CancellableChild(this.cancellable);
        this._updateTrashCancellable = cancellable;

        try {
            const trashInfo = await this.location.query_info_async(
                Gio.FILE_ATTRIBUTE_TRASH_ITEM_COUNT,
                Gio.FileQueryInfoFlags.NONE,
                priority, cancellable);
            this.empty = !trashInfo.get_attribute_uint32(
                Gio.FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
            return;
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e, 'Impossible to get trash children from infos');
        } finally {
            cancellable.cancel();
            if (this._updateIconCancellable === cancellable)
                delete this._updateTrashCancellable;
        }

        try {
            const childrenEnumerator = await this.location.enumerate_children_async(
                Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE,
                priority, cancellable);
            const children = await childrenEnumerator.next_files_async(1,
                priority, cancellable);
            this.empty = !children.length;

            await childrenEnumerator.close_async(priority, null);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e, 'Impossible to enumerate trash children');
        } finally {
            cancellable.cancel();
            if (this._updateIconCancellable === cancellable)
                delete this._updateTrashCancellable;
        }
    }

    launchAction(action, timestamp) {
        if (!this.list_actions().includes(action))
            throw new Error('Action %s is not supported by %s', action, this);

        const nautilus = makeNautilusFileOperationsProxy();
        const askConfirmation = true;
        nautilus.EmptyTrashRemote(askConfirmation,
            nautilus.platformData({timestamp}), (_p, error) => {
                if (error)
                    logError(error, 'Empty trash failed');
            }, this.cancellable);
    }
});

/**
 * @param shellApp
 */
function wrapWindowsBackedApp(shellApp) {
    if (shellApp._dtdData)
        throw new Error('%s has been already wrapped'.format(shellApp));

    shellApp._dtdData = {
        windows: [],
        state: undefined,
        startingWorkspace: 0,
        isFocused: false,
        proxyProperties: [],
        sources: new Set(),
        signalConnections: new Utils.GlobalSignalsHandler(),
        methodInjections: new Utils.InjectionsHandler(),
        propertyInjections: new Utils.PropertyInjectionsHandler(),
        addProxyProperties(parent, proxyProperties) {
            Object.entries(proxyProperties).forEach(([p, o]) => {
                const publicProp = o.public ? p : `_${p}`;
                const get = o.getter && o.value instanceof Function
                    ? () => this[p]() : () => this[p];
                Object.defineProperty(parent, publicProp, Object.assign({
                    get,
                    set: v => (this[p] = v),
                    configurable: true,
                    enumerable: !!o.enumerable,
                }, o.readOnly ? {set: undefined} : {}));
                if (o.value)
                    this[p] = o.value;
                this.proxyProperties.push(publicProp);
            });
        },
        destroy() {
            this.windows = [];
            this.proxyProperties = [];
            this.sources.forEach(s => GLib.source_remove(s));
            this.sources.clear();
            this.signalConnections.destroy();
            this.methodInjections.destroy();
            this.propertyInjections.destroy();
        },
    };

    shellApp._dtdData.addProxyProperties(shellApp, {
        windows: {},
        state: {},
        startingWorkspace: {},
        isFocused: {public: true},
        signalConnections: {readOnly: true},
        sources: {readOnly: true},
        checkFocused: {},
        setDtdData: {},
    });

    shellApp._setDtdData = function (data, params = {}) {
        for (const [name, value] of Object.entries(data)) {
            if (params.readOnly && name in this._dtdData)
                throw new Error('Property %s is already defined'.format(name));
            const defaultParams = {public: true, readOnly: true};
            this._dtdData.addProxyProperties(this, {
                [name]: {...defaultParams, ...params, value},
            });
        }
    };

    const m = (...args) => shellApp._dtdData.methodInjections.add(shellApp, ...args);
    const p = (...args) => shellApp._dtdData.propertyInjections.add(shellApp, ...args);

    // mi is Method injector, pi is Property injector
    shellApp._setDtdData({mi: m, pi: p}, {public: false});

    m('get_state', () => shellApp._state ?? shellApp._getStateByWindows());
    p('state', {get: () => shellApp.get_state()});

    m('get_windows', () => shellApp._windows);
    m('get_n_windows', () => shellApp._windows.length);
    m('get_pids', () => shellApp._windows.reduce((pids, w) => {
        if (w.get_pid() > 0 && !pids.includes(w.get_pid()))
            pids.push(w.get_pid());
        return pids;
    }, []));
    m('is_on_workspace', (_om, workspace) => shellApp._windows.some(w =>
        w.get_workspace() === workspace) ||
        (shellApp.state === Shell.AppState.STARTING &&
         [-1, workspace.index()].includes(shellApp._startingWorkspace)));
    m('request_quit', () => shellApp._windows.filter(w =>
        w.can_close()).forEach(w => w.delete(global.get_current_time())));

    shellApp._setDtdData({
        _getStateByWindows() {
            return this.get_n_windows() ? Shell.AppState.RUNNING : Shell.AppState.STOPPED;
        },

        _updateWindows() {
            throw new GObject.NotImplementedError(`_updateWindows in ${this.constructor.name}`);
        },

        _notifyStateChanged() {
            Shell.AppSystem.get_default().emit('app-state-changed', this);
            this.notify('state');
        },

        _setState(state) {
            const oldState = this.state;
            this._state = state;

            if (this.state !== oldState)
                this._notifyStateChanged();
        },

        _setWindows(windows) {
            const oldState = this.state;
            const oldWindows = this._windows.slice();
            const result = {windowsChanged: false, stateChanged: false};
            this._state = undefined;

            if (windows.length !== oldWindows.length ||
                windows.some((win, index) => win !== oldWindows[index])) {
                this._windows = windows.filter(w => !w.is_override_redirect());
                this.emit('windows-changed');
                result.windowsChanged = true;
            }

            if (this.state !== oldState) {
                this._notifyStateChanged();
                this._checkFocused();
                result.stateChanged = true;
            }

            return result;
        },
    }, {readOnly: false});

    shellApp._sources.add(GLib.idle_add(GLib.DEFAULT_PRIORITY, () => {
        shellApp._updateWindows();
        shellApp._sources.delete(GLib.main_current_source().source_id);
        return GLib.SOURCE_REMOVE;
    }));

    const windowTracker = Shell.WindowTracker.get_default();
    shellApp._checkFocused = function () {
        if (this._windows.some(w => w.has_focus())) {
            this.isFocused = true;
            windowTracker.notify('focus-app');
        } else if (this.isFocused) {
            this.isFocused = false;
            windowTracker.notify('focus-app');
        }
    };

    shellApp._checkFocused();
    shellApp._signalConnections.add(global.display, 'notify::focus-window', () =>
        shellApp._checkFocused());

    // Re-implements shell_app_activate_window for generic activation and alt-tab support
    m('activate_window', function (_om, window, timestamp) {
        /* eslint-disable no-invalid-this */
        if (!window)
            [window] = this.get_windows();
        else if (!this._windows.includes(window))
            return;

        const currentWorkspace = global.workspace_manager.get_active_workspace();
        const workspace = window.get_workspace();
        const sameWorkspaceWindows = this.get_windows().filter(w =>
            w.get_workspace() === workspace);
        sameWorkspaceWindows.forEach(w => w.raise());

        if (workspace !== currentWorkspace)
            workspace.activate_with_focus(window, timestamp);
        else
            window.activate(timestamp);
        /* eslint-enable no-invalid-this */
    });

    // Re-implements shell_app_activate_full for generic activation and dash support
    m('activate_full', function (_om, workspace, timestamp) {
        /* eslint-disable no-invalid-this */
        if (!timestamp)
            timestamp = global.get_current_time();

        switch (this.state) {
        case Shell.AppState.STOPPED:
            try {
                this._startingWorkspace = workspace;
                this._setState(Shell.AppState.STARTING);
                this.launch(timestamp, workspace, Shell.AppLaunchGpu.APP_PREF);
            } catch (e) {
                logError(e);
                this._setState(Shell.AppState.STOPPED);
                global.notify_error(_('Failed to launch “%s”'.format(
                    this.get_name())), e.message);
            }
            break;
        case Shell.AppState.RUNNING:
            this.activate_window(null, timestamp);
            break;
        }
        /* eslint-enable no-invalid-this */
    });

    m('activate', () => shellApp.activate_full(-1, 0));

    m('compare', (_om, other) => Utils.shellAppCompare(shellApp, other));

    const {destroy: defaultDestroy} = shellApp;
    shellApp.destroy = function () {
        /* eslint-disable no-invalid-this */
        this._dtdData.proxyProperties.forEach(prop => delete this[prop]);
        this._dtdData.destroy();
        this._dtdData = undefined;
        this.appInfo.destroy?.();
        this.destroy = defaultDestroy;
        defaultDestroy?.call(this);
        /* eslint-enable no-invalid-this */
    };

    return shellApp;
}

/**
 * We can't inherit from Shell.App as it's a final type, so let's patch it
 *
 * @param params
 */
function makeLocationApp(params) {
    if (!(params?.appInfo instanceof LocationAppInfo))
        throw new TypeError('Invalid location');

    const {fallbackIconName} = params;
    delete params.fallbackIconName;

    const shellApp = new Shell.App(params);
    wrapWindowsBackedApp(shellApp);

    shellApp._setDtdData({
        location: () => shellApp.appInfo.location,
        isTrash: shellApp.appInfo instanceof TrashAppInfo,
    }, {getter: true, enumerable: true});

    shellApp._mi('toString', defaultToString =>
        '[LocationApp "%s" - %s]'.format(shellApp.get_id(),
            defaultToString.call(shellApp)));

    shellApp._mi('launch', (_om, timestamp, workspace, _gpuPref) =>
        shellApp.appInfo.launch([],
            global.create_app_launch_context(timestamp, workspace)));

    shellApp._mi('launch_action', (_om, actionName, ...args) =>
        shellApp.appInfo.launchAction(actionName, ...args));

    shellApp._mi('create_icon_texture', (_om, iconSize) => new St.Icon({
        iconSize,
        gicon: shellApp.icon,
        fallbackIconName,
    }));

    // FIXME: We need to add a new API to Nautilus to open new windows
    shellApp._mi('can_open_new_window', () => {
        try {
            if (!shellApp.get_n_windows())
                return true;

            const handlerApp = shellApp.appInfo.getHandlerApp();

            if (handlerApp.has_key('SingleMainWindow'))
                return !handlerApp.get_boolean('SingleMainWindow');

            if (handlerApp.has_key('X-GNOME-SingleWindow'))
                return !handlerApp.get_boolean('X-GNOME-SingleWindow');

            if (handlerApp.get_commandline()?.split(' ').includes('--new-window'))
                return true;

            const [window] = shellApp.get_windows();
            if (window && window.get_gtk_window_object_path())
                return window.get_gtk_application_id() === null;

            return true;
        } catch {
            return false;
        }
    });

    shellApp._mi('open_new_window', function (_om, workspace) {
        /* eslint-disable no-invalid-this */
        const context = global.create_app_launch_context(0, workspace);
        if (!this.get_n_windows()) {
            this.appInfo.launch([], context);
            return;
        }
        const appId = this.appInfo.get_id();
        Gio.AppInfo.create_from_commandline(this.appInfo.get_commandline(),
            this.appInfo.get_id(), appId
                ? Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION
                : Gio.AppInfoCreateFlags.NONE).launch_uris(
            [this.appInfo.location.get_uri()], context);
        /* eslint-enable no-invalid-this */
    });

    if (shellApp.appInfo instanceof MountableVolumeAppInfo) {
        shellApp._mi('get_busy', function (parentGetBusy) {
            /* eslint-disable no-invalid-this */
            if (this.appInfo.busy)
                return true;
            return parentGetBusy.call(this);
            /* eslint-enable no-invalid-this */
        });
        shellApp._pi('busy', {get: () => shellApp.get_busy()});
        shellApp._signalConnections.add(shellApp.appInfo, 'notify::busy', _ =>
            shellApp.notify('busy'));
    }

    shellApp._mi('get_windows', function () {
        /* eslint-disable no-invalid-this */
        if (this._needsResort)
            this._sortWindows();
        return this._windows;
        /* eslint-enable no-invalid-this */
    });

    const {fm1Client} = Docking.DockManager.getDefault();
    shellApp._setDtdData({
        _needsResort: true,

        _windowsOrderChanged() {
            this._needsResort = true;
            this.emit('windows-changed');
        },

        _sortWindows() {
            this._windows.sort(Utils.shellWindowsCompare);
            this._needsResort = false;
        },

        _updateWindows() {
            const windows = fm1Client.getWindows(this.location?.get_uri()).sort(
                Utils.shellWindowsCompare);
            const {windowsChanged} = this._setWindows(windows);

            if (!windowsChanged)
                return;

            this._signalConnections.removeWithLabel(Labels.LOCATION_WINDOWS);
            windows.forEach(w =>
                this._signalConnections.addWithLabel(Labels.LOCATION_WINDOWS, w,
                    'notify::user-time', () => {
                        if (w !== this._windows[0])
                            this._windowsOrderChanged();
                    }));
        },
    }, {readOnly: false});

    shellApp._signalConnections.add(fm1Client, 'windows-changed', () =>
        shellApp._updateWindows());
    shellApp._signalConnections.add(shellApp.appInfo, 'notify::icon', () =>
        shellApp.notify('icon'));
    shellApp._signalConnections.add(global.workspaceManager,
        'workspace-switched', () => shellApp._windowsOrderChanged());

    return shellApp;
}

/**
 *
 */
function getFileManagerApp() {
    return Shell.AppSystem.get_default().lookup_app(FILE_MANAGER_DESKTOP_APP_ID);
}

/**
 *
 */
export function wrapFileManagerApp() {
    const fileManagerApp = getFileManagerApp();
    if (!fileManagerApp)
        return null;

    if (fileManagerApp._dtdData)
        return fileManagerApp;

    const originalGetWindows = fileManagerApp.get_windows;
    wrapWindowsBackedApp(fileManagerApp);

    const {removables, trash} = Docking.DockManager.getDefault();
    fileManagerApp._signalConnections.addWithLabel(Labels.WINDOWS_CHANGED,
        fileManagerApp, 'windows-changed', () => {
            fileManagerApp.stop_emission_by_name('windows-changed');
            // Let's wait for the location app to take control before of us
            const id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => {
                fileManagerApp._sources.delete(id);
                fileManagerApp._updateWindows();
                return GLib.SOURCE_REMOVE;
            });
            fileManagerApp._sources.add(id);
        });

    fileManagerApp._signalConnections.add(global.workspaceManager,
        'workspace-switched', () => {
            fileManagerApp._signalConnections.blockWithLabel(Labels.WINDOWS_CHANGED);
            fileManagerApp.emit('windows-changed');
            fileManagerApp._signalConnections.unblockWithLabel(Labels.WINDOWS_CHANGED);
        });

    if (removables) {
        fileManagerApp._signalConnections.add(removables, 'changed', () =>
            fileManagerApp._updateWindows());
        fileManagerApp._signalConnections.add(removables, 'windows-changed', () =>
            fileManagerApp._updateWindows());
    }

    if (trash?.getApp()) {
        fileManagerApp._signalConnections.add(trash.getApp(), 'windows-changed', () =>
            fileManagerApp._updateWindows());
    }

    fileManagerApp._updateWindows = function () {
        const locationWindows = [];
        getRunningApps().forEach(a => locationWindows.push(...a.get_windows()));
        const windows = originalGetWindows.call(this).filter(w =>
            !locationWindows.includes(w));

        this._signalConnections.blockWithLabel(Labels.WINDOWS_CHANGED);
        this._setWindows(windows);
        this._signalConnections.unblockWithLabel(Labels.WINDOWS_CHANGED);
    };

    fileManagerApp._mi('toString', defaultToString =>
        '[FileManagerApp - %s]'.format(defaultToString.call(fileManagerApp)));

    return fileManagerApp;
}

/**
 *
 */
export function unWrapFileManagerApp() {
    const fileManagerApp = getFileManagerApp();
    if (!fileManagerApp || !fileManagerApp._dtdData)
        return;

    fileManagerApp.destroy();
}

/**
 * This class maintains a Shell.App representing the Trash and keeps it
 * up-to-date as the trash fills and is emptied over time.
 */
export class Trash {
    destroy() {
        this._trashApp?.destroy();
    }

    _ensureApp() {
        if (this._trashApp)
            return;

        this._trashApp = makeLocationApp({
            appInfo: new TrashAppInfo(new Gio.Cancellable()),
            fallbackIconName: FALLBACK_TRASH_ICON,
        });
    }

    getApp() {
        this._ensureApp();
        return this._trashApp;
    }
}

/**
 * This class maintains Shell.App representations for removable devices
 * plugged into the system, and keeps the list of Apps up-to-date as
 * devices come and go and are mounted and unmounted.
 */
export class Removables {
    static initVolumePromises(object) {
        // TODO: This can be simplified using actual interface type when we
        // can depend on gjs 1.72
        if (!(object instanceof Gio.Volume) || object.constructor.prototype._d2dPromisified)
            return;

        Gio._promisify(object.constructor.prototype, 'mount', 'mount_finish');
        Gio._promisify(object.constructor.prototype, 'eject_with_operation',
            'eject_with_operation_finish');
        object.constructor.prototype._d2dPromisified = true;
    }

    static initMountPromises(object) {
        // TODO: This can be simplified using actual interface type when we
        // can depend on gjs 1.72
        if (!(object instanceof Gio.Mount) || object.constructor.prototype._d2dPromisified)
            return;

        Gio._promisify(object.constructor.prototype, 'eject_with_operation',
            'eject_with_operation_finish');
        Gio._promisify(object.constructor.prototype, 'unmount_with_operation',
            'unmount_with_operation_finish');
        object.constructor.prototype._d2dPromisified = true;
    }

    constructor() {
        this._signalsHandler = new Utils.GlobalSignalsHandler();

        this._monitor = Gio.VolumeMonitor.get();
        this._cancellable = new Gio.Cancellable();

        this._monitor.get_mounts().forEach(m => Removables.initMountPromises(m));
        this._updateVolumes();

        this._signalsHandler.add([
            this._monitor,
            'volume-added',
            (_, volume) => this._onVolumeAdded(volume),
        ], [
            this._monitor,
            'volume-removed',
            (_, volume) => this._onVolumeRemoved(volume),
        ], [
            this._monitor,
            'mount-added',
            (_, mount) => this._onMountAdded(mount),
        ], [
            Docking.DockManager.settings,
            'changed::show-mounts-only-mounted',
            () => this._updateVolumes(),
        ], [
            Docking.DockManager.settings,
            'changed::show-mounts-network',
            () => this._updateVolumes(),
        ]);
    }

    destroy() {
        this._volumeApps.forEach(a => a.destroy());
        this._volumeApps = [];
        this._cancellable.cancel();
        this._cancellable = null;
        this._signalsHandler.destroy();
        this._monitor = null;
    }

    _updateVolumes() {
        this._volumeApps?.forEach(a => a.destroy());
        this._volumeApps = [];
        this.emit('changed');

        this._monitor.get_volumes().forEach(v => this._onVolumeAdded(v));
    }

    _onVolumeAdded(volume) {
        Removables.initVolumePromises(volume);

        if (!Docking.DockManager.settings.showMountsNetwork &&
            volume.get_identifier('class') === 'network')
            return;


        const mount = volume.get_mount();
        if (mount) {
            if (mount.is_shadowed())
                return;
            if (!mount.can_eject() && !mount.can_unmount())
                return;
        } else {
            if (Docking.DockManager.settings.showMountsOnlyMounted)
                return;
            if (!volume.can_mount() && !volume.can_eject())
                return;
        }

        const appInfo = new MountableVolumeAppInfo(volume,
            new Utils.CancellableChild(this._cancellable));
        const volumeApp = makeLocationApp({
            appInfo,
            fallbackIconName: FALLBACK_REMOVABLE_MEDIA_ICON,
        });

        volumeApp._signalConnections.add(volumeApp, 'windows-changed',
            () => this.emit('windows-changed', volumeApp));

        if (Docking.DockManager.settings.showMountsOnlyMounted) {
            volumeApp._signalConnections.add(appInfo, 'notify::mount',
                () => !appInfo.mount && this._onVolumeRemoved(appInfo.volume));
        }

        this._volumeApps.push(volumeApp);
        this.emit('changed');
    }

    _onVolumeRemoved(volume) {
        const volumeIndex = this._volumeApps.findIndex(({appInfo}) =>
            appInfo.volume === volume);
        if (volumeIndex !== -1) {
            const [volumeApp] = this._volumeApps.splice(volumeIndex, 1);
            // We don't care about cancelling the ongoing operations from now on.
            volumeApp.appInfo.cancellable = null;
            volumeApp.destroy();
            this.emit('changed');
        }
    }

    _onMountAdded(mount) {
        Removables.initMountPromises(mount);

        if (!Docking.DockManager.settings.showMountsOnlyMounted)
            return;

        if (!this._volumeApps.find(({appInfo}) => appInfo.mount === mount)) {
            // In some Gio.Mount implementations the volume may be set after
            // mount is emitted, so we could just ignore it as we'll get it
            // later via volume-added
            const volume = mount.get_volume();
            if (volume)
                this._onVolumeAdded(volume);
        }
    }

    getApps() {
        return this._volumeApps;
    }
}
Signals.addSignalMethods(Removables.prototype);

/**
 *
 */
function getApps() {
    const dockManager = Docking.DockManager.getDefault();
    const locationApps = [];

    if (dockManager.removables)
        locationApps.push(...dockManager.removables.getApps());

    if (dockManager.trash)
        locationApps.push(dockManager.trash.getApp());

    return locationApps;
}

/**
 *
 */
export function getRunningApps() {
    return getApps().filter(a => a.state === Shell.AppState.RUNNING);
}

/**
 *
 */
export function getStartingApps() {
    return getApps().filter(a => a.state === Shell.AppState.STARTING);
}

Filemanager

Name Type Size Permission Actions
dependencies Folder 0755
media Folder 0755
README.md File 2.4 KB 0644
Settings.ui File 163.97 KB 0644
appIconIndicators.js File 44.14 KB 0644
appIcons.js File 57.69 KB 0644
appIconsDecorator.js File 5.74 KB 0644
appSpread.js File 7.06 KB 0644
dash.js File 41.15 KB 0644
dbusmenuUtils.js File 10.63 KB 0644
desktopIconsIntegration.js File 6.39 KB 0644
docking.js File 95.61 KB 0644
extension.js File 1.18 KB 0644
fileManager1API.js File 6.66 KB 0644
imports.js File 859 B 0644
intellihide.js File 11.11 KB 0644
launcherAPI.js File 9.22 KB 0644
locations.js File 48.36 KB 0644
locationsWorker.js File 2.69 KB 0644
metadata.json File 509 B 0644
notificationsMonitor.js File 3.86 KB 0644
prefs.js File 47.03 KB 0644
stylesheet.css File 75.26 KB 0644
theming.js File 19.89 KB 0644
ubuntu.css File 0 B 0644
utils.js File 20.27 KB 0644
windowPreview.js File 21.99 KB 0644
Filemanager