diff options
Diffstat (limited to 'js/ui/audioDeviceSelection.js')
-rw-r--r-- | js/ui/audioDeviceSelection.js | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/js/ui/audioDeviceSelection.js b/js/ui/audioDeviceSelection.js new file mode 100644 index 0000000..e284772 --- /dev/null +++ b/js/ui/audioDeviceSelection.js @@ -0,0 +1,207 @@ +/* exported AudioDeviceSelectionDBus */ +const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; + +const Dialog = imports.ui.dialog; +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 = GObject.registerClass({ + Signals: { 'device-selected': { param_types: [GObject.TYPE_UINT] } }, +}, class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog { + _init(devices) { + super._init({ 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'); + } + + _buildLayout() { + let content = new Dialog.MessageDialogContent({ + title: _('Select Audio Device'), + }); + + this._selectionBox = new St.BoxLayout({ + style_class: 'audio-selection-box', + x_align: Clutter.ActorAlign.CENTER, + x_expand: true, + }); + content.add_child(this._selectionBox); + + this.contentLayout.add_child(content); + + if (Main.sessionMode.allowSettings) { + this.addButton({ + action: this._openSettings.bind(this), + label: _('Sound Settings'), + }); + } + this.addButton({ + action: () => this.close(), + label: _('Cancel'), + key: Clutter.KEY_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) { + const 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; + return GLib.SOURCE_REMOVE; + }); + }); + + const icon = new St.Icon({ + style_class: 'audio-selection-device-icon', + icon_name: this._getDeviceIcon(device), + }); + box.add(icon); + + const label = new St.Label({ + style_class: 'audio-selection-device-label', + text: this._getDeviceLabel(device), + x_align: Clutter.ActorAlign.CENTER, + }); + box.add(label); + + const 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(); + const 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); + } +}; |