const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; const AudioDevice = { HEADPHONES: 1 << 0, HEADSET: 1 << 1, MICROPHONE: 1 << 2 }; const AudioDeviceSelectionIface = ' \ \ \ \ \ \ \ \ \ \ \ '; const AudioDeviceSelectionDialog = new Lang.Class({ Name: 'AudioDeviceSelectionDialog', Extends: ModalDialog.ModalDialog, _init: function(devices) { this.parent({ 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: function() { this.parent(); }, _buildLayout: function(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 }); this.addButton({ action: Lang.bind(this, this._openSettings), label: _("Sound Settings") }); this.addButton({ action: Lang.bind(this, this.close), label: _("Cancel"), key: Clutter.Escape }); }, _getDeviceLabel: function(device) { switch(device) { case AudioDevice.HEADPHONES: return _("Headphones"); case AudioDevice.HEADSET: return _("Headset"); case AudioDevice.MICROPHONE: return _("Microphone"); default: return null; } }, _getDeviceIcon: function(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: function(device) { let box = new St.BoxLayout({ style_class: 'audio-selection-device-box', vertical: true }); box.connect('notify::height', function() { Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function() { 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', Lang.bind(this, function() { this.emit('device-selected', device); this.close(); Main.overview.hide(); })); }, _openSettings: function() { 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(); } }); const AudioDeviceSelectionDBus = new Lang.Class({ Name: 'AudioDeviceSelectionDBus', _init: function() { 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: function() { this._audioSelectionDialog = null; }, _onDeviceSelected: function(dialog, device) { let connection = this._dbusImpl.get_connection(); let info = this._dbusImpl.get_info(); let deviceName = Object.keys(AudioDevice).filter( function(dev) { return 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: function(params, invocation) { if (this._audioSelectionDialog) { invocation.return_value(null); return; } let [deviceNames] = params; let devices = 0; deviceNames.forEach(function(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', Lang.bind(this, this._onDialogClosed)); dialog.connect('device-selected', Lang.bind(this, this._onDeviceSelected)); dialog.open(); this._audioSelectionDialog = dialog; invocation.return_value(null); }, CloseAsync: function(params, invocation) { if (this._audioSelectionDialog && this._audioSelectionDialog._sender == invocation.get_sender()) this._audioSelectionDialog.close(); invocation.return_value(null); } });