Skip to content

Commit 3724d7a

Browse files
authored
Add a clutter audio device selection dialog (#12461)
Requires: linuxmint/cinnamon-settings-daemon#401
1 parent cbf69f7 commit 3724d7a

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

data/theme/cinnamon-sass/widgets/_dialogs.scss

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,25 @@
115115

116116
&-user-root-label { color: $error_color; }
117117
}
118+
119+
// Audio selection dialog
120+
121+
.audio-device-selection-dialog {
122+
min-width: 24em;
123+
124+
.audio-selection-box {
125+
spacing: $base_padding *2;
126+
127+
.audio-selection-device {
128+
@extend %flat_button;
129+
border-radius: $base_border_radius;
130+
131+
.audio-selection-device-box {
132+
padding: $base_padding * 2;
133+
spacing: $base_padding * 2;
134+
}
135+
136+
.audio-selection-device-icon { icon-size: 64px;}
137+
}
138+
}
139+
}

js/ui/audioDeviceSelection.js

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
const Cinnamon = imports.gi.Cinnamon;
2+
const Clutter = imports.gi.Clutter;
3+
const Gio = imports.gi.Gio;
4+
const GLib = imports.gi.GLib;
5+
const GObject = imports.gi.GObject;
6+
const Meta = imports.gi.Meta;
7+
const St = imports.gi.St;
8+
9+
const Dialog = imports.ui.dialog;
10+
const Main = imports.ui.main;
11+
const ModalDialog = imports.ui.modalDialog;
12+
13+
const Util = imports.misc.util;
14+
15+
const AudioDeviceSelectionIface = `
16+
<node>
17+
<interface name="org.Cinnamon.AudioDeviceSelection">
18+
<method name="Open">
19+
<arg name="devices" direction="in" type="as" />
20+
</method>
21+
<method name="Close">
22+
</method>
23+
<signal name="DeviceSelected">
24+
<arg name="device" type="s" />
25+
</signal>
26+
</interface>
27+
</node>`;
28+
29+
const AudioDevice = {
30+
HEADPHONES: 1 << 0,
31+
HEADSET: 1 << 1,
32+
MICROPHONE: 1 << 2,
33+
};
34+
35+
const AudioDeviceSelectionDialog = GObject.registerClass({
36+
Signals: { 'device-selected': { param_types: [GObject.TYPE_UINT] }},
37+
}, class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog {
38+
_init(devices) {
39+
super._init({ styleClass: 'audio-device-selection-dialog' });
40+
41+
this._deviceItems = {};
42+
43+
this._buildLayout();
44+
45+
if (devices & AudioDevice.HEADPHONES)
46+
this._addDevice(AudioDevice.HEADPHONES);
47+
if (devices & AudioDevice.HEADSET)
48+
this._addDevice(AudioDevice.HEADSET);
49+
if (devices & AudioDevice.MICROPHONE)
50+
this._addDevice(AudioDevice.MICROPHONE);
51+
52+
if (this._selectionBox.get_n_children() < 2)
53+
throw new Error('Too few devices for a selection');
54+
}
55+
56+
_buildLayout() {
57+
let content = new Dialog.MessageDialogContent({
58+
title: _('Select Audio Device'),
59+
});
60+
61+
this._selectionBox = new St.BoxLayout({
62+
style_class: 'audio-selection-box',
63+
x_align: Clutter.ActorAlign.CENTER,
64+
x_expand: true,
65+
});
66+
content.add_child(this._selectionBox);
67+
68+
this.contentLayout.add_child(content);
69+
70+
this.addButton({
71+
action: () => this.close(),
72+
label: _('Cancel'),
73+
key: Clutter.KEY_Escape,
74+
destructive_action: true,
75+
});
76+
77+
this.addButton({
78+
action: this._openSettings.bind(this),
79+
label: _('Sound Settings'),
80+
});
81+
}
82+
83+
_getDeviceLabel(device) {
84+
switch (device) {
85+
case AudioDevice.HEADPHONES:
86+
return _('Headphones');
87+
case AudioDevice.HEADSET:
88+
return _('Headset');
89+
case AudioDevice.MICROPHONE:
90+
return _('Microphone');
91+
default:
92+
return null;
93+
}
94+
}
95+
96+
_getDeviceIcon(device) {
97+
switch (device) {
98+
case AudioDevice.HEADPHONES:
99+
return 'audio-headphones-symbolic';
100+
case AudioDevice.HEADSET:
101+
return 'audio-headset-symbolic';
102+
case AudioDevice.MICROPHONE:
103+
return 'audio-input-microphone-symbolic';
104+
default:
105+
return null;
106+
}
107+
}
108+
109+
_addDevice(device) {
110+
const box = new St.BoxLayout({
111+
style_class: 'audio-selection-device-box',
112+
vertical: true,
113+
});
114+
box.connect('notify::height', () => {
115+
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
116+
box.width = box.height;
117+
return GLib.SOURCE_REMOVE;
118+
});
119+
});
120+
121+
const icon = new St.Icon({
122+
style_class: 'audio-selection-device-icon',
123+
icon_name: this._getDeviceIcon(device),
124+
});
125+
box.add_child(icon);
126+
127+
const label = new St.Label({
128+
style_class: 'audio-selection-device-label',
129+
text: this._getDeviceLabel(device),
130+
x_align: Clutter.ActorAlign.CENTER,
131+
});
132+
box.add_child(label);
133+
134+
const button = new St.Button({
135+
style_class: 'audio-selection-device',
136+
can_focus: true,
137+
child: box,
138+
});
139+
this._selectionBox.add_child(button);
140+
141+
button.connect('clicked', () => {
142+
this.emit('device-selected', device);
143+
this.close();
144+
Main.overview.hide();
145+
});
146+
}
147+
148+
_openSettings() {
149+
Util.spawnCommandLine('cinnamon-settings sound');
150+
this.close();
151+
}
152+
});
153+
154+
var AudioDeviceSelectionDBus = class AudioDeviceSelectionDBus {
155+
constructor() {
156+
this._audioSelectionDialog = null;
157+
158+
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(AudioDeviceSelectionIface, this);
159+
this._dbusImpl.export(Gio.DBus.session, '/org/Cinnamon/AudioDeviceSelection');
160+
161+
Gio.DBus.session.own_name('org.Cinnamon.AudioDeviceSelection', Gio.BusNameOwnerFlags.REPLACE, null, null);
162+
}
163+
164+
_onDialogClosed() {
165+
this._audioSelectionDialog = null;
166+
}
167+
168+
_onDeviceSelected(dialog, device) {
169+
let connection = this._dbusImpl.get_connection();
170+
let info = this._dbusImpl.get_info();
171+
const deviceName = Object.keys(AudioDevice)
172+
.filter(dev => AudioDevice[dev] === device)[0].toLowerCase();
173+
connection.emit_signal(
174+
this._audioSelectionDialog._sender,
175+
this._dbusImpl.get_object_path(),
176+
info ? info.name : null,
177+
'DeviceSelected',
178+
GLib.Variant.new('(s)', [deviceName]));
179+
}
180+
181+
OpenAsync(params, invocation) {
182+
if (this._audioSelectionDialog) {
183+
invocation.return_value(null);
184+
return;
185+
}
186+
187+
let [deviceNames] = params;
188+
let devices = 0;
189+
deviceNames.forEach(n => (devices |= AudioDevice[n.toUpperCase()]));
190+
191+
let dialog;
192+
try {
193+
dialog = new AudioDeviceSelectionDialog(devices);
194+
} catch (e) {
195+
invocation.return_value(null);
196+
return;
197+
}
198+
dialog._sender = invocation.get_sender();
199+
200+
dialog.connect('closed', this._onDialogClosed.bind(this));
201+
dialog.connect('device-selected',
202+
this._onDeviceSelected.bind(this));
203+
dialog.open();
204+
205+
this._audioSelectionDialog = dialog;
206+
invocation.return_value(null);
207+
}
208+
209+
CloseAsync(params, invocation) {
210+
if (this._audioSelectionDialog &&
211+
this._audioSelectionDialog._sender === invocation.get_sender())
212+
this._audioSelectionDialog.close();
213+
214+
invocation.return_value(null);
215+
}
216+
}

js/ui/main.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const GObject = imports.gi.GObject;
8888
const XApp = imports.gi.XApp;
8989
const PointerTracker = imports.misc.pointerTracker;
9090

91+
const AudioDeviceSelection = imports.ui.audioDeviceSelection;
9192
const SoundManager = imports.ui.soundManager;
9293
const BackgroundManager = imports.ui.backgroundManager;
9394
const Config = imports.misc.config;
@@ -154,6 +155,7 @@ var messageTray = null;
154155
var notificationDaemon = null;
155156
var windowAttentionHandler = null;
156157
var screenRecorder = null;
158+
var cinnamonAudioSelectionDBusService = null;
157159
var cinnamonDBusService = null;
158160
var screenshotService = null;
159161
var modalCount = 0;
@@ -312,6 +314,7 @@ function start() {
312314
Clutter.get_default_backend().set_input_method(new InputMethod.InputMethod());
313315

314316
new CinnamonPortalHandler();
317+
cinnamonAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus();
315318
cinnamonDBusService = new CinnamonDBus.CinnamonDBus();
316319
setRunState(RunState.STARTUP);
317320

0 commit comments

Comments
 (0)