__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ 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 Params from 'resource:///org/gnome/shell/misc/params.js';
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
import * as Config from 'resource:///org/gnome/shell/misc/config.js';

import * as IconCache from './iconCache.js';
import * as Util from './util.js';
import * as Interfaces from './interfaces.js';
import * as PixmapsUtils from './pixmapsUtils.js';
import * as PromiseUtils from './promiseUtils.js';
import * as SettingsManager from './settingsManager.js';
import {DBusProxy} from './dbusProxy.js';

Gio._promisify(Gio.File.prototype, 'read_async');
Gio._promisify(GdkPixbuf.Pixbuf, 'get_file_info_async');
Gio._promisify(GdkPixbuf.Pixbuf, 'new_from_stream_at_scale_async',
    'new_from_stream_finish');
Gio._promisify(St.IconInfo.prototype, 'load_symbolic_async');
Gio._promisify(Gio.DBusConnection.prototype, 'call');

const MAX_UPDATE_FREQUENCY = 30; // In ms
const FALLBACK_ICON_NAME = 'image-loading-symbolic';
const PIXMAPS_FORMAT = imports.gi.Cogl.PixelFormat.ARGB_8888;
const GNOME_48_PLUS = Number(Config.PACKAGE_VERSION.split(".")[0]) >= 48;

export const SNICategory = Object.freeze({
    APPLICATION: 'ApplicationStatus',
    COMMUNICATIONS: 'Communications',
    SYSTEM: 'SystemServices',
    HARDWARE: 'Hardware',
});

export const SNIStatus = Object.freeze({
    PASSIVE: 'Passive',
    ACTIVE: 'Active',
    NEEDS_ATTENTION: 'NeedsAttention',
});

const SNIconType = Object.freeze({
    NORMAL: 0,
    ATTENTION: 1,
    OVERLAY: 2,

    toPropertyName: (iconType, params = {isPixbuf: false}) => {
        let propertyName = 'Icon';

        if (iconType === SNIconType.OVERLAY)
            propertyName = 'OverlayIcon';
        else if (iconType === SNIconType.ATTENTION)
            propertyName = 'AttentionIcon';

        return `${propertyName}${params.isPixbuf ? 'Pixmap' : 'Name'}`;
    },
});

export const AppIndicatorProxy = GObject.registerClass(
class AppIndicatorProxy extends DBusProxy {
    static get interfaceInfo() {
        if (!this._interfaceInfo) {
            this._interfaceInfo = Gio.DBusInterfaceInfo.new_for_xml(
                Interfaces.StatusNotifierItem);
        }
        return this._interfaceInfo;
    }

    static get OPTIONAL_PROPERTIES() {
        return [
            'XAyatanaLabel',
            'XAyatanaLabelGuide',
            'XAyatanaOrderingIndex',
            'IconAccessibleDesc',
            'AttentionAccessibleDesc',
        ];
    }

    static get TUPLE_TYPE() {
        if (!this._tupleType)
            this._tupleType = new GLib.VariantType('()');

        return this._tupleType;
    }

    static destroy() {
        delete this._interfaceInfo;
        delete this._tupleType;
    }

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

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

        this.set_cached_property('Status',
            new GLib.Variant('s', SNIStatus.PASSIVE));


        this._accumulatedProperties = new Set();
        this._cancellables = new Map();
        this._changedProperties = Object.create(null);
    }

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

        this._setupProxyPropertyList();
    }

    destroy() {
        const cachedProperties = this.get_cached_property_names();
        if (cachedProperties) {
            cachedProperties.forEach(propertyName =>
                this.set_cached_property(propertyName, null));
        }

        super.destroy();
    }

    _onNameOwnerChanged() {
        this._resetNeededProperties();

        if (!this.gNameOwner)
            this._cancelRefreshProperties();
        else
            this._setupProxyPropertyList();
    }

    _setupProxyPropertyList() {
        this._propertiesList =
            (this.get_cached_property_names() || []).filter(p =>
                this.gInterfaceInfo.properties.some(pInfo => pInfo.name === p));

        if (this._propertiesList.length) {
            AppIndicatorProxy.OPTIONAL_PROPERTIES.forEach(
                p => this._addExtraProperty(p));
        }
    }

    _addExtraProperty(name) {
        if (this._propertiesList.includes(name))
            return;

        if (!(name in this)) {
            Object.defineProperty(this, name, {
                configurable: false,
                enumerable: true,
                get: () => {
                    const v = this.get_cached_property(name);
                    return v ? v.deep_unpack() : null;
                },
            });
        }

        this._propertiesList.push(name);
    }

    _signalToPropertyName(signal) {
        if (signal.startsWith('New'))
            return signal.substr(3);
        else if (signal.startsWith('XAyatanaNew'))
            return `XAyatana${signal.substr(11)}`;

        return null;
    }

    // The Author of the spec didn't like the PropertiesChanged signal, so he invented his own
    async _refreshOwnProperties(prop) {
        await Promise.all(
            [prop, `${prop}Name`, `${prop}Pixmap`, `${prop}AccessibleDesc`].filter(p =>
                this._propertiesList.includes(p)).map(async p => {
                try {
                    await this.refreshProperty(p, {
                        skipEqualityCheck: p.endsWith('Pixmap'),
                    });
                } catch (e) {
                    if (!AppIndicatorProxy.OPTIONAL_PROPERTIES.includes(p) ||
                        !(e instanceof Gio.DBusError))
                        logError(e);
                }
            }));
    }

    _onSignal(sender, signal, ...args) {
        this._onSignalAsync(sender, signal, ...args).catch(e => {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e, `Error while processing signal '${signal}'`);
        });
    }

    async _onSignalAsync(_sender, signal, params) {
        const property = this._signalToPropertyName(signal);
        if (!property)
            return;

        if (this.status === SNIStatus.PASSIVE &&
            ![...AppIndicator.NEEDED_PROPERTIES, 'Status'].includes(property)) {
            this._accumulatedProperties.add(property);
            return;
        }

        if (!params.get_type().equal(AppIndicatorProxy.TUPLE_TYPE)) {
            // If the property includes arguments, we can just queue the signal emission
            const [value] = params.unpack();
            try {
                await this._queuePropertyUpdate(property, value);
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                    throw e;
            }

            if (!this._accumulatedProperties.size)
                return;
        } else {
            this._accumulatedProperties.add(property);
        }

        if (this._signalsAccumulator)
            return;

        this._signalsAccumulator = new PromiseUtils.TimeoutPromise(
            MAX_UPDATE_FREQUENCY, GLib.PRIORITY_DEFAULT_IDLE, this._cancellable);
        try {
            await this._signalsAccumulator;
            const refreshPropertiesPromises =
                [...this._accumulatedProperties].map(p =>
                    this._refreshOwnProperties(p));
            this._accumulatedProperties.clear();
            await Promise.all(refreshPropertiesPromises);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                throw e;
        } finally {
            delete this._signalsAccumulator;
        }
    }

    _resetNeededProperties() {
        AppIndicator.NEEDED_PROPERTIES.forEach(p =>
            this.set_cached_property(p, null));
    }

    async refreshAllProperties() {
        const cancellableName = 'org.freedesktop.DBus.Properties.GetAll';
        const cancellable = this._cancelRefreshProperties({
            propertyName: cancellableName,
            addNew: true,
        });

        try {
            const [valuesVariant] = (await this.getProperties(
                cancellable)).deep_unpack();

            this._cancellables.delete(cancellableName);

            await Promise.all(
                Object.entries(valuesVariant).map(([propertyName, valueVariant]) =>
                    this._queuePropertyUpdate(propertyName, valueVariant, {
                        skipEqualityCheck: true,
                        cancellable,
                    })));
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                // the property may not even exist, silently ignore it
                Util.Logger.debug(`While refreshing all properties: ${e}`);

                this.get_cached_property_names().forEach(propertyName =>
                    this.set_cached_property(propertyName, null));

                this._cancellables.delete(cancellableName);
                throw e;
            }
        }
    }

    async refreshProperty(propertyName, params) {
        params = Params.parse(params, {
            skipEqualityCheck: false,
        });

        const cancellable = this._cancelRefreshProperties({
            propertyName,
            addNew: true,
        });

        try {
            const [valueVariant] = (await this.getProperty(
                propertyName, cancellable)).deep_unpack();

            this._cancellables.delete(propertyName);
            await this._queuePropertyUpdate(propertyName, valueVariant,
                Object.assign(params, {cancellable}));
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                // the property may not even exist, silently ignore it
                Util.Logger.debug(`Error when calling 'Get(${propertyName})' ` +
                    `in ${this.gName}, ${this.gObjectPath}, ` +
                    `org.freedesktop.DBus.Properties, ${this.gInterfaceName} ` +
                    `while refreshing property ${propertyName}: ${e}`);
                this.set_cached_property(propertyName, null);
                this._cancellables.delete(propertyName);
                delete this._changedProperties[propertyName];
                throw e;
            }
        }
    }

    async _queuePropertyUpdate(propertyName, value, params) {
        params = Params.parse(params, {
            skipEqualityCheck: false,
            cancellable: null,
        });

        if (!params.skipEqualityCheck) {
            const cachedProperty = this.get_cached_property(propertyName);

            if (value && cachedProperty &&
                value.equal(this.get_cached_property(propertyName)))
                return;
        }

        this.set_cached_property(propertyName, value);

        // synthesize a batched property changed event
        this._changedProperties[propertyName] = value;

        if (!this._propertiesEmitTimeout || !this._propertiesEmitTimeout.pending()) {
            if (!params.cancellable) {
                params.cancellable = this._cancelRefreshProperties({
                    propertyName,
                    addNew: true,
                });
            }
            this._propertiesEmitTimeout = new PromiseUtils.TimeoutPromise(
                MAX_UPDATE_FREQUENCY * 2, GLib.PRIORITY_DEFAULT_IDLE, params.cancellable);
            await this._propertiesEmitTimeout;

            if (Object.keys(this._changedProperties).length) {
                this.emit('g-properties-changed', GLib.Variant.new('a{sv}',
                    this._changedProperties), []);
                this._changedProperties = Object.create(null);
            }
        }
    }

    _cancelRefreshProperties(params) {
        params = Params.parse(params, {
            propertyName: undefined,
            addNew: false,
        });

        if (!this._cancellables.size && !params.addNew)
            return null;

        if (params.propertyName !== undefined) {
            let cancellable = this._cancellables.get(params.propertyName);
            if (cancellable) {
                cancellable.cancel();

                if (!params.addNew)
                    this._cancellables.delete(params.propertyName);
            }

            if (params.addNew) {
                cancellable = new Util.CancellableChild(this._cancellable);
                this._cancellables.set(params.propertyName, cancellable);
                return cancellable;
            }
        } else {
            this._cancellables.forEach(c => c.cancel());
            this._cancellables.clear();
            this._changedProperties = Object.create(null);
        }

        return null;
    }
});

/**
 * the AppIndicator class serves as a generic container for indicator information and functions common
 * for every displaying implementation (IndicatorMessageSource and IndicatorStatusIcon)
 */
export class AppIndicator extends Signals.EventEmitter {
    static get NEEDED_PROPERTIES() {
        return ['Id', 'Menu'];
    }

    constructor(service, busName, object) {
        super();

        this.isReady = false;
        this.busName = busName;
        this._uniqueId = Util.indicatorId(service, busName, object);

        this._cancellable = new Gio.Cancellable();
        this._proxy = new AppIndicatorProxy(busName, object);
        this._invalidatedPixmapsIcons = new Set();

        this._setupProxy().catch(logError);
        Util.connectSmart(this._proxy, 'g-properties-changed', this, this._onPropertiesChanged);
        Util.connectSmart(this._proxy, 'notify::g-name-owner', this, this._nameOwnerChanged);

        if (this.uniqueId === service) {
            this._nameWatcher = new Util.NameWatcher(service);
            Util.connectSmart(this._nameWatcher, 'changed', this, this._nameOwnerChanged);
        }
    }

    async _setupProxy() {
        const cancellable = this._cancellable;

        try {
            await this._proxy.initAsync(cancellable);
            this._checkIfReady();
            await this._checkNeededProperties();
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                logError(e, `While initalizing proxy for ${this._uniqueId}`);
                this.destroy();
            }
        }

        // We try to lookup the activate method to see if the app supports it
        try {
            const introspectionVariant = await this._proxy.gConnection.call(
                this._proxy.gNameOwner, this._proxy.gObjectPath,
                'org.freedesktop.DBus.Introspectable', 'Introspect', null, null,
                Gio.DBusCallFlags.NONE, -1, cancellable);
            const [introspectionXml] = introspectionVariant.deep_unpack();
            const nodeInfo = Gio.DBusNodeInfo.new_for_xml(introspectionXml);
            const interfaceInfo = nodeInfo.lookup_interface(this._proxy.gInterfaceName);
            this.supportsActivation = !!interfaceInfo.lookup_method('Activate');
            this._hasAyatanaSecondaryActivate =
                !!interfaceInfo.lookup_method('XAyatanaSecondaryActivate');
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                Util.Logger.debug(
                    `${this.uniqueId}, check for Activation support: ${e.message}`);
            }
        }

        try {
            this._commandLine = await Util.getProcessName(this.busName,
                cancellable, GLib.PRIORITY_LOW);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                Util.Logger.debug(
                    `${this.uniqueId}, failed getting command line: ${e.message}`);
            }
        }
    }

    _checkIfReady() {
        const wasReady = this.isReady;
        let isReady = false;

        if (this.hasNameOwner && this.id && this.menuPath)
            isReady = true;

        this.isReady = isReady;

        if (this.isReady && !wasReady) {
            if (this._delayCheck) {
                this._delayCheck.cancel();
                delete this._delayCheck;
            }

            this.emit('ready');
            return true;
        }

        return false;
    }

    async _checkNeededProperties() {
        if (this.id && this.menuPath)
            return true;

        const MAX_RETRIES = 3;
        const cancellable = this._cancellable;
        for (let checks = 0; checks < MAX_RETRIES; ++checks) {
            this._delayCheck = new PromiseUtils.TimeoutSecondsPromise(1,
                GLib.PRIORITY_DEFAULT_IDLE, cancellable);
            // eslint-disable-next-line no-await-in-loop
            await this._delayCheck;

            try {
                // eslint-disable-next-line no-await-in-loop
                await Promise.all(AppIndicator.NEEDED_PROPERTIES.map(p =>
                    this._proxy.refreshProperty(p)));
            } catch (e) {
                if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                    throw e;

                if (checks < MAX_RETRIES - 1)
                    continue;

                throw e;
            }

            if (this.id && this.menuPath)
                break;
        }

        return this.id && this.menuPath;
    }

    async _nameOwnerChanged() {
        if (!this.hasNameOwner) {
            this._checkIfReady();
        } else {
            try {
                await this._checkNeededProperties();
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                    Util.Logger.warn(`${this.uniqueId}, Impossible to get basic properties: ${e}`);
                    this.checkAlive();
                }
            }
        }

        this.emit('name-owner-changed');
    }

    // public property getters
    get title() {
        return this._proxy.Title;
    }

    get id() {
        return this._proxy.Id;
    }

    get uniqueId() {
        return this._uniqueId;
    }

    get status() {
        return this._proxy.Status;
    }

    get label() {
        return this._proxy.XAyatanaLabel || null;
    }

    get accessibleName() {
        const accessibleDesc = this.status === SNIStatus.NEEDS_ATTENTION
            ? this._proxy.AccessibleDesc : this._proxy.IconAccessibleDesc;

        return accessibleDesc || this.title;
    }

    get menuPath() {
        if (this._proxy.Menu === '/NO_DBUSMENU')
            return null;

        return this._proxy.Menu;
    }

    get attentionIcon() {
        return {
            theme: this._proxy.IconThemePath,
            name: this._proxy.AttentionIconName,
            pixmap: this._getPixmapProperty(SNIconType.ATTENTION),
        };
    }

    get icon() {
        return {
            theme: this._proxy.IconThemePath,
            name: this._proxy.IconName,
            pixmap: this._getPixmapProperty(SNIconType.NORMAL),
        };
    }

    get overlayIcon() {
        return {
            theme: this._proxy.IconThemePath,
            name: this._proxy.OverlayIconName,
            pixmap: this._getPixmapProperty(SNIconType.OVERLAY),
        };
    }

    get hasOverlayIcon() {
        const {name, pixmap} = this.overlayIcon;

        return name || (pixmap && pixmap.n_children());
    }

    get hasNameOwner() {
        if (this._nameWatcher && !this._nameWatcher.nameOnBus)
            return false;
        return !!this._proxy.g_name_owner;
    }

    get cancellable() {
        return this._cancellable;
    }

    async checkAlive() {
        // Some applications (hey electron!) just remove the indicator object
        // from bus after hiding it, without closing its bus name, so we are
        // not able to understand whe they're gone.
        // Thus we just kill it when an expected well-known method is failing.
        if (this.status !== SNIStatus.PASSIVE && this._checkIfReady()) {
            if (this._checkAliveTimeout) {
                this._checkAliveTimeout.cancel();
                delete this._checkAliveTimeout;
            }
            return;
        }

        if (this._checkAliveTimeout)
            return;

        try {
            const cancellable = this._cancellable;
            this._checkAliveTimeout = new PromiseUtils.TimeoutSecondsPromise(10,
                GLib.PRIORITY_DEFAULT_IDLE, cancellable);
            Util.Logger.debug(`${this.uniqueId}: may not respond, checking...`);
            await this._checkAliveTimeout;

            // We should call the Ping method instead but in some containers
            // such as snaps that's not accessible, so let's just use our own
            await this._proxy.getProperty('Status', cancellable);
        } catch (e) {
            if (e.matches(Gio.DBusError, Gio.DBusError.NAME_HAS_NO_OWNER) ||
                e.matches(Gio.DBusError, Gio.DBusError.SERVICE_UNKNOWN) ||
                e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_OBJECT) ||
                e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_INTERFACE) ||
                e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD) ||
                e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_PROPERTY)) {
                Util.Logger.warn(`${this.uniqueId}: not on bus anymore, removing it`);
                this.destroy();
                return;
            }

            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        } finally {
            delete this._checkAliveTimeout;
        }
    }

    _onPropertiesChanged(_proxy, changed, _invalidated) {
        const props = Object.keys(changed.unpack());
        const signalsToEmit = new Set();
        const checkIfReadyChanged = () => {
            if (checkIfReadyChanged.value === undefined)
                checkIfReadyChanged.value = this._checkIfReady();
            return checkIfReadyChanged.value;
        };

        props.forEach(property => {
            // some property changes require updates on our part,
            // a few need to be passed down to the displaying code
            if (property === 'Id')
                checkIfReadyChanged();

            // all these can mean that the icon has to be changed
            if (property.startsWith('Icon') ||
                property.startsWith('AttentionIcon'))
                signalsToEmit.add('icon');

            // same for overlays
            if (property.startsWith('OverlayIcon'))
                signalsToEmit.add('overlay-icon');

            // this may make all of our icons invalid
            if (property === 'IconThemePath') {
                signalsToEmit.add('icon');
                signalsToEmit.add('overlay-icon');
            }

            // the label will be handled elsewhere
            if (property === 'XAyatanaLabel')
                signalsToEmit.add('label');

            if (property === 'Menu') {
                if (!checkIfReadyChanged() && this.isReady)
                    signalsToEmit.add('menu');
            }

            if (property === 'IconAccessibleDesc' ||
                property === 'AttentionAccessibleDesc' ||
                property === 'Title')
                signalsToEmit.add('accessible-name');

            // status updates may cause the indicator to be hidden
            if (property === 'Status') {
                signalsToEmit.add('icon');
                signalsToEmit.add('overlay-icon');
                signalsToEmit.add('status');
                signalsToEmit.add('accessible-name');
            }
        });

        signalsToEmit.forEach(s => this.emit(s));
    }

    reset() {
        this.emit('reset');
    }

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

        this.disconnectAll();
        this._proxy.destroy();
        this._cancellable.cancel();
        this._invalidatedPixmapsIcons.clear();

        if (this._nameWatcher)
            this._nameWatcher.destroy();
        delete this._cancellable;
        delete this._proxy;
        delete this._nameWatcher;
    }

    _getPixmapProperty(iconType) {
        const propertyName = SNIconType.toPropertyName(iconType,
            {isPixbuf: true});
        const pixmap = this._proxy.get_cached_property(propertyName);
        const wasInvalidated = this._invalidatedPixmapsIcons.delete(iconType);

        if (!pixmap && wasInvalidated) {
            this._proxy.refreshProperty(propertyName, {
                skipEqualityCheck: true,
            }).catch(e => {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                    logError(e);
            });
        }

        return pixmap;
    }

    invalidatePixmapProperty(iconType) {
        this._invalidatedPixmapsIcons.add(iconType);
        this._proxy.set_cached_property(
            SNIconType.toPropertyName(iconType, {isPixbuf: true}), null);
    }

    _getActivationToken(timestamp) {
        const launchContext = global.create_app_launch_context(timestamp, -1);
        const fakeAppInfo = Gio.AppInfo.create_from_commandline(
            this._commandLine || 'true', this.id,
            Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION);
        return [launchContext, launchContext.get_startup_notify_id(fakeAppInfo, [])];
    }

    async provideActivationToken(timestamp) {
        if (this._hasProvideXdgActivationToken === false)
            return;

        const [launchContext, activationToken] = this._getActivationToken(timestamp);
        try {
            await this._proxy.ProvideXdgActivationTokenAsync(activationToken,
                this._cancellable);
            this._hasProvideXdgActivationToken = true;
        } catch (e) {
            launchContext.launch_failed(activationToken);

            if (e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD))
                this._hasProvideXdgActivationToken = false;
            else
                Util.Logger.warn(`${this.id}, failed to provide activation token: ${e.message}`);
        }
    }

    async open(x, y, timestamp) {
        const cancellable = this._cancellable;
        // we can't use WindowID because we're not able to get the x11 window id from a MetaWindow
        // nor can we call any X11 functions. Luckily, the Activate method usually works fine.
        // parameters are "an hint to the item where to show eventual windows" [sic]
        // ... and don't seem to have any effect.

        try {
            await this.provideActivationToken(timestamp);
            await this._proxy.ActivateAsync(x, y, cancellable);
            this.supportsActivation = true;
        } catch (e) {
            if (e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD)) {
                this.supportsActivation = false;
                Util.Logger.warn(`${this.id}, does not support activation: ${e.message}`);
                return;
            }

            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                Util.Logger.critical(`${this.id}, failed to activate: ${e.message}`);
        }
    }

    async secondaryActivate(timestamp, x, y) {
        const cancellable = this._cancellable;

        try {
            await this.provideActivationToken(timestamp);

            if (this._hasAyatanaSecondaryActivate !== false) {
                try {
                    await this._proxy.XAyatanaSecondaryActivateAsync(timestamp, cancellable);
                    this._hasAyatanaSecondaryActivate = true;
                } catch (e) {
                    if (e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD))
                        this._hasAyatanaSecondaryActivate = false;
                    else
                        throw e;
                }
            }

            if (!this._hasAyatanaSecondaryActivate)
                await this._proxy.SecondaryActivateAsync(x, y, cancellable);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                Util.Logger.critical(`${this.id}, failed to secondary activate: ${e.message}`);
        }
    }

    async scroll(dx, dy) {
        const cancellable = this._cancellable;

        try {
            const actions = [];

            if (dx !== 0) {
                actions.push(this._proxy.ScrollAsync(Math.floor(dx),
                    'horizontal', cancellable));
            }

            if (dy !== 0) {
                actions.push(this._proxy.ScrollAsync(Math.floor(dy),
                    'vertical', cancellable));
            }

            await Promise.all(actions);
        } catch (e) {
            Util.Logger.critical(`${this.id}, failed to scroll: ${e.message}`);
        }
    }
}

const StTextureCacheSkippingFileIcon = GObject.registerClass({
    Implements: [Gio.Icon],
}, class StTextureCacheSkippingFileIconImpl extends Gio.EmblemedIcon {
    _init(params) {
        // FIXME: We can't just inherit from Gio.FileIcon for some reason
        super._init({gicon: new Gio.FileIcon(params)});
    }

    vfunc_to_tokens() {
        // Disables the to_tokens() vfunc so that the icon to_string()
        // method won't work and thus can't be kept forever around by
        // StTextureCache, see the awesome debugging session in this thread:
        //   https://twitter.com/mild_sunrise/status/1458739604098621443
        // upstream bug is at:
        //   https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/4944
        return [false, [], 0];
    }
});

export const IconActor = GObject.registerClass(
class AppIndicatorsIconActor extends St.Icon {
    static get DEFAULT_STYLE() {
        return 'padding: 0';
    }

    static get USER_WRITABLE_PATHS() {
        if (!this._userWritablePaths) {
            this._userWritablePaths = [
                GLib.get_user_cache_dir(),
                GLib.get_user_data_dir(),
                GLib.get_user_config_dir(),
                GLib.get_user_runtime_dir(),
                GLib.get_home_dir(),
                GLib.get_tmp_dir(),
            ];

            this._userWritablePaths.push(Object.values(GLib.UserDirectory).slice(
                0, -1).map(dirId => GLib.get_user_special_dir(dirId)));
        }

        return this._userWritablePaths;
    }

    _init(indicator, iconSize) {
        super._init({
            reactive: true,
            style_class: 'system-status-icon',
            fallbackIconName: FALLBACK_ICON_NAME,
        });

        this.name = this.constructor.name;
        this.add_style_class_name('appindicator-icon');
        this.add_style_class_name('status-notifier-icon');
        this.set_style(AppIndicatorsIconActor.DEFAULT_STYLE);

        const themeContext = St.ThemeContext.get_for_stage(global.stage);
        this.height = iconSize * themeContext.scale_factor;

        this._indicator     = indicator;
        this._customIcons   = new Map();
        this._iconSize      = iconSize;
        this._iconCache     = new IconCache.IconCache();
        this._cancellable   = new Gio.Cancellable();
        this._loadingIcons  = Object.create(null);

        Object.values(SNIconType).forEach(t => (this._loadingIcons[t] = new Map()));

        Util.connectSmart(this._indicator, 'icon', this, () => {
            if (this.is_mapped())
                this._updateIcon();
        });
        Util.connectSmart(this._indicator, 'overlay-icon', this, () => {
            if (this.is_mapped())
                this._updateIcon();
        });
        Util.connectSmart(this._indicator, 'reset', this,
            () => this._invalidateIconWhenFullyReady());

        const settings = SettingsManager.getDefaultGSettings();
        Util.connectSmart(settings, 'changed::icon-size', this, () =>
            this._updateWhenFullyReady());
        Util.connectSmart(settings, 'changed::custom-icons', this, () => {
            this._updateCustomIcons();
            this._invalidateIconWhenFullyReady();
        });

        if (GObject.signal_lookup('resource-scale-changed', this))
            this.connect('resource-scale-changed', () => this._invalidateIcon());
        else
            this.connect('notify::resource-scale', () => this._invalidateIcon());

        Util.connectSmart(themeContext, 'notify::scale-factor', this, tc => {
            this._updateIconSize();
            this.height = this._iconSize * tc.scale_factor;
            this.width = -1;
            this._invalidateIcon();
        });

        Util.connectSmart(Util.getDefaultTheme(), 'changed', this,
            () => this._invalidateIconWhenFullyReady());

        this.connect('notify::mapped', () => {
            if (!this.is_mapped())
                this._updateWhenFullyReady();
        });

        this._updateWhenFullyReady();

        this.connect('destroy', () => {
            this._iconCache.destroy();
            this._cancellable.cancel();
            this._cancellable = null;
            this._indicator = null;
            this._loadingIcons = null;
            this._iconTheme = null;
        });
    }

    get debugId() {
        return this._indicator ? this._indicator.id : this.toString();
    }

    async _waitForFullyReady() {
        const waitConditions = [];

        if (!this.is_mapped()) {
            waitConditions.push(new PromiseUtils.SignalConnectionPromise(
                this, 'notify::mapped', this._cancellable));
        }

        if (!this._indicator.isReady) {
            waitConditions.push(new PromiseUtils.SignalConnectionPromise(
                this._indicator, 'ready', this._cancellable));
        }

        if (!waitConditions.length)
            return true;

        await Promise.all(waitConditions);
        return this._waitForFullyReady();
    }

    async _updateWhenFullyReady() {
        if (this._waitingReady)
            return;

        try {
            this._waitingReady = true;
            await this._waitForFullyReady();

            this._updateIconSize();
            this._updateIconClass();
            this._updateCustomIcons();
            this._invalidateIcon();
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        } finally {
            delete this._waitingReady;
        }
    }

    _updateIconClass() {
        if (!this._indicator)
            return;

        this.add_style_class_name(
            `appindicator-icon-${this._indicator.id.toLowerCase().replace(/_|\s/g, '-')}`);
    }

    _cancelLoadingByType(iconType) {
        this._loadingIcons[iconType].forEach(c => c.cancel());
        this._loadingIcons[iconType].clear();
    }

    _ensureNoIconIsLoading(iconType, id) {
        if (this._loadingIcons[iconType].has(id)) {
            Util.Logger.debug(`${this.debugId}, Icon ${id} Is still loading, ignoring the request`);
            throw new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING,
                'Already in progress');
        } else if (this._loadingIcons[iconType].size > 0) {
            throw new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS,
                'Another icon is already loading');
        }
    }

    _getIconLoadingCancellable(iconType, loadingId) {
        try {
            this._ensureNoIconIsLoading(iconType, loadingId);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
                throw e;
            this._cancelLoadingByType(iconType);
        }

        const cancellable = new Util.CancellableChild(this._cancellable);
        this._loadingIcons[iconType].set(loadingId, cancellable);

        return cancellable;
    }

    _cleanupIconLoadingCancellable(iconType, loadingId) {
        if (this._loadingIcons)
            this._loadingIcons[iconType].delete(loadingId);
    }

    _getResourceScale() {
        // Remove this when we remove support for versions earlier than 3.38
        const resourceScale = this.get_resource_scale();
        if (Array.isArray(resourceScale))
            return resourceScale[0] ? resourceScale[1] : 1.0;

        return resourceScale;
    }

    // Will look the icon up in the cache, if it's found
    // it will return it. Otherwise, it will create it and cache it.
    async _cacheOrCreateIconByName(iconType, iconSize, iconScaling, iconName, themePath) {
        const id = `${iconType}:${iconName}@${iconSize * iconScaling}:${themePath || ''}`;
        let gicon = this._iconCache.get(id);

        if (gicon)
            return gicon;

        const iconData = this._getIconData(iconName, themePath, iconSize, iconScaling);
        const loadingId = iconData.file ? iconData.file.get_path() : id;

        const cancellable = await this._getIconLoadingCancellable(iconType, id);
        try {
            gicon = await this._createIconByIconData(iconData, iconSize,
                iconScaling, cancellable);
        } finally {
            this._cleanupIconLoadingCancellable(iconType, loadingId);
        }
        if (gicon)
            gicon = this._iconCache.add(id, gicon);
        return gicon;
    }

    _getIconLookupFlags(themeNode) {
        let lookupFlags = 0;

        if (!themeNode)
            return lookupFlags;

        const lookupFlagsEnum = St.IconLookupFlags;
        const iconStyle = themeNode.get_icon_style();
        if (iconStyle === St.IconStyle.REGULAR)
            lookupFlags |= lookupFlagsEnum.FORCE_REGULAR;
        else if (iconStyle === St.IconStyle.SYMBOLIC)
            lookupFlags |= lookupFlagsEnum.FORCE_SYMBOLIC;

        if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
            lookupFlags |= lookupFlagsEnum.DIR_RTL;
        else
            lookupFlags |= lookupFlagsEnum.DIR_LTR;

        return lookupFlags;
    }

    async _createIconByIconData(iconData, iconSize, iconScaling, cancellable) {
        const {file, name} = iconData;

        if (!file && !name) {
            if (this._createIconIdle) {
                throw new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING,
                    'Already in progress');
            }

            try {
                this._createIconIdle = new PromiseUtils.IdlePromise(GLib.PRIORITY_DEFAULT_IDLE,
                    cancellable);
                await this._createIconIdle;
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                    logError(e);
                throw e;
            } finally {
                delete this._createIconIdle;
            }
            return this.gicon;
        } else if (this._createIconIdle) {
            this._createIconIdle.cancel();
            delete this._createIconIdle;
        }

        if (name)
            return new Gio.ThemedIcon({name});

        if (!file)
            throw new Error('Neither file or name are set');

        if (!this._isFileInWritableArea(file))
            return new Gio.FileIcon({file});

        try {
            const [format, width, height] = await GdkPixbuf.Pixbuf.get_file_info_async(
                file.get_path(), cancellable);

            if (!format) {
                Util.Logger.critical(`${this.debugId}, Invalid image format: ${file.get_path()}`);
                return null;
            }

            if (width >= height * 1.5) {
                /* Hello indicator-multiload! */
                await this._loadCustomImage(file,
                    width, height, iconSize, iconScaling, cancellable);
                return null;
            } else {
                /* We'll wrap the icon so that it won't be cached forever by the shell */
                return new StTextureCacheSkippingFileIcon({file});
            }
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                Util.Logger.warn(
                    `${this.debugId}, Impossible to read image info from ` +
                    `path '${file ? file.get_path() : null}' or name '${name}': ${e}`);
            }
            throw e;
        }
    }

    async _loadCustomImage(file, width, height, iconSize, iconScaling, cancellable) {
        const textureCache = St.TextureCache.get_default();
        const customImage = textureCache.load_file_async(file, -1,
            height, 1, iconScaling);

        const setCustomImageActor = imageActor => {
            const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
            const {content} = imageActor;
            imageActor.content = null;
            imageActor.destroy();

            this._setImageContent(content,
                width * scaleFactor, height * scaleFactor);
        };

        if (customImage.content) {
            setCustomImageActor(customImage);
            return;
        }

        const imageContentPromise = new PromiseUtils.SignalConnectionPromise(
            customImage, 'notify::content', cancellable);
        const waitPromise = new PromiseUtils.TimeoutSecondsPromise(
            1, GLib.PRIORITY_DEFAULT, cancellable);

        const racingPromises = [imageContentPromise, waitPromise];

        try {
            await Promise.race(racingPromises);
            if (!waitPromise.resolved())
                setCustomImageActor(customImage);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                throw e;
        } finally {
            racingPromises.forEach(p => p.cancel());
        }
    }

    _isFileInWritableArea(file) {
        // No need to use IO here, we can just do some assumptions
        // print('Writable paths', IconActor.USER_WRITABLE_PATHS)
        const path = file.get_path();
        return IconActor.USER_WRITABLE_PATHS.some(writablePath =>
            path.startsWith(writablePath));
    }

    _createIconTheme(searchPath = []) {
        const iconTheme = new St.IconTheme();
        iconTheme.set_search_path(searchPath);

        return iconTheme;
    }

    _getIconData(name, themePath, size, scale) {
        const emptyIconData = {iconInfo: null, file: null, name: null};

        if (!name) {
            delete this._iconTheme;
            return emptyIconData;
        }

        // HACK: icon is a path name. This is not specified by the API,
        // but at least indicator-sensors uses it.
        if (name[0] === '/') {
            delete this._iconTheme;

            const file = Gio.File.new_for_path(name);
            return {file, iconInfo: null, name: null};
        }

        if (name.includes('.')) {
            const splits = name.split('.');

            if (['svg', 'png'].includes(splits[splits.length - 1]))
                name = splits.slice(0, -1).join('');
        }

        if (themePath && Util.getDefaultTheme().get_search_path().includes(themePath))
            themePath = null;

        if (themePath) {
            // If a theme path is provided, we need to lookup the icon ourself
            // as St won't be able to do it unless we mess with default theme
            // that is something we prefer not to do, as it would imply lots of
            // St.TextureCache cleanups.

            const newSearchPath = [themePath];
            if (!this._iconTheme) {
                this._iconTheme = this._createIconTheme(newSearchPath);
            } else {
                const currentSearchPath = this._iconTheme.get_search_path();

                if (!currentSearchPath.includes(newSearchPath))
                    this._iconTheme.set_search_path(newSearchPath);
            }

            // try to look up the icon in the icon theme
            const iconInfo = this._iconTheme.lookup_icon_for_scale(`${name}`,
                size, scale, this._getIconLookupFlags(this.get_theme_node()) |
                St.IconLookupFlags.GENERIC_FALLBACK);

            if (iconInfo) {
                return {
                    iconInfo,
                    file: Gio.File.new_for_path(iconInfo.get_filename()),
                    name: null,
                };
            }

            const logger = this.gicon ? Util.Logger.debug : Util.Logger.warn;
            logger(`${this.debugId}, Impossible to lookup icon ` +
                `for '${name}' in ${themePath}`);

            return emptyIconData;
        }

        delete this._iconTheme;
        return {name, iconInfo: null, file: null};
    }

    _setImageContent(content, width, height) {
        this.set({
            content,
            width,
            height,
            contentGravity: Clutter.ContentGravity.RESIZE_ASPECT,
            fallbackIconName: null,
        });
    }

    async _createIconFromPixmap(iconType, iconSize, iconScaling, scaleFactor, pixmapsVariant) {
        const {pixmapVariant, width, height, rowStride} =
            PixmapsUtils.getBestPixmap(pixmapsVariant, iconSize * iconScaling);

        const id = `__PIXMAP_ICON_${width}x${height}`;

        const imageContent = new St.ImageContent({
            preferredWidth: width,
            preferredHeight: height,
        });

        const setBytesArgs = [pixmapVariant.get_data_as_bytes(), PIXMAPS_FORMAT, width, height, rowStride];
        if (GNOME_48_PLUS) {
            setBytesArgs.unshift(global.stage.context.get_backend().get_cogl_context());
        }
        imageContent.set_bytes(...setBytesArgs);

        if (iconType !== SNIconType.OVERLAY && !this._indicator.hasOverlayIcon) {
            const scaledSize = iconSize * scaleFactor;
            this._setImageContent(imageContent, scaledSize, scaledSize);
            return null;
        }

        const cancellable = this._getIconLoadingCancellable(iconType, id);
        try {
            // FIXME: async API results in a gray icon for some reason
            const [inputStream] = imageContent.load(iconSize, cancellable);
            return await GdkPixbuf.Pixbuf.new_from_stream_at_scale_async(
                inputStream, -1, iconSize * iconScaling, true, cancellable);
        } catch (e) {
            // the image data was probably bogus. We don't really know why, but it _does_ happen.
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                Util.Logger.warn(`${this.debugId}, Impossible to create image from data: ${e}`);
            throw e;
        } finally {
            this._cleanupIconLoadingCancellable(iconType, id);
        }
    }

    // The icon cache Active flag will be set to true if the used gicon matches
    // the cached one (as in some cases it may be equal, but not the same object).
    // So when it's not need anymore we make sure to check the active state
    // and set it to false so that it can be picked up by the garbage collector.
    _setGicon(iconType, gicon) {
        if (iconType !== SNIconType.OVERLAY) {
            if (gicon) {
                if (this.gicon === gicon ||
                    (this.gicon && this.gicon.get_icon() === gicon))
                    return;

                if (gicon instanceof Gio.EmblemedIcon)
                    this.gicon = gicon;
                else
                    this.gicon = new Gio.EmblemedIcon({gicon});

                this._iconCache.updateActive(SNIconType.NORMAL, gicon,
                    this.gicon.get_icon() === gicon);
            } else {
                this.gicon = null;
            }
        } else if (gicon) {
            this._emblem = new Gio.Emblem({icon: gicon});
            this._iconCache.updateActive(iconType, gicon, true);
        } else {
            this._emblem = null;
        }

        if (this.gicon) {
            if (!this.gicon.get_emblems().some(e => e.equal(this._emblem))) {
                this.gicon.clear_emblems();
                if (this._emblem)
                    this.gicon.add_emblem(this._emblem);
            }
        }
    }

    async _updateIconByType(iconType, iconSize) {
        let icon;
        switch (iconType) {
        case SNIconType.ATTENTION:
            icon = this._indicator.attentionIcon;
            break;
        case SNIconType.NORMAL:
            ({icon} = this._indicator);
            break;
        case SNIconType.OVERLAY:
            icon = this._indicator.overlayIcon;
            break;
        }

        const {theme, name, pixmap} = icon;
        const commonArgs = [theme, iconType, iconSize];

        if (this._customIcons.size) {
            let customIcon = this._customIcons.get(iconType);
            if (!await this._createAndSetIcon(customIcon, null, ...commonArgs)) {
                if (iconType !== SNIconType.OVERLAY) {
                    customIcon = this._customIcons.get(SNIconType.NORMAL);
                    await this._createAndSetIcon(customIcon, null, ...commonArgs);
                }
            }
        } else {
            await this._createAndSetIcon(name, pixmap, ...commonArgs);
        }
    }

    async _createAndSetIcon(name, pixmap, theme, iconType, iconSize) {
        let gicon = null;

        try {
            gicon = await this._createIcon(name, pixmap, theme, iconType, iconSize);
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED) ||
                e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING))
                return null;

            if (iconType === SNIconType.OVERLAY) {
                logError(e, `${this.debugId} unable to update icon emblem`);
            } else {
                this.fallbackIconName = FALLBACK_ICON_NAME;
                logError(e, `${this.debugId} unable to update icon`);
            }
        }

        try {
            this._setGicon(iconType, gicon);

            if (pixmap && this.gicon) {
                // The pixmap has been saved, we can free the variants memory
                this._indicator.invalidatePixmapProperty(iconType);
            }

            return gicon;
        } catch (e) {
            logError(e, 'Setting GIcon failed');
            return null;
        }
    }

    // updates the base icon
    async _createIcon(name, pixmap, theme, iconType, iconSize) {
        const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
        const resourceScale = this._getResourceScale();
        const iconScaling = Math.ceil(resourceScale * scaleFactor);

        // From now on we consider them the same thing, as one replaces the other
        if (iconType === SNIconType.ATTENTION)
            iconType = SNIconType.NORMAL;

        if (name) {
            const gicon = await this._cacheOrCreateIconByName(
                iconType, iconSize, iconScaling, name, theme);
            if (gicon)
                return gicon;
        }

        if (pixmap && pixmap.n_children()) {
            return this._createIconFromPixmap(iconType,
                iconSize, iconScaling, scaleFactor, pixmap);
        }

        return null;
    }

    // updates the base icon
    async _updateIcon() {
        if (this._indicator.status === SNIStatus.PASSIVE)
            return;

        if (this.gicon instanceof Gio.EmblemedIcon) {
            const {gicon} = this.gicon;
            this._iconCache.updateActive(SNIconType.NORMAL, gicon, false);
        }

        // we might need to use the AttentionIcon*, which have precedence over the normal icons
        const iconType = this._indicator.status === SNIStatus.NEEDS_ATTENTION
            ? SNIconType.ATTENTION : SNIconType.NORMAL;

        try {
            await this._updateIconByType(iconType, this._iconSize);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED) &&
                !e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING))
                logError(e, `${this.debugId}: Updating icon type ${iconType} failed`);
        }
    }

    async _updateOverlayIcon() {
        if (this._indicator.status === SNIStatus.PASSIVE)
            return;

        if (this._emblem) {
            const {icon} = this._emblem;
            this._iconCache.updateActive(SNIconType.OVERLAY, icon, false);
        }

        // KDE hardcodes the overlay icon size to 10px (normal icon size 16px)
        // we approximate that ratio for other sizes, too.
        // our algorithms will always pick a smaller one instead of stretching it.
        const iconSize = Math.floor(this._iconSize / 1.6);

        try {
            await this._updateIconByType(SNIconType.OVERLAY, iconSize);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED) &&
                !e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING))
                logError(e, `${this.debugId}: Updating overlay icon failed`);
        }
    }

    async _invalidateIconWhenFullyReady() {
        if (this._waitingInvalidation)
            return;

        try {
            this._waitingInvalidation = true;
            await this._waitForFullyReady();
            this._invalidateIcon();
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        } finally {
            delete this._waitingInvalidation;
        }
    }

    // called when the icon theme changes
    _invalidateIcon() {
        this._iconCache.clear();
        this._cancellable.cancel();
        this._cancellable = new Gio.Cancellable();
        Object.values(SNIconType).forEach(iconType =>
            this._loadingIcons[iconType].clear());

        this._updateIcon().catch(e => logError(e));
        this._updateOverlayIcon().catch(e => logError(e));
    }

    _updateIconSize() {
        const settings = SettingsManager.getDefaultGSettings();
        const sizeValue = settings.get_int('icon-size');

        if (sizeValue > 0) {
            if (!this._defaultIconSize)
                this._defaultIconSize = this._iconSize;

            this._iconSize = sizeValue;
        } else if (this._defaultIconSize) {
            this._iconSize = this._defaultIconSize;
            delete this._defaultIconSize;
        }

        const themeIconSize = Math.round(
            this.get_theme_node().get_length('icon-size'));
        let iconStyle = AppIndicatorsIconActor.DEFAULT_STYLE;

        if (themeIconSize > 0) {
            const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);

            if (themeIconSize / scaleFactor !== this._iconSize) {
                iconStyle = `${AppIndicatorsIconActor.DEFAULT_STYLE};` +
                    'icon-size: 0';
            }
        }

        this.set_style(iconStyle);
        this.set_icon_size(this._iconSize);
    }

    _updateCustomIcons() {
        const settings = SettingsManager.getDefaultGSettings();
        this._customIcons.clear();

        settings.get_value('custom-icons').deep_unpack().forEach(customIcons => {
            const [indicatorId, normalIcon, attentionIcon] = customIcons;
            if (this._indicator.id === indicatorId) {
                this._customIcons.set(SNIconType.NORMAL, normalIcon);
                this._customIcons.set(SNIconType.ATTENTION, attentionIcon);
            }
        });
    }
});

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