diff --git a/data/theme/gnome-shell-high-contrast.css b/data/theme/gnome-shell-high-contrast.css index 47c3090f7..c27717d88 100644 --- a/data/theme/gnome-shell-high-contrast.css +++ b/data/theme/gnome-shell-high-contrast.css @@ -399,6 +399,34 @@ StScrollBar { width: 48px; height: 48px; } +/* Audio selection dialog */ +.audio-device-selection-dialog { + spacing: 30px; } + +.audio-selection-content { + spacing: 20px; + padding: 24px; } + +.audio-selection-title { + font-weight: bold; + text-align: center; } + +.audio-selection-box { + spacing: 20px; } + +.audio-selection-device { + border: 1px solid rgba(238, 238, 236, 0.2); + border-radius: 12px; } + .audio-selection-device:active, .audio-selection-device:hover, .audio-selection-device:focus { + background-color: #215d9c; } + +.audio-selection-device-box { + padding: 20px; + spacing: 20px; } + +.audio-selection-device-icon { + icon-size: 64px; } + /* Network Agent Dialog */ .network-dialog-secret-table { spacing-rows: 15px; diff --git a/data/theme/gnome-shell-sass b/data/theme/gnome-shell-sass index c67499686..9fb391883 160000 --- a/data/theme/gnome-shell-sass +++ b/data/theme/gnome-shell-sass @@ -1 +1 @@ -Subproject commit c67499686eb40b821784561fbb0596cc470d8017 +Subproject commit 9fb3918831459cd002f3d621494cf5eac70fe46a diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 0c94b1978..2e09c1c65 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -399,6 +399,34 @@ StScrollBar { width: 48px; height: 48px; } +/* Audio selection dialog */ +.audio-device-selection-dialog { + spacing: 30px; } + +.audio-selection-content { + spacing: 20px; + padding: 24px; } + +.audio-selection-title { + font-weight: bold; + text-align: center; } + +.audio-selection-box { + spacing: 20px; } + +.audio-selection-device { + border: 1px solid rgba(238, 238, 236, 0.2); + border-radius: 12px; } + .audio-selection-device:active, .audio-selection-device:hover, .audio-selection-device:focus { + background-color: #215d9c; } + +.audio-selection-device-box { + padding: 20px; + spacing: 20px; } + +.audio-selection-device-icon { + icon-size: 64px; } + /* Network Agent Dialog */ .network-dialog-secret-table { spacing-rows: 15px; diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index bd8f30708..169d952db 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -35,6 +35,7 @@ ui/animation.js ui/appDisplay.js ui/appFavorites.js + ui/audioDeviceSelection.js ui/backgroundMenu.js ui/background.js ui/boxpointer.js diff --git a/js/ui/audioDeviceSelection.js b/js/ui/audioDeviceSelection.js new file mode 100644 index 000000000..c97184de9 --- /dev/null +++ b/js/ui/audioDeviceSelection.js @@ -0,0 +1,216 @@ +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); + } +}); diff --git a/js/ui/main.js b/js/ui/main.js index 377040342..ad95ce0d4 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -11,6 +11,7 @@ const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; +const AudioDeviceSelection = imports.ui.audioDeviceSelection; const Components = imports.ui.components; const CtrlAltTab = imports.ui.ctrlAltTab; const EndSessionDialog = imports.ui.endSessionDialog; @@ -62,6 +63,7 @@ let ctrlAltTabManager = null; let osdWindowManager = null; let osdMonitorLabeler = null; let sessionMode = null; +let shellAudioSelectionDBusService = null; let shellDBusService = null; let shellMountOpDBusService = null; let screenSaverDBus = null; @@ -120,6 +122,7 @@ function start() { _loadDefaultStylesheet); _initializeUI(); + shellAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus(); shellDBusService = new ShellDBus.GnomeShell(); shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();