Merge lp:~diwic/unity-settings-daemon/what-did-you-plug-in into lp:unity-settings-daemon

Proposed by David Henningsson on 2014-02-18
Status: Merged
Approved by: Sebastien Bacher on 2014-02-18
Approved revision: 4015
Merged at revision: 4014
Proposed branch: lp:~diwic/unity-settings-daemon/what-did-you-plug-in
Merge into: lp:unity-settings-daemon
Diff against target: 685 lines (+534/-1)
10 files modified
configure.ac (+1/-1)
debian/control (+1/-0)
plugins/media-keys/Makefile.am (+4/-0)
plugins/media-keys/gsd-media-keys-manager.c (+80/-0)
plugins/media-keys/gvc/gvc-mixer-control.c (+13/-0)
plugins/media-keys/what-did-you-plug-in/dialog-window.c (+130/-0)
plugins/media-keys/what-did-you-plug-in/dialog-window.h (+18/-0)
plugins/media-keys/what-did-you-plug-in/pa-backend.c (+264/-0)
plugins/media-keys/what-did-you-plug-in/pa-backend.h (+22/-0)
po/POTFILES.in (+1/-0)
To merge this branch: bzr merge lp:~diwic/unity-settings-daemon/what-did-you-plug-in
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration 2014-02-18 Approve on 2014-02-18
Sebastien Bacher 2014-02-18 Approve on 2014-02-18
Matthew Paul Thomas design 2014-02-18 Pending
Review via email: mp+206915@code.launchpad.net

This proposal supersedes a proposal from 2014-02-15.

Commit Message

Handle unknown audio jack devices

Description of the Change

Hi,

Here comes the implementation of the "what did you plug in"-dialog for 14.04. While I'd like some more real-world testing before the actual merge happens (I can see to that), in parallel it would be good with some code focused review so I don't spend time polishing things if there's something with the design that you think is bad.

To post a comment you must log in.
David Henningsson (diwic) wrote : Posted in a previous version of this proposal

Hrm, looking at the diff below I probably did something wrong, like submitting against the wrong branch or something. Anyway, it's the file debian/patches/what-did-you-plug-in.patch in the lp:~diwic/gnome-settings-daemon/what-did-you-plug-in branch that should be looked at.

Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

@David: can you resubmit it (top right corner of the launchpad page) and change the target vcs to lp:~ubuntu-desktop/gnome-settings-daemon/ubuntu?

David Henningsson (diwic) wrote : Posted in a previous version of this proposal

Sure, done now. I notice that a new version of g-s-d was released in parallel, but hopefully that won't stop you from looking at the code - I can rebase it on top of the latest g-s-d when we feel ready quality wise.

David Henningsson (diwic) wrote : Posted in a previous version of this proposal

Btw, I chose to implement it in the media-keys plugin instead of making a new plugin, mostly for efficiency reasons: no need to have two different connections to PulseAudio constantly up, and I save some code because I don't have to reimplement stuff like reconnecting on PA restart and such.

Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

Thanks for the work David, some questions/comments:

- is there any easy way to test that? I've tried plugging headphones/a microphone on my laptop but that doesn't trigger the dialog, not sure if you can easily emulate the unknown case? Having a way to display the UI at least would be nice

- we started the transition to unity-settings-daemon, can you retarget your changes against that branch (not distro patch there, it's full source)

- could you explain a bit what the code does? I tried to look at bit at the logic but I don't really know pulseaudio and things like "snd_ctl_elem_id_set_name(id, "Headset Mic Phantom Jack")" look a bit weird to me...

- pa-backend.h has commented functions, do you plan to add those back/use them? if not could you delete the lines rather than commenting them?

- > if (system("gnome-control-center sound") == -1)

better to cal "unity-control-center sound" since we are transitionning to that

- > d->settings_btn = gtk_button_new_with_label(_("Sound Settings..."));

can you replace the "..." by a their utf8 variant "…"?

- the design has a checkbox to store the choice, do you plan to add that?

- you need to list the new sources in po/POTFILES.in so the translatable strings are included in the translation template

review: Needs Fixing
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

I've tweaked the source to run the UI, some extra comments:

- the "What kind of device did you plug in?" label is centered in the design and left aligned on your version

- the spacing between the Cancel/Sound Settings buttons is non standard

review: Needs Fixing
David Henningsson (diwic) wrote : Posted in a previous version of this proposal

Hi Seb, thanks for your review! Just a few question before I start fixing the things you said:

> - is there any easy way to test that? I've tried plugging headphones/a microphone on my laptop but that doesn't
> trigger the dialog, not sure if you can easily emulate the unknown case? Having a way to display the UI at least
> would be nice

Do you think adding a local "#define DIALOGTEST" (that would pop up the dialog on any plug in or so) would do? Or do you want something that can be runtime enabled through some switch?

> - we started the transition to unity-settings-daemon, can you retarget your changes against that branch (not distro
> patch there, it's full source)

Sure, is it https://code.launchpad.net/~ubuntu-desktop/unity-greeter/unity-settings-daemon ?

> - the design has a checkbox to store the choice, do you plan to add that?

Because it requires significant additional coding (mostly in sound settings, where you need to be able to revert back to "please ask me"), I was thinking of deferring it to the next cycle, where things have to be rewritten for Qt anyway. Also, this checkbox was not present in the wireframe we got from the design team at one point, so I figured it would be a nice-to-have rather than an absolute requirement. Is that okay with you?

Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

> Do you think adding a local "#define DIALOGTEST" (that would pop up the dialog on any plug in or so) would do? Or do you want something that can be runtime enabled through some switch?

That would be useful, but feel free to skip over that. I basically hacked dialog-window.c to add a main() with gtk_init/gtk_run/call to dialog_create, which was enough to do a local testing

> Sure, is it https://code.launchpad.net/~ubuntu-desktop/unity-greeter/unity-settings-daemon ?

It moved teams for acl reasons, it's now https://code.launchpad.net/~unity-settings-daemon-team/unity-settings-daemon/trunk (or just use lp:unity-settings-daemon)

> Because it requires significant additional coding (mostly in sound settings, where you need to be able to revert back to "please ask me"), I was thinking of deferring it to the next cycle, where things have to be rewritten for Qt anyway. Also, this checkbox was not present in the wireframe we got from the design team at one point, so I figured it would be a nice-to-have rather than an absolute requirement. Is that okay with you?

That seems reasonable to me as well, yes.

David Henningsson (diwic) wrote : Posted in a previous version of this proposal

Here comes the new merge proposal. Changes since previous version:

 > - is there any easy way to test that? I've tried plugging headphones/a microphone on my laptop but that doesn't trigger the dialog, not sure if you can easily emulate the unknown case? Having a way to display the UI at least would be nice

See the TEST_WDYPI_DIALOG define in gsd-media-keys-manager.c

 > - we started the transition to unity-settings-daemon, can you retarget your changes against that branch (not distro patch there, it's full source)

Fixed

 > - could you explain a bit what the code does? I tried to look at bit at the logic but I don't really know pulseaudio and things like "snd_ctl_elem_id_set_name(id, "Headset Mic Phantom Jack")" look a bit weird to me...

I added two comments with some overview of the detection and port setting mechanism. Is this helpful? If you need additional comments, I'll be happy to add them.

 > - pa-backend.h has commented functions, do you plan to add those back/use them? if not could you delete the lines rather than commenting them?

Fixed

 > - > if (system("gnome-control-center sound") == -1)
 > better to cal "unity-control-center sound" since we are transitionning to that

Fixed

 > - > d->settings_btn = gtk_button_new_with_label(_("Sound Settings..."));
 > can you replace the "..." by a their utf8 variant "…"?

Fixed

 > - you need to list the new sources in po/POTFILES.in so the translatable strings are included in the translation template

Fixed, I think (unsure how to test it though)

 > - the "What kind of device did you plug in?" label is centered in the design and left aligned on your version

Fixed (design team was inconsistent on this one though)

 > - the spacing between the Cancel/Sound Settings buttons is non standard

I changed the spacing, but not sure if it is standard now, because I'm unsure what the standard is.

PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:4013
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~diwic/unity-settings-daemon/what-did-you-plug-in/+merge/206573/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-ci/7/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-trusty-amd64-ci/7/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-trusty-armhf-ci/7/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-trusty-i386-ci/7/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity-settings-daemon-ci/7/rebuild

review: Needs Fixing (continuous-integration)
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

Thanks for the update!

> See the TEST_WDYPI_DIALOG define in gsd-media-keys-manager.c

You use the on_wdypi_popup() before it's defined which leads to a build error, once that resolved it seems to build/work fine though

> I added two comments with some overview of the detection and port setting mechanism. Is this helpful? If you need additional comments, I'll be happy to add them.

Those comments are quite useful indeed, thanks for that!

That seems a bit hackish
" Headphone Mic Jack - indicates headphone and mic-in mode share the same jack,
...
  Headset Mic Phantom Jack - indicates headset jack where hardware can not
...
  Headset Mic Jack - indicates headset jack where hardware can distinguish
..."

Those names seem like current descriptions from alsa, are we sure the strings are not going to change/be translated/... that seems not a very reliable comparaison? If we don't have a better API I guess it would do the job for a stable serie but we should put some tests in the build to make sure those names keep being current maybe, no?

> I changed the spacing, but not sure if it is standard now, because I'm unsure what the standard is.

The GNOME HIG use 6 pixels steps, I think it's a 6 pixel value button the button

I've done a screenshot and copied it on http://people.canonical.com/~seb128/headphone.png that might make easier to ask comments from design

Otherwise the code looks fine to me, do you want to do extra testing or do you consider it ready to land?

review: Needs Fixing
Matthew Paul Thomas (mpt) wrote : Posted in a previous version of this proposal

> I changed the spacing, but not sure if it is standard now, because I'm unsure what the standard is.

a. 6 pixels between the large buttons (Headphones vs. Headset, Headset vs. Microphone)
b. 6 pixels between "Cancel" and "Sound Settings…"
c. 12 pixels between "Cancel"/"Sound Settings…" and the bottom of the window.

Remember too that the dialog should not be resizable, and therefore should not have a Maximize button.

review: Needs Fixing (design)
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

One other small issue, with the define set the dialog opens at u-s-d start ... is that supposed to happen? (or should it only react to even?) (said differently, does it mean it's going to prompt at login if you have a such device plugged as well?)

David Henningsson (diwic) wrote : Posted in a previous version of this proposal

 > You use the on_wdypi_popup() before it's defined which leads to a build error

Oops, fixed.

 > Those names seem like current descriptions from alsa, are we sure the strings are not going to change/be translated/...

They are not translated. They are unlikely to change, and it's what PulseAudio depends on too. But yeah, the discussion comes up every year on the upstream audio miniconf, that there should be some new API with a more structured approach than mathing on names. And the conclusion is that nobody has time to do it, and that the current solution works well enough. Sort of. :-)

 > Otherwise the code looks fine to me, do you want to do extra testing or do you consider it ready to land?

I think it's ready to land (will post new merge proposal shortly). Acelan did some testing on real hw a few iterations ago and little has changed since, i e in the detection part.

David Henningsson (diwic) wrote : Posted in a previous version of this proposal

 > a. 6 pixels between the large buttons (Headphones vs. Headset, Headset vs. Microphone)
 > b. 6 pixels between "Cancel" and "Sound Settings…"
 > c. 12 pixels between "Cancel"/"Sound Settings…" and the bottom of the window.

All these three should be fixed now. A little unsure if I at the same time managed to make the distance between the large buttons and the bottom buttons have a few pixels too many...

 > Remember too that the dialog should not be resizable, and therefore should not have a Maximize button.

Ok, fixed.

PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:4015
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~diwic/unity-settings-daemon/what-did-you-plug-in/+merge/206915/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-ci/10/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-trusty-amd64-ci/10
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-trusty-armhf-ci/10
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-settings-daemon-trusty-i386-ci/10

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity-settings-daemon-ci/10/rebuild

review: Needs Fixing (continuous-integration)
Sebastien Bacher (seb128) wrote :

> the current solution works well enough. Sort of. :-)

Ok, that works for me then, thanks for the explanations!

Testing seems fine as well, let's get that in, thanks for the work!

review: Approve
Sebastien Bacher (seb128) wrote :

(btw no need to resubmit a mp every time you do changes, just push to the branch you have been using, bzr/launchpad handle that fine and pick the new commits/update the merge request page diff and revisions list)

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2014-02-06 10:43:50 +0000
3+++ configure.ac 2014-02-18 12:21:20 +0000
4@@ -207,7 +207,7 @@
5 dnl - media-keys plugin stuff
6 dnl ---------------------------------------------------------------------------
7
8-PKG_CHECK_MODULES(MEDIA_KEYS, [gio-unix-2.0 libpulse >= $PA_REQUIRED_VERSION $GUDEV_PKG libpulse-mainloop-glib >= $PA_REQUIRED_VERSION libcanberra-gtk3 libnotify])
9+PKG_CHECK_MODULES(MEDIA_KEYS, [gio-unix-2.0 libpulse >= $PA_REQUIRED_VERSION $GUDEV_PKG libpulse-mainloop-glib >= $PA_REQUIRED_VERSION libcanberra-gtk3 libnotify alsa])
10 PKG_CHECK_MODULES(GVC, [gobject-2.0 libpulse >= $PA_REQUIRED_VERSION libpulse-mainloop-glib >= $PA_REQUIRED_VERSION])
11 AM_CONDITIONAL(HAVE_INTROSPECTION, false)
12
13
14=== modified file 'debian/control'
15--- debian/control 2014-02-07 14:57:09 +0000
16+++ debian/control 2014-02-18 12:21:20 +0000
17@@ -22,6 +22,7 @@
18 gsettings-desktop-schemas-dev (>= 3.7.2.1),
19 libgnome-desktop-3-dev (>= 3.7.90),
20 libpulse-dev (>= 1:2.0),
21+ libasound2-dev,
22 librsvg2-dev (>= 2.36.2),
23 libcanberra-gtk3-dev,
24 libcups2-dev,
25
26=== modified file 'plugins/media-keys/Makefile.am'
27--- plugins/media-keys/Makefile.am 2013-12-04 23:55:26 +0000
28+++ plugins/media-keys/Makefile.am 2014-02-18 12:21:20 +0000
29@@ -29,6 +29,8 @@
30 $(srcdir)/org.gnome.ShellKeyGrabber.xml
31
32 libmedia_keys_la_SOURCES = \
33+ what-did-you-plug-in/pa-backend.c \
34+ what-did-you-plug-in/dialog-window.c \
35 gsd-media-keys-plugin.c \
36 gsd-media-keys-manager.h \
37 gsd-media-keys-manager.c \
38@@ -44,6 +46,7 @@
39 -I$(top_srcdir)/gnome-settings-daemon \
40 -I$(top_srcdir)/plugins/common \
41 -I$(top_srcdir)/plugins/media-keys/gvc \
42+ -I$(top_srcdir)/plugins/media-keys/what-did-you-plug-in \
43 -DBINDIR=\"$(bindir)\" \
44 -DPIXMAPDIR=\""$(pkgdatadir)"\" \
45 -DGTKBUILDERDIR=\""$(pkgdatadir)"\" \
46@@ -87,6 +90,7 @@
47 -I$(top_srcdir)/gnome-settings-daemon \
48 -I$(top_srcdir)/plugins/common \
49 -I$(top_srcdir)/plugins/media-keys/gvc \
50+ -I$(top_srcdir)/plugins/media-keys/what-did-you-plug-in \
51 -DBINDIR=\"$(bindir)\" \
52 -DPIXMAPDIR=\""$(pkgdatadir)"\" \
53 -DGTKBUILDERDIR=\""$(pkgdatadir)"\" \
54
55=== modified file 'plugins/media-keys/gsd-media-keys-manager.c'
56--- plugins/media-keys/gsd-media-keys-manager.c 2013-11-13 02:03:10 +0000
57+++ plugins/media-keys/gsd-media-keys-manager.c 2014-02-18 12:21:20 +0000
58@@ -60,7 +60,10 @@
59 #include <canberra.h>
60 #include <pulse/pulseaudio.h>
61 #include "gvc-mixer-control.h"
62+#include "gvc-mixer-control-private.h"
63 #include "gvc-mixer-sink.h"
64+#include "pa-backend.h"
65+#include "dialog-window.h"
66
67 #include <libnotify/notify.h>
68
69@@ -215,6 +218,8 @@
70 guint panel_name_owner_id;
71 guint have_legacy_keygrabber;
72
73+ /* What did you plug in dialog */
74+ pa_backend *wdypi_pa_backend;
75 };
76
77 static void gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass);
78@@ -2690,6 +2695,69 @@
79 }
80 }
81
82+
83+static void
84+launch_sound_settings()
85+{
86+ if (fork() != 0)
87+ return;
88+
89+ /* Child process */
90+ if (system("unity-control-center sound") == -1)
91+ g_warning("Failed to launch sound settings.\n");
92+ exit(0);
93+}
94+
95+static void
96+on_wdypi_action (int action, void *userdata)
97+{
98+ GsdMediaKeysManager *manager = userdata;
99+ pa_backend *pb = manager->priv->wdypi_pa_backend;
100+
101+ pa_backend_set_context(pb, gvc_mixer_control_get_pa_context(manager->priv->volume));
102+
103+ switch (action) {
104+ case WDYPI_DIALOG_SOUND_SETTINGS:
105+ launch_sound_settings();
106+ break;
107+ case WDYPI_DIALOG_HEADPHONES:
108+ pa_backend_set_port(pb, "analog-output-headphones", true);
109+ pa_backend_set_port(pb, "analog-input-microphone-internal", false);
110+ break;
111+ case WDYPI_DIALOG_HEADSET:
112+ pa_backend_set_port(pb, "analog-output-headphones", true);
113+ pa_backend_set_port(pb, "analog-input-microphone-headset", false);
114+ break;
115+ case WDYPI_DIALOG_MICROPHONE:
116+ pa_backend_set_port(pb, "analog-output-speaker", true);
117+ pa_backend_set_port(pb, "analog-input-microphone", false);
118+ break;
119+ default:
120+ break;
121+ }
122+}
123+
124+static void
125+on_wdypi_popup (bool hsmic, bool hpmic, void *userdata)
126+{
127+ if (!hpmic && !hsmic)
128+ wdypi_dialog_kill();
129+ else wdypi_dialog_run(hsmic, hpmic, on_wdypi_action, userdata);
130+}
131+
132+static void
133+on_control_card_info_updated (GvcMixerControl *control,
134+ gpointer card_info,
135+ GsdMediaKeysManager *manager)
136+{
137+ pa_backend_card_changed (manager->priv->wdypi_pa_backend, card_info);
138+#ifdef TEST_WDYPI_DIALOG
139+ /* Just a simple way to test the dialog on all types of hardware
140+ (pops up dialog on program start, and on every plug in) */
141+ on_wdypi_popup (true, true, manager);
142+#endif
143+}
144+
145 static void
146 initialize_volume_handler (GsdMediaKeysManager *manager)
147 {
148@@ -2703,6 +2771,8 @@
149
150 manager->priv->volume = gvc_mixer_control_new ("GNOME Volume Control Media Keys");
151
152+ manager->priv->wdypi_pa_backend = pa_backend_new(on_wdypi_popup, manager);
153+
154 g_signal_connect (manager->priv->volume,
155 "state-changed",
156 G_CALLBACK (on_control_state_changed),
157@@ -2719,6 +2789,10 @@
158 "stream-removed",
159 G_CALLBACK (on_control_stream_removed),
160 manager);
161+ g_signal_connect (manager->priv->volume,
162+ "card-info",
163+ G_CALLBACK (on_control_card_info_updated),
164+ manager);
165
166 gvc_mixer_control_open (manager->priv->volume);
167
168@@ -3074,6 +3148,12 @@
169 gdk_error_trap_pop_ignored ();
170 }
171
172+ if (manager->priv->wdypi_pa_backend) {
173+ pa_backend_free (manager->priv->wdypi_pa_backend);
174+ manager->priv->wdypi_pa_backend = NULL;
175+ }
176+ wdypi_dialog_kill();
177+
178 if (priv->grab_cancellable != NULL) {
179 g_cancellable_cancel (priv->grab_cancellable);
180 g_clear_object (&priv->grab_cancellable);
181
182=== modified file 'plugins/media-keys/gvc/gvc-mixer-control.c'
183--- plugins/media-keys/gvc/gvc-mixer-control.c 2013-11-13 02:13:58 +0000
184+++ plugins/media-keys/gvc/gvc-mixer-control.c 2014-02-18 12:21:20 +0000
185@@ -114,6 +114,7 @@
186 INPUT_ADDED,
187 OUTPUT_REMOVED,
188 INPUT_REMOVED,
189+ CARD_INFO,
190 LAST_SIGNAL
191 };
192
193@@ -2150,6 +2151,10 @@
194 }
195 }
196 g_signal_emit (G_OBJECT (control),
197+ signals[CARD_INFO],
198+ 0,
199+ info);
200+ g_signal_emit (G_OBJECT (control),
201 signals[CARD_ADDED],
202 0,
203 info->index);
204@@ -3206,6 +3211,14 @@
205 NULL, NULL,
206 g_cclosure_marshal_VOID__UINT,
207 G_TYPE_NONE, 1, G_TYPE_UINT);
208+ signals [CARD_INFO] =
209+ g_signal_new ("card-info",
210+ G_TYPE_FROM_CLASS (klass),
211+ G_SIGNAL_RUN_LAST,
212+ 0,
213+ NULL, NULL,
214+ g_cclosure_marshal_VOID__POINTER,
215+ G_TYPE_NONE, 1, G_TYPE_POINTER);
216 signals [CARD_ADDED] =
217 g_signal_new ("card-added",
218 G_TYPE_FROM_CLASS (klass),
219
220=== added directory 'plugins/media-keys/what-did-you-plug-in'
221=== added file 'plugins/media-keys/what-did-you-plug-in/dialog-window.c'
222--- plugins/media-keys/what-did-you-plug-in/dialog-window.c 1970-01-01 00:00:00 +0000
223+++ plugins/media-keys/what-did-you-plug-in/dialog-window.c 2014-02-18 12:21:20 +0000
224@@ -0,0 +1,130 @@
225+#include <glib.h>
226+#include <glib/gi18n.h>
227+#include <gtk/gtk.h>
228+#include <gdk-pixbuf/gdk-pixbuf.h>
229+
230+#include "dialog-window.h"
231+
232+typedef struct dialog_window {
233+ GtkWidget *dialog;
234+ GtkWidget *ca_box;
235+ GtkWidget *v_box;
236+ GtkWidget *icon_box;
237+ GtkWidget *btn_box;
238+ GtkWidget *label;
239+ GtkWidget *cancel_btn;
240+ GtkWidget *settings_btn;
241+ GtkWidget *hp_btn;
242+ GtkWidget *hs_btn;
243+ GtkWidget *mic_btn;
244+
245+ int button_response;
246+ wdypi_dialog_cb cb;
247+ void *cb_userdata;
248+} dialog_window;
249+
250+/* It's okay to have a global here - we should never show more than one dialog */
251+dialog_window dlg;
252+
253+void wdypi_dialog_kill()
254+{
255+ dialog_window *d = &dlg;
256+ if (d->dialog) {
257+ gtk_widget_destroy(d->dialog);
258+ d->dialog = NULL;
259+ }
260+}
261+
262+static void on_iconbtn_clicked (GtkWidget *widget, gpointer data)
263+{
264+ dialog_window *d = &dlg;
265+ d->button_response = (ssize_t) data;
266+ gtk_dialog_response(GTK_DIALOG(d->dialog), GTK_RESPONSE_OK);
267+}
268+
269+static void on_response (GtkWidget *widget, gint response_id, gpointer data)
270+{
271+ int resp;
272+ dialog_window *d = data;
273+ if (!d->cb)
274+ return;
275+
276+ switch (response_id) {
277+ case GTK_RESPONSE_YES:
278+ resp = WDYPI_DIALOG_SOUND_SETTINGS;
279+ break;
280+ case GTK_RESPONSE_OK:
281+ resp = d->button_response;
282+ break;
283+ default:
284+ resp = WDYPI_DIALOG_CANCELLED;
285+ }
286+
287+ d->cb(resp, d->cb_userdata);
288+
289+ wdypi_dialog_kill();
290+}
291+
292+static GtkWidget * create_icon_button(int response, const char *name, const char *icon)
293+{
294+ GtkWidget *btn = gtk_button_new();
295+ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
296+ GtkWidget *lbl = gtk_label_new(name);
297+ GtkWidget *img = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_DIALOG);
298+
299+ gtk_box_pack_end(GTK_BOX(box), lbl, FALSE, FALSE, 0);
300+ gtk_box_pack_end(GTK_BOX(box), img, FALSE, FALSE, 0);
301+ gtk_container_set_border_width(GTK_CONTAINER(box), 6);
302+ gtk_container_add(GTK_CONTAINER(btn), box);
303+ g_signal_connect(btn, "clicked", G_CALLBACK(on_iconbtn_clicked), (void*) (ssize_t) response);
304+ return btn;
305+}
306+
307+static void dialog_create(dialog_window *d, bool show_headset, bool show_mic)
308+{
309+ d->dialog = gtk_dialog_new();
310+ gtk_window_set_title(GTK_WINDOW(d->dialog), _("Unknown Audio Device"));
311+ gtk_container_set_border_width(GTK_CONTAINER(d->dialog), 6);
312+ gtk_window_set_icon_name(GTK_WINDOW(d->dialog), "audio-headphones");
313+ gtk_window_set_resizable(GTK_WINDOW(d->dialog), FALSE);
314+
315+ d->ca_box = gtk_dialog_get_content_area(GTK_DIALOG(d->dialog));
316+ d->v_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
317+ gtk_container_set_border_width(GTK_CONTAINER(d->v_box), 5);
318+ d->icon_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
319+ gtk_box_set_homogeneous(GTK_BOX(d->icon_box), TRUE);
320+
321+ d->label = gtk_label_new(_("What kind of device did you plug in?"));
322+ gtk_misc_set_alignment(GTK_MISC(d->label), 0.5, 0.5);
323+ gtk_box_pack_start(GTK_CONTAINER(d->v_box), d->label, FALSE, FALSE, 6);
324+
325+ d->hp_btn = create_icon_button(WDYPI_DIALOG_HEADPHONES, _("Headphones"), "audio-headphones");
326+ gtk_box_pack_start(GTK_BOX(d->icon_box), d->hp_btn, FALSE, TRUE, 0);
327+ if (show_headset) {
328+ d->hs_btn = create_icon_button(WDYPI_DIALOG_HEADSET, _("Headset"), "audio-headset");
329+ gtk_box_pack_start(GTK_BOX(d->icon_box), d->hs_btn, FALSE, TRUE, 0);
330+ }
331+ if (show_mic) {
332+ d->mic_btn = create_icon_button(WDYPI_DIALOG_MICROPHONE, _("Microphone"), "audio-input-microphone");
333+ gtk_box_pack_start(GTK_BOX(d->icon_box), d->mic_btn, FALSE, TRUE, 0);
334+ }
335+ gtk_box_pack_start(GTK_CONTAINER(d->v_box), d->icon_box, FALSE, FALSE, 6);
336+
337+ d->cancel_btn = gtk_dialog_add_button(GTK_DIALOG(d->dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
338+ d->settings_btn = gtk_dialog_add_button(GTK_DIALOG(d->dialog), _("Sound Settings…"), GTK_RESPONSE_YES);
339+
340+ gtk_container_add(GTK_CONTAINER(d->ca_box), d->v_box);
341+ g_signal_connect(d->dialog, "response", G_CALLBACK(on_response), d);
342+
343+ gtk_widget_show_all(d->dialog);
344+}
345+
346+void wdypi_dialog_run(bool show_headset, bool show_mic, wdypi_dialog_cb cb, void *cb_userdata)
347+{
348+ dialog_window *d = &dlg;
349+
350+ wdypi_dialog_kill();
351+ d->cb = cb;
352+ d->cb_userdata = cb_userdata;
353+ dialog_create(d, show_headset, show_mic);
354+}
355
356=== added file 'plugins/media-keys/what-did-you-plug-in/dialog-window.h'
357--- plugins/media-keys/what-did-you-plug-in/dialog-window.h 1970-01-01 00:00:00 +0000
358+++ plugins/media-keys/what-did-you-plug-in/dialog-window.h 2014-02-18 12:21:20 +0000
359@@ -0,0 +1,18 @@
360+#ifndef __DIALOG_WINDOW_H__
361+#define __DIALOG_WINDOW_H__
362+
363+#include <stdbool.h>
364+
365+#define WDYPI_DIALOG_CANCELLED 0
366+#define WDYPI_DIALOG_HEADPHONES 1
367+#define WDYPI_DIALOG_HEADSET 2
368+#define WDYPI_DIALOG_MICROPHONE 3
369+#define WDYPI_DIALOG_SOUND_SETTINGS 4
370+
371+typedef void (*wdypi_dialog_cb)(int response, void *userdata);
372+
373+void wdypi_dialog_run(bool show_headset, bool show_mic, wdypi_dialog_cb cb, void *cb_userdata);
374+
375+void wdypi_dialog_kill();
376+
377+#endif
378
379=== added file 'plugins/media-keys/what-did-you-plug-in/pa-backend.c'
380--- plugins/media-keys/what-did-you-plug-in/pa-backend.c 1970-01-01 00:00:00 +0000
381+++ plugins/media-keys/what-did-you-plug-in/pa-backend.c 2014-02-18 12:21:20 +0000
382@@ -0,0 +1,264 @@
383+#include <stdlib.h>
384+#include <stdio.h>
385+#include <string.h>
386+#include <alsa/asoundlib.h>
387+#include <pulse/pulseaudio.h>
388+#include <pulse/glib-mainloop.h>
389+
390+#include "pa-backend.h"
391+
392+struct pa_backend {
393+ const pa_context *context;
394+ pa_backend_cb dialog_cb;
395+ void *cb_userdata;
396+ int headset_card;
397+ bool headset_plugged_in;
398+ bool has_headsetmic;
399+ bool has_headphonemic;
400+
401+ const char *sink_port_name_to_set;
402+ const char *source_port_name_to_set;
403+};
404+
405+void pa_backend_set_context(pa_backend *p, const pa_context *c)
406+{
407+ p->context = c;
408+}
409+
410+pa_backend *pa_backend_new(pa_backend_cb cb, void *cb_userdata)
411+{
412+ pa_backend *p = calloc(1, sizeof(*p));
413+
414+ if (!p)
415+ return NULL;
416+
417+ p->headset_card = -1;
418+ p->dialog_cb = cb;
419+ p->cb_userdata = cb_userdata;
420+ return p;
421+}
422+
423+typedef struct headset_ports {
424+ const pa_card_port_info *headphones, *headsetmic, *headphonemic;
425+} headset_ports;
426+
427+/* In PulseAudio ports will show up with the following names:
428+ Headphones - analog-output-headphones
429+ Headset mic - analog-input-microphone-headset
430+ Jack in mic-in mode - analog-input-microphone
431+
432+ However, since regular mics also show up as analog-input-microphone,
433+ we need to check for certain controls on alsa mixer level too, to know
434+ if we deal with a separate mic jack, or a multi-function jack with a
435+ mic-in mode (also called "headphone mic").
436+ We check for the following names:
437+
438+ Headphone Mic Jack - indicates headphone and mic-in mode share the same jack,
439+ i e, not two separate jacks. Hardware cannot distinguish between a
440+ headphone and a mic.
441+ Headset Mic Phantom Jack - indicates headset jack where hardware can not
442+ distinguish between headphones and headsets
443+ Headset Mic Jack - indicates headset jack where hardware can distinguish
444+ between headphones and headsets. There is no use popping up a dialog in
445+ this case, unless we already need to do this for the mic-in mode.
446+*/
447+
448+static headset_ports get_headset_ports(const pa_card_info *c)
449+{
450+ headset_ports h = {NULL, NULL, NULL};
451+ int i;
452+ for (i = 0; i < c->n_ports; i++) {
453+ pa_card_port_info *p = c->ports[i];
454+ if (!strcmp(p->name, "analog-output-headphones"))
455+ h.headphones = p;
456+ else if (!strcmp(p->name, "analog-input-microphone-headset"))
457+ h.headsetmic = p;
458+ else if (!strcmp(p->name, "analog-input-microphone"))
459+ h.headphonemic = p;
460+ }
461+ return h;
462+}
463+
464+static bool verify_alsa_card(int cardindex, bool *headsetmic, bool *headphonemic)
465+{
466+ char ctlstr[20];
467+ snd_hctl_t *hctl;
468+ snd_ctl_elem_id_t *id;
469+ int err;
470+
471+ *headsetmic = false;
472+ *headphonemic = false;
473+
474+ snprintf(ctlstr, sizeof(ctlstr), "hw:%i", cardindex);
475+ if ((err = snd_hctl_open(&hctl, ctlstr, 0)) < 0) {
476+ g_warning("snd_hctl_open failed: %s", snd_strerror(err));
477+ return false;
478+ }
479+
480+ if ((err = snd_hctl_load(hctl)) < 0) {
481+ g_warning("snd_hctl_load failed: %s", snd_strerror(err));
482+ snd_hctl_close(hctl);
483+ return false;
484+ }
485+
486+ snd_ctl_elem_id_alloca(&id);
487+
488+ snd_ctl_elem_id_clear(id);
489+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
490+ snd_ctl_elem_id_set_name(id, "Headphone Mic Jack");
491+ if (snd_hctl_find_elem(hctl, id))
492+ *headphonemic = true;
493+
494+ snd_ctl_elem_id_clear(id);
495+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
496+ snd_ctl_elem_id_set_name(id, "Headset Mic Phantom Jack");
497+ if (snd_hctl_find_elem(hctl, id))
498+ *headsetmic = true;
499+
500+ if (*headphonemic) {
501+ snd_ctl_elem_id_clear(id);
502+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
503+ snd_ctl_elem_id_set_name(id, "Headset Mic Jack");
504+ if (snd_hctl_find_elem(hctl, id))
505+ *headsetmic = true;
506+ }
507+
508+ snd_hctl_close(hctl);
509+ return *headsetmic || *headphonemic;
510+}
511+
512+void pa_backend_card_changed(pa_backend *p, const pa_card_info *i)
513+{
514+ headset_ports h;
515+ bool start_dialog = false, stop_dialog = false;
516+
517+ h = get_headset_ports(i);
518+
519+ if (!h.headphones || (!h.headsetmic && !h.headphonemic))
520+ return; /* Not a headset jack */
521+
522+ if (p->headset_card != (int) i->index) {
523+ int cardindex = 0;
524+ bool hsmic, hpmic;
525+ const char *s = pa_proplist_gets(i->proplist, "alsa.card");
526+ if (!s)
527+ return;
528+ cardindex = strtol(s, NULL, 10);
529+ if (cardindex == 0 && strcmp(s, "0"))
530+ return;
531+
532+ if (!verify_alsa_card(cardindex, &hsmic, &hpmic))
533+ return;
534+
535+ p->headset_card = (int) i->index;
536+ p->has_headsetmic = hsmic && h.headsetmic;
537+ p->has_headphonemic = hpmic && h.headphonemic;
538+ }
539+ else {
540+ start_dialog = p->dialog_cb && (h.headphones->available != PA_PORT_AVAILABLE_NO) &&
541+ !p->headset_plugged_in;
542+ stop_dialog = p->dialog_cb && (h.headphones->available == PA_PORT_AVAILABLE_NO) &&
543+ p->headset_plugged_in;
544+ }
545+
546+
547+ p->headset_plugged_in = h.headphones->available != PA_PORT_AVAILABLE_NO;
548+
549+ if (start_dialog)
550+ p->dialog_cb(p->has_headsetmic, p->has_headphonemic, p->cb_userdata);
551+ else if (stop_dialog)
552+ p->dialog_cb(false, false, p->cb_userdata);
553+}
554+
555+/*
556+ We need to re-enumerate sources and sinks every time the user makes a choice,
557+ because they can change due to use interaction in other software (or policy
558+ changes inside PulseAudio). Enumeration means PulseAudio will do a series of
559+ callbacks, one for every source/sink.
560+ Set the port when we find the correct source/sink.
561+ */
562+
563+static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
564+{
565+ pa_backend *p = userdata;
566+ pa_operation *o;
567+ int j;
568+ const char *s = p->sink_port_name_to_set;
569+
570+ if (eol)
571+ return;
572+
573+ if (i->card != p->headset_card)
574+ return;
575+
576+ if (i->active_port && !strcmp(i->active_port->name, s))
577+ return;
578+
579+ for (j = 0; j < i->n_ports; j++)
580+ if (!strcmp(i->ports[j]->name, s))
581+ break;
582+
583+ if (j >= i->n_ports)
584+ return;
585+
586+ o = pa_context_set_sink_port_by_index(c, i->index, s, NULL, NULL);
587+ if (o)
588+ pa_operation_unref(o);
589+}
590+
591+static void source_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata)
592+{
593+ pa_backend *p = userdata;
594+ pa_operation *o;
595+ int j;
596+ const char *s = p->source_port_name_to_set;
597+
598+ if (eol)
599+ return;
600+
601+ if (i->card != p->headset_card)
602+ return;
603+
604+ if (i->active_port && !strcmp(i->active_port->name, s))
605+ return;
606+
607+ for (j = 0; j < i->n_ports; j++)
608+ if (!strcmp(i->ports[j]->name, s))
609+ break;
610+
611+ if (j >= i->n_ports)
612+ return;
613+
614+ o = pa_context_set_source_port_by_index(c, i->index, s, NULL, NULL);
615+ if (o)
616+ pa_operation_unref(o);
617+}
618+
619+
620+void pa_backend_set_port(pa_backend *p, const char *portname, bool is_output)
621+{
622+ pa_operation *o;
623+
624+ if (is_output) {
625+ p->sink_port_name_to_set = portname;
626+ o = pa_context_get_sink_info_list(p->context, sink_info_cb, p);
627+ }
628+ else {
629+ p->source_port_name_to_set = portname;
630+ o = pa_context_get_source_info_list(p->context, source_info_cb, p);
631+ }
632+
633+ if (o) {
634+ pa_operation_unref(o);
635+ }
636+}
637+
638+
639+void pa_backend_free(pa_backend *p)
640+{
641+ if (!p)
642+ return;
643+
644+ free(p);
645+}
646+
647
648=== added file 'plugins/media-keys/what-did-you-plug-in/pa-backend.h'
649--- plugins/media-keys/what-did-you-plug-in/pa-backend.h 1970-01-01 00:00:00 +0000
650+++ plugins/media-keys/what-did-you-plug-in/pa-backend.h 2014-02-18 12:21:20 +0000
651@@ -0,0 +1,22 @@
652+#ifndef __PA_BACKEND_H__
653+#define __PA_BACKEND_H__
654+
655+#include <stdbool.h>
656+#include <pulse/pulseaudio.h>
657+
658+typedef struct pa_backend pa_backend;
659+
660+typedef void (*pa_backend_cb)(bool headsetmic, bool headphonemic, void *userdata);
661+
662+pa_backend *pa_backend_new(pa_backend_cb cb, void *cb_userdata);
663+
664+void pa_backend_free(pa_backend *p);
665+
666+void pa_backend_card_changed(pa_backend *p, const pa_card_info *i);
667+
668+void pa_backend_set_context(pa_backend *p, const pa_context *c);
669+
670+void pa_backend_set_port(pa_backend *p, const char *portname, bool is_output);
671+
672+
673+#endif
674
675=== modified file 'po/POTFILES.in'
676--- po/POTFILES.in 2014-02-06 10:43:50 +0000
677+++ po/POTFILES.in 2014-02-18 12:21:20 +0000
678@@ -25,6 +25,7 @@
679 plugins/media-keys/gvc/gvc-mixer-control.c
680 [type: gettext/ini]plugins/media-keys/media-keys.gnome-settings-plugin.in
681 plugins/media-keys/shortcuts-list.h
682+plugins/media-keys/what-did-you-plug-in/dialog-window.c
683 plugins/mouse/gsd-mouse-manager.c
684 [type: gettext/ini]plugins/mouse/mouse.gnome-settings-plugin.in
685 [type: gettext/ini]plugins/orientation/orientation.gnome-settings-plugin.in

Subscribers

People subscribed via source and target branches