Migrating to gnome 45 - import errors (original) (raw)

February 13, 2024, 10:03am 1

SyntaxError: ambiguous indirect export: GObject @ file:///home/bernhard/.local/share/gnome-shell/extensions/audio-switcher@ahoi.io/extension.js:1:9

import { GObject } from 'gi://GObject';
import { PopupMenu }  from  'resource:///org/gnome/shell/ui/popupMenu.js';
import { Extension as GExtension } from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';

const AudioOutputSubMenu = GObject.registerClass({
    GTypeName: 'ASAudioOutputSubMenu',
}, class AudioOutputSubMenu extends PopupMenu.PopupSubMenuMenuItem {
    _init() {
        super._init('Audio Output: Connecting...', true);

        this._control = Main.panel.statusArea.aggregateMenu._volume._control;

        this._controlSignal = this._control.connect('default-sink-changed', () => {
            this._updateDefaultSink();
        });

        this._updateDefaultSink();

        this.menu.connect('open-state-changed', (menu, isOpen) => {
            if (isOpen)
                this._updateSinkList();
        });

        //Unless there is at least one item here, no 'open' will be emitted...
        let item = new PopupMenu.PopupMenuItem('Connecting...');
        this.menu.addMenuItem(item);
    }

    _updateDefaultSink() {
        let defsink = this._control.get_default_sink();
        //Unfortunately, Gvc neglects some pulse-devices, such as all "Monitor of ..."
        if (defsink == null)
            this.label.set_text("Other");
        else
            this.label.set_text(defsink.get_description());
    }

    _updateSinkList() {
        this.menu.removeAll();

        let defsink = this._control.get_default_sink();
        let sinklist = this._control.get_sinks();
        let control = this._control;
        let item;

        for (let i=0; i<sinklist.length; i++) {
            let sink = sinklist[i];
            if (sink === defsink)
                continue;
            item = new PopupMenu.PopupMenuItem(sink.get_description());
            item.connect('activate', () => {
                control.set_default_sink(sink);
            });
            this.menu.addMenuItem(item);
        }
        if (sinklist.length == 0 ||
            (sinklist.length == 1 && sinklist[0] === defsink)) {
            item = new PopupMenu.PopupMenuItem("No more Devices");
            this.menu.addMenuItem(item);
        }
    }

    destroy() {
        this._control.disconnect(this._controlSignal);
        super.destroy();
    }
});

const AudioInputSubMenu = GObject.registerClass({
    GTypeName: 'ASAudioInputSubMenu',
}, class AudioInputSubMenu extends PopupMenu.PopupSubMenuMenuItem {
    _init() {
        super._init('Audio Input: Connecting...', true);

        this._control = Main.panel.statusArea.aggregateMenu._volume._control;


        this._controlSignal = this._control.connect('default-source-changed', () => {
            this._updateDefaultSource();
        });

        this._updateDefaultSource();

        this.menu.connect('open-state-changed', (menu, isOpen) => {
            if (isOpen)
                this._updateSourceList();
        });

        //Unless there is at least one item here, no 'open' will be emitted...
        let item = new PopupMenu.PopupMenuItem('Connecting...');
        this.menu.addMenuItem(item);
    }

    _updateDefaultSource() {
        let defsource = this._control.get_default_source();
        //Unfortunately, Gvc neglects some pulse-devices, such as all "Monitor of ..."
        if (defsource == null)
            this.label.set_text("Other");
        else
            this.label.set_text(defsource.get_description());
    }

    _updateSourceList() {
        this.menu.removeAll();

        let defsource = this._control.get_default_source();
        let sourcelist = this._control.get_sources();
        let control = this._control;
        let item;

        for (var i = 0; i < sourcelist.length; i++) {
            let source = sourcelist[i];
            if (source === defsource) {
                continue;
            }
            item = new PopupMenu.PopupMenuItem(source.get_description());
            item.connect('activate', () => {
                control.set_default_source(source);
            });
            this.menu.addMenuItem(item);
        }
        if (sourcelist.length == 0 ||
            (sourcelist.length == 1 && sourcelist[0] === defsource)) {
            item = new PopupMenu.PopupMenuItem("No more Devices");
            this.menu.addMenuItem(item);
        }
    }

    destroy() {
        this._control.disconnect(this._controlSignal);
        super.destroy();
    }
});


export default class AudioSwitcherExtension extends GExtension {
    constructor(metadata) {
        super(metadata)

        this.audioOutputSubMenu = null;
        this.audioInputSubMenu = null;
        this.savedUpdateVisibility = null;
    }
    enable() {
        if ((audioInputSubMenu != null) || (audioOutputSubMenu != null))
            return;
        this.audioInputSubMenu = new AudioInputSubMenu();
        this.audioOutputSubMenu = new AudioOutputSubMenu();

        //Try to add the switchers right below the sliders...
        let volMen = Main.panel.statusArea.aggregateMenu._volume._volumeMenu;
        let items = volMen._getMenuItems();
        let i = 0;
        let addedInput, addedOutput = false;
        while (i < items.length){
            if (items[i] === volMen._output.item){
                volMen.addMenuItem(audioOutputSubMenu, i+1);
                addedOutput = true;
            } else if (items[i] === volMen._input.item){
                volMen.addMenuItem(audioInputSubMenu, i+2);
                addedInput = true;
            }
            if (addedOutput && addedInput){
                break;
            }
            i++;
        }

        //Make input-slider allways visible.
        this.savedUpdateVisibility = Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility;
        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility = function () {};
        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input.item.actor.visible = true;
    }
    disable() {
        this.audioInputSubMenu.destroy();
        this.audioInputSubMenu = null;
        this.audioOutputSubMenu.destroy();
        this.audioOutputSubMenu = null;

        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility = savedUpdateVisibility;
        this.savedUpdateVisibility = null;
        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility();
    }
}

The full plugin can be found GitHub - drahnr/audio-switcher: Easily switch between your audio inputs/outputs from the system menu in GNOME 42

Note that i.e. caffeine does the same GObject import and works.

Port Extensions to GNOME Shell 45 | GNOME JavaScript doesn’t help much.

I’d appreciate any help!

jrahmatzadeh (Javad Rahmatzadeh) February 13, 2024, 10:23am 2

You need to drop those {} in line 1 and 2:

import GObject from 'gi://GObject';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';

drahnr February 15, 2024, 2:39pm 3

Hey, yes, that got me one step further.

Note that Anatomy of an Extension | GNOME JavaScript appears to suggest to do what I had initially, so if you could sheld some light on why it doesn’t work, that’d be much appreciated.

The next error message is

TypeError: Extension is not a constructor

get from

import GObject from 'gi://GObject';
import * as PopupMenu  from  'resource:///org/gnome/shell/ui/popupMenu.js';
import * as Extension from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';

const AudioOutputSubMenu = GObject.registerClass({
    GTypeName: 'ASAudioOutputSubMenu',
}, class AudioOutputSubMenu extends PopupMenu.PopupSubMenuMenuItem {
    _init() {
        super._init('Audio Output: Connecting...', true);

        this._control = Main.panel.statusArea.aggregateMenu._volume._control;

        this._controlSignal = this._control.connect('default-sink-changed', () => {
            this._updateDefaultSink();
        });

        this._updateDefaultSink();

        this.menu.connect('open-state-changed', (menu, isOpen) => {
            if (isOpen)
                this._updateSinkList();
        });

        //Unless there is at least one item here, no 'open' will be emitted...
        let item = new PopupMenu.PopupMenuItem('Connecting...');
        this.menu.addMenuItem(item);
    }

    _updateDefaultSink() {
        let defsink = this._control.get_default_sink();
        //Unfortunately, Gvc neglects some pulse-devices, such as all "Monitor of ..."
        if (defsink == null)
            this.label.set_text("Other");
        else
            this.label.set_text(defsink.get_description());
    }

    _updateSinkList() {
        this.menu.removeAll();

        let defsink = this._control.get_default_sink();
        let sinklist = this._control.get_sinks();
        let control = this._control;
        let item;

        for (let i=0; i<sinklist.length; i++) {
            let sink = sinklist[i];
            if (sink === defsink)
                continue;
            item = new PopupMenu.PopupMenuItem(sink.get_description());
            item.connect('activate', () => {
                control.set_default_sink(sink);
            });
            this.menu.addMenuItem(item);
        }
        if (sinklist.length == 0 ||
            (sinklist.length == 1 && sinklist[0] === defsink)) {
            item = new PopupMenu.PopupMenuItem("No more Devices");
            this.menu.addMenuItem(item);
        }
    }

    destroy() {
        this._control.disconnect(this._controlSignal);
        super.destroy();
    }
});

const AudioInputSubMenu = GObject.registerClass({
    GTypeName: 'ASAudioInputSubMenu',
}, class AudioInputSubMenu extends PopupMenu.PopupSubMenuMenuItem {
    _init() {
        super._init('Audio Input: Connecting...', true);

        this._control = Main.panel.statusArea.aggregateMenu._volume._control;


        this._controlSignal = this._control.connect('default-source-changed', () => {
            this._updateDefaultSource();
        });

        this._updateDefaultSource();

        this.menu.connect('open-state-changed', (menu, isOpen) => {
            if (isOpen)
                this._updateSourceList();
        });

        //Unless there is at least one item here, no 'open' will be emitted...
        let item = new PopupMenu.PopupMenuItem('Connecting...');
        this.menu.addMenuItem(item);
    }

    _updateDefaultSource() {
        let defsource = this._control.get_default_source();
        //Unfortunately, Gvc neglects some pulse-devices, such as all "Monitor of ..."
        if (defsource == null)
            this.label.set_text("Other");
        else
            this.label.set_text(defsource.get_description());
    }

    _updateSourceList() {
        this.menu.removeAll();

        let defsource = this._control.get_default_source();
        let sourcelist = this._control.get_sources();
        let control = this._control;
        let item;

        for (var i = 0; i < sourcelist.length; i++) {
            let source = sourcelist[i];
            if (source === defsource) {
                continue;
            }
            item = new PopupMenu.PopupMenuItem(source.get_description());
            item.connect('activate', () => {
                control.set_default_source(source);
            });
            this.menu.addMenuItem(item);
        }
        if (sourcelist.length == 0 ||
            (sourcelist.length == 1 && sourcelist[0] === defsource)) {
            item = new PopupMenu.PopupMenuItem("No more Devices");
            this.menu.addMenuItem(item);
        }
    }

    destroy() {
        this._control.disconnect(this._controlSignal);
        super.destroy();
    }
});


export default class AudioSwitcherExtension extends Extension {
    audioOutputSubMenu = null;
    audioInputSubMenu = null;
    savedUpdateVisibility = null;

    // constructor(metadata) {
        // super(metadata)
// 
        // this.audioOutputSubMenu = null;
        // this.audioInputSubMenu = null;
        // this.savedUpdateVisibility = null;
    // }
    enable() {
        if ((this.audioInputSubMenu != null) || (this.audioOutputSubMenu != null))
            return;
        this.audioInputSubMenu = new AudioInputSubMenu();
        this.audioOutputSubMenu = new AudioOutputSubMenu();

        //Try to add the switchers right below the sliders...
        let volMen = Main.panel.statusArea.aggregateMenu._volume._volumeMenu;
        let items = volMen._getMenuItems();
        let i = 0;
        let addedInput, addedOutput = false;
        while (i < items.length){
            if (items[i] === volMen._output.item){
                volMen.addMenuItem(audioOutputSubMenu, i+1);
                addedOutput = true;
            } else if (items[i] === volMen._input.item){
                volMen.addMenuItem(audioInputSubMenu, i+2);
                addedInput = true;
            }
            if (addedOutput && addedInput){
                break;
            }
            i++;
        }

        //Make input-slider allways visible.
        this.savedUpdateVisibility = Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility;
        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility = function () {};
        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input.item.actor.visible = true;
    }
    disable() {
        this.audioInputSubMenu.destroy();
        this.audioInputSubMenu = null;
        this.audioOutputSubMenu.destroy();
        this.audioOutputSubMenu = null;

        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility = savedUpdateVisibility;
        this.savedUpdateVisibility = null;
        Main.panel.statusArea.aggregateMenu._volume._volumeMenu._input._updateVisibility();
    }
}

regardless if I comment constructor(metdata) {..} or not. Could you shed some light on that too.

Thank you very much for your time and help!

jrahmatzadeh (Javad Rahmatzadeh) February 15, 2024, 3:58pm 4

As I mentioned before, that change is only related to the line 1 and 2 in your first code.

drahnr February 15, 2024, 4:51pm 5

That doesn’t work, if I only apply your suggested changes to line 1 and 2, I get:

SyntaxError: ambiguous indirect export: default @ file:///home/bernhard/.local/share/gnome-shell/extensions/audio-switcher@ahoi.io/extension.js:3:7

Edith: tried again, now the only thing left to figure is how to replace the removed aggregate member on the panel.

Thanks again!

system (system) Closed March 16, 2024, 4:52pm 6

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.