Blob Blame History Raw
const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;

const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;

const { loadInterfaceXML } = imports.misc.fileUtils;

var AudioDevice = {
    HEADPHONES: 1 << 0,
    HEADSET:    1 << 1,
    MICROPHONE: 1 << 2
};

const AudioDeviceSelectionIface = loadInterfaceXML('org.gnome.Shell.AudioDeviceSelection');

var AudioDeviceSelectionDialog =
class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog {
    constructor(devices) {
        super({ styleClass: 'audio-device-selection-dialog' });

        this._deviceItems = {};

        this._buildLayout();

        if (devices & AudioDevice.HEADPHONES)
            this._addDevice(AudioDevice.HEADPHONES);
        if (devices & AudioDevice.HEADSET)
            this._addDevice(AudioDevice.HEADSET);
        if (devices & AudioDevice.MICROPHONE)
            this._addDevice(AudioDevice.MICROPHONE);

        if (this._selectionBox.get_n_children() < 2)
            throw new Error('Too few devices for a selection');
    }

    destroy() {
        super.destroy();
    }

    _buildLayout(devices) {
        let title = new St.Label({ style_class: 'audio-selection-title',
                                   text: _("Select Audio Device"),
                                   x_align: Clutter.ActorAlign.CENTER });

        this.contentLayout.style_class = 'audio-selection-content';
        this.contentLayout.add(title);

        this._selectionBox = new St.BoxLayout({ style_class: 'audio-selection-box' });
        this.contentLayout.add(this._selectionBox, { expand: true });

        if (Main.sessionMode.allowSettings)
            this.addButton({ action: this._openSettings.bind(this),
                             label: _("Sound Settings") });
        this.addButton({ action: this.close.bind(this),
                         label: _("Cancel"),
                         key: Clutter.Escape });
    }

    _getDeviceLabel(device) {
        switch(device) {
            case AudioDevice.HEADPHONES:
                return _("Headphones");
            case AudioDevice.HEADSET:
                return _("Headset");
            case AudioDevice.MICROPHONE:
                return _("Microphone");
            default:
                return null;
        }
    }

    _getDeviceIcon(device) {
        switch(device) {
            case AudioDevice.HEADPHONES:
                return 'audio-headphones-symbolic';
            case AudioDevice.HEADSET:
                return 'audio-headset-symbolic';
            case AudioDevice.MICROPHONE:
                return 'audio-input-microphone-symbolic';
            default:
                return null;
        }
    }

    _addDevice(device) {
        let box = new St.BoxLayout({ style_class: 'audio-selection-device-box',
                                     vertical: true });
        box.connect('notify::height', () => {
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                box.width = box.height;
            });
        });

        let icon = new St.Icon({ style_class: 'audio-selection-device-icon',
                                 icon_name: this._getDeviceIcon(device) });
        box.add(icon);

        let label = new St.Label({ style_class: 'audio-selection-device-label',
                                   text: this._getDeviceLabel(device),
                                   x_align: Clutter.ActorAlign.CENTER });
        box.add(label);

        let button = new St.Button({ style_class: 'audio-selection-device',
                                     can_focus: true,
                                     child: box });
        this._selectionBox.add(button);

        button.connect('clicked', () => {
            this.emit('device-selected', device);
            this.close();
            Main.overview.hide();
        });
    }

    _openSettings() {
        let desktopFile = 'gnome-sound-panel.desktop'
        let app = Shell.AppSystem.get_default().lookup_app(desktopFile);

        if (!app) {
            log('Settings panel for desktop file ' + desktopFile + ' could not be loaded!');
            return;
        }

        this.close();
        Main.overview.hide();
        app.activate();
    }
};

var AudioDeviceSelectionDBus = class AudioDeviceSelectionDBus {
    constructor() {
        this._audioSelectionDialog = null;

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(AudioDeviceSelectionIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/AudioDeviceSelection');

        Gio.DBus.session.own_name('org.gnome.Shell.AudioDeviceSelection', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    _onDialogClosed() {
        this._audioSelectionDialog = null;
    }

    _onDeviceSelected(dialog, device) {
        let connection = this._dbusImpl.get_connection();
        let info = this._dbusImpl.get_info();
        let deviceName = Object.keys(AudioDevice).filter(
            dev => AudioDevice[dev] == device
        )[0].toLowerCase();
        connection.emit_signal(this._audioSelectionDialog._sender,
                               this._dbusImpl.get_object_path(),
                               info ? info.name : null,
                               'DeviceSelected',
                               GLib.Variant.new('(s)', [deviceName]));
    }

    OpenAsync(params, invocation) {
        if (this._audioSelectionDialog) {
            invocation.return_value(null);
            return;
        }

        let [deviceNames] = params;
        let devices = 0;
        deviceNames.forEach(n => { devices |= AudioDevice[n.toUpperCase()]; });

        let dialog;
        try {
            dialog = new AudioDeviceSelectionDialog(devices);
        } catch(e) {
            invocation.return_value(null);
            return;
        }
        dialog._sender = invocation.get_sender();

        dialog.connect('closed', this._onDialogClosed.bind(this));
        dialog.connect('device-selected',
                       this._onDeviceSelected.bind(this));
        dialog.open();

        this._audioSelectionDialog = dialog;
        invocation.return_value(null);
    }

    CloseAsync(params, invocation) {
        if (this._audioSelectionDialog &&
            this._audioSelectionDialog._sender == invocation.get_sender())
            this._audioSelectionDialog.close();

        invocation.return_value(null);
    }
};