1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 /***
4 This file is part of libcanberra.
5
6 Copyright 2008 Lennart Poettering
7
8 libcanberra is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation, either version 2.1 of the
11 License, or (at your option) any later version.
12
13 libcanberra is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with libcanberra. If not, see
20 <http://www.gnu.org/licenses/>.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <gtk/gtk.h>
28 #include <gdk/gdkx.h>
29 #include <X11/Xatom.h>
30
31 #include "canberra-gtk.h"
32
33 typedef struct {
34 guint signal_id;
35 gboolean arg1_is_set;
36 GObject *object;
37 GValue arg1;
38 GdkEvent *event;
39 } SoundEventData;
40
41 /*
42 We generate these sounds:
43
44 dialog-error
45 dialog-warning
46 dialog-information
47 dialog-question
48 window-new
49 window-close
50 window-minimized
51 window-unminimized
52 window-maximized
53 window-unmaximized
54 notebook-tab-changed
55 dialog-ok
56 dialog-cancel
57 item-selected
58 link-pressed
59 link-released
60 button-pressed
61 button-released
62 menu-click
63 button-toggle-on
64 button-toggle-off
65 menu-popup
66 menu-popdown
67 menu-replace
68 tooltip-popup
69 tooltip-popdown
70 drag-start
71 drag-accept
72 drag-fail
73 expander-toggle-on
74 expander-toggle-off
75
76 TODO:
77 scroll-xxx
78 window-switch
79 window-resize-xxx
80 window-move-xxx
81
82 */
83
84 static gboolean disabled = FALSE;
85
86 static GQueue sound_event_queue = G_QUEUE_INIT;
87
88 static guint idle_id = 0;
89
90 static guint
91 signal_id_dialog_response,
92 signal_id_widget_show,
93 signal_id_widget_hide,
94 signal_id_check_menu_item_toggled,
95 signal_id_menu_item_activate,
96 signal_id_toggle_button_toggled,
97 signal_id_button_pressed,
98 signal_id_button_released,
99 signal_id_widget_window_state_event,
100 signal_id_notebook_switch_page,
101 signal_id_tree_view_cursor_changed,
102 signal_id_icon_view_selection_changed,
103 signal_id_widget_drag_begin,
104 signal_id_widget_drag_failed,
105 signal_id_widget_drag_drop,
106 signal_id_expander_activate;
107
108 static GQuark
109 disable_sound_quark,
110 was_iconized_quark,
111 is_xembed_quark;
112
113 /* Make sure GCC doesn't warn us about a missing prototype for this
114 * exported function */
115 void gtk_module_init(gint *argc, gchar ***argv[]);
116
translate_message_tye(GtkMessageType mt)117 static const char *translate_message_tye(GtkMessageType mt) {
118 static const char *const message_type_table[] = {
119 [GTK_MESSAGE_INFO] = "dialog-information",
120 [GTK_MESSAGE_WARNING] = "dialog-warning",
121 [GTK_MESSAGE_QUESTION] = "dialog-question",
122 [GTK_MESSAGE_ERROR] = "dialog-error",
123 [GTK_MESSAGE_OTHER] = NULL
124 };
125
126 if (mt >= G_N_ELEMENTS(message_type_table))
127 return NULL;
128
129 return message_type_table[mt];
130 }
131
translate_response(int response)132 static const char *translate_response(int response) {
133 static const char *const response_table[] = {
134 [-GTK_RESPONSE_NONE] = NULL,
135 [-GTK_RESPONSE_REJECT] = "dialog-cancel",
136 [-GTK_RESPONSE_DELETE_EVENT] = "dialog-cancel",
137 [-GTK_RESPONSE_ACCEPT] = "dialog-ok",
138 [-GTK_RESPONSE_OK] = "dialog-ok",
139 [-GTK_RESPONSE_CANCEL] = "dialog-cancel",
140 [-GTK_RESPONSE_CLOSE] = "dialog-ok",
141 [-GTK_RESPONSE_YES] = "dialog-ok",
142 [-GTK_RESPONSE_NO] = "dialog-cancel",
143 [-GTK_RESPONSE_APPLY] = "dialog-ok",
144 [-GTK_RESPONSE_HELP] = NULL,
145 };
146
147 if (response >= 0)
148 return NULL;
149
150 if ((unsigned) -response >= G_N_ELEMENTS(response_table))
151 return NULL;
152
153 return response_table[-response];
154 }
155
is_child_of_combo_box(GtkWidget * w)156 static gboolean is_child_of_combo_box(GtkWidget *w) {
157
158 while (w) {
159
160 if (GTK_IS_COMBO_BOX(w))
161 return TRUE;
162
163 w = gtk_widget_get_parent(w);
164 }
165
166 return FALSE;
167 }
168
find_parent_dialog(GtkWidget * w)169 static GtkDialog* find_parent_dialog(GtkWidget *w) {
170
171 while (w) {
172
173 if (GTK_IS_DIALOG(w))
174 return GTK_DIALOG(w);
175
176 w = gtk_widget_get_parent(w);
177 }
178
179 return NULL;
180 }
181
free_sound_event(SoundEventData * d)182 static void free_sound_event(SoundEventData *d) {
183
184 g_object_unref(d->object);
185
186 if (d->arg1_is_set)
187 g_value_unset(&d->arg1);
188
189 if (d->event)
190 gdk_event_free(d->event);
191
192 g_slice_free(SoundEventData, d);
193 }
194
is_menu_hint(GdkWindowTypeHint hint)195 static gboolean is_menu_hint(GdkWindowTypeHint hint) {
196 return
197 hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU ||
198 hint == GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU ||
199 hint == GDK_WINDOW_TYPE_HINT_MENU;
200 }
201
filter_sound_event(SoundEventData * d)202 static SoundEventData* filter_sound_event(SoundEventData *d) {
203 GList *i, *n;
204
205 do {
206
207 for (i = sound_event_queue.head; i; i = n) {
208 SoundEventData *j;
209
210 j = i->data;
211 n = i->next;
212
213 if (d->object == j->object) {
214
215 /* Let's drop a show event immediately followed by a
216 * hide event */
217
218 if (d->signal_id == signal_id_widget_show &&
219 j->signal_id == signal_id_widget_hide) {
220
221 free_sound_event(d);
222 free_sound_event(j);
223 g_queue_delete_link(&sound_event_queue, i);
224
225 return NULL;
226 }
227
228 /* Let's drop widget hide events in favour of dialog
229 * response.
230 *
231 * Let's drop widget window state events in favour of
232 * widget hide/show.
233 *
234 * Let's drop double events */
235
236 if ((d->signal_id == signal_id_widget_hide &&
237 j->signal_id == signal_id_dialog_response) ||
238
239 (d->signal_id == signal_id_widget_window_state_event &&
240 j->signal_id == signal_id_widget_hide) ||
241
242 (d->signal_id == signal_id_widget_window_state_event &&
243 j->signal_id == signal_id_widget_show)) {
244
245 free_sound_event(d);
246 d = j;
247 g_queue_delete_link(&sound_event_queue, i);
248 break;
249 }
250
251 if ((d->signal_id == signal_id_dialog_response &&
252 j->signal_id == signal_id_widget_hide) ||
253
254 (d->signal_id == signal_id_widget_show &&
255 j->signal_id == signal_id_widget_window_state_event) ||
256
257 (d->signal_id == signal_id_widget_hide &&
258 j->signal_id == signal_id_widget_window_state_event) ||
259
260 (d->signal_id == j->signal_id)) {
261
262 free_sound_event(j);
263 g_queue_delete_link(&sound_event_queue, i);
264 }
265
266 } else if (GTK_IS_WINDOW(d->object) && GTK_IS_WINDOW(j->object)) {
267
268 GdkWindowTypeHint dhint, jhint;
269
270 dhint = gtk_window_get_type_hint(GTK_WINDOW(d->object));
271 jhint = gtk_window_get_type_hint(GTK_WINDOW(j->object));
272
273 if (is_menu_hint(dhint) && is_menu_hint(jhint)) {
274
275 if (d->signal_id == signal_id_widget_hide &&
276 j->signal_id == signal_id_widget_show) {
277 free_sound_event(d);
278 d = j;
279 g_queue_delete_link(&sound_event_queue, i);
280 break;
281 }
282
283 if (d->signal_id == signal_id_widget_show &&
284 j->signal_id == signal_id_widget_hide) {
285
286 free_sound_event(j);
287 g_queue_delete_link(&sound_event_queue, i);
288 }
289 }
290 }
291 }
292
293 /* If we exited the iteration early, let's retry. */
294
295 } while (i);
296
297 /* FIXME: Filter menu hide on menu show */
298
299 return d;
300 }
301
window_get_desktop(GdkDisplay * d,GdkWindow * w)302 static gint window_get_desktop(GdkDisplay *d, GdkWindow *w) {
303 Atom type_return;
304 gint format_return;
305 gulong nitems_return;
306 gulong bytes_after_return;
307 guchar *data = NULL;
308 gint ret = -1;
309
310 #ifdef GDK_IS_X11_DISPLAY
311 if (!GDK_IS_X11_DISPLAY(d))
312 return 0;
313 #endif
314
315 if (XGetWindowProperty(GDK_DISPLAY_XDISPLAY(d), GDK_WINDOW_XID(w),
316 gdk_x11_get_xatom_by_name_for_display(d, "_NET_WM_DESKTOP"),
317 0, G_MAXLONG, False, XA_CARDINAL, &type_return,
318 &format_return, &nitems_return, &bytes_after_return,
319 &data) != Success)
320 return -1;
321
322 if (type_return == XA_CARDINAL && format_return == 32 && data) {
323 guint32 desktop = *(guint32*) data;
324
325 if (desktop != 0xFFFFFFFF)
326 ret = (gint) desktop;
327 }
328
329 if (type_return != None && data != NULL)
330 XFree(data);
331
332 return ret;
333 }
334
display_get_desktop(GdkDisplay * d)335 static gint display_get_desktop(GdkDisplay *d) {
336 Atom type_return;
337 gint format_return;
338 gulong nitems_return;
339 gulong bytes_after_return;
340 guchar *data = NULL;
341 gint ret = -1;
342
343 #ifdef GDK_IS_X11_DISPLAY
344 if (!GDK_IS_X11_DISPLAY(d))
345 return 0;
346 #endif
347
348 if (XGetWindowProperty(GDK_DISPLAY_XDISPLAY(d), DefaultRootWindow(GDK_DISPLAY_XDISPLAY(d)),
349 gdk_x11_get_xatom_by_name_for_display(d, "_NET_CURRENT_DESKTOP"),
350 0, G_MAXLONG, False, XA_CARDINAL, &type_return,
351 &format_return, &nitems_return, &bytes_after_return,
352 &data) != Success)
353 return -1;
354
355 if (type_return == XA_CARDINAL && format_return == 32 && data) {
356
357 guint32 desktop = *(guint32*) data;
358
359 if (desktop != 0xFFFFFFFF)
360 ret = (gint) desktop;
361 }
362
363 if (type_return != None && data != NULL)
364 XFree(data);
365
366 return ret;
367 }
368
window_is_xembed(GdkDisplay * d,GdkWindow * w)369 static gboolean window_is_xembed(GdkDisplay *d, GdkWindow *w) {
370 Atom type_return;
371 gint format_return;
372 gulong nitems_return;
373 gulong bytes_after_return;
374 guchar *data = NULL;
375 gboolean ret = FALSE;
376 Atom xembed;
377
378 #ifdef GDK_IS_X11_DISPLAY
379 if (!GDK_IS_X11_DISPLAY(d))
380 return FALSE;
381 #endif
382
383 /* Gnome Panel applets are XEMBED windows. We need to make sure we
384 * ignore them */
385
386 xembed = gdk_x11_get_xatom_by_name_for_display(d, "_XEMBED_INFO");
387
388 /* be robust against not existing XIDs (LP: #834403) */
389 gdk_error_trap_push();
390 if (XGetWindowProperty(GDK_DISPLAY_XDISPLAY(d), GDK_WINDOW_XID(w),
391 xembed,
392 0, 2, False, xembed, &type_return,
393 &format_return, &nitems_return, &bytes_after_return,
394 &data) != Success) {
395 return FALSE;
396 }
397
398 #if GTK_CHECK_VERSION(3,0,0)
399 gdk_error_trap_pop_ignored();
400 #else
401 gdk_flush();
402 gdk_error_trap_pop();
403 #endif
404
405 if (type_return == xembed && format_return == 32 && data)
406 ret = TRUE;
407
408 if (type_return != None && data != NULL)
409 XFree(data);
410
411 return ret;
412 }
413
dispatch_sound_event(SoundEventData * d)414 static void dispatch_sound_event(SoundEventData *d) {
415 int ret = CA_SUCCESS;
416 static gboolean menu_is_popped_up = FALSE;
417
418 if (g_object_get_qdata(d->object, disable_sound_quark))
419 return;
420
421 /* The GdkWindow of the the widget might have changed while this
422 * event was queued for us. Make sure to update it from the
423 * current one if necessary. */
424 if (d->event && d->event->any.window) {
425 GdkWindow *window;
426
427 g_object_unref(G_OBJECT(d->event->any.window));
428
429 if ((window = gtk_widget_get_window(GTK_WIDGET(d->object))))
430 d->event->any.window = GDK_WINDOW(g_object_ref(G_OBJECT(window)));
431 else
432 d->event->any.window = NULL;
433 }
434
435 if (d->signal_id == signal_id_widget_show) {
436 GdkWindowTypeHint hint;
437
438 /* Show/hide signals for non-windows have already been filtered out
439 * by the emission hook! */
440
441 hint = gtk_window_get_type_hint(GTK_WINDOW(d->object));
442
443 if (is_menu_hint(hint)) {
444
445 if (!menu_is_popped_up) {
446
447 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
448 CA_PROP_EVENT_ID, "menu-popup",
449 CA_PROP_EVENT_DESCRIPTION, "Menu popped up",
450 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
451 NULL);
452 } else {
453 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
454 CA_PROP_EVENT_ID, "menu-replace",
455 CA_PROP_EVENT_DESCRIPTION, "Menu replaced",
456 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
457 NULL);
458 }
459
460 menu_is_popped_up = TRUE;
461
462 } else if (hint == GDK_WINDOW_TYPE_HINT_TOOLTIP) {
463
464 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
465 CA_PROP_EVENT_ID, "tooltip-popup",
466 CA_PROP_EVENT_DESCRIPTION, "Tooltip popped up",
467 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
468 NULL);
469
470 } else if (hint == GDK_WINDOW_TYPE_HINT_NORMAL ||
471 hint == GDK_WINDOW_TYPE_HINT_DIALOG) {
472
473 gboolean played_sound = FALSE;
474 gboolean is_xembed;
475
476 is_xembed =
477 gtk_widget_get_realized(GTK_WIDGET(d->object)) &&
478 window_is_xembed(
479 gtk_widget_get_display(GTK_WIDGET(d->object)),
480 gtk_widget_get_window(GTK_WIDGET(d->object)));
481
482 g_object_set_qdata(d->object, is_xembed_quark, GINT_TO_POINTER(is_xembed));
483
484 if (GTK_IS_MESSAGE_DIALOG(d->object)) {
485 GtkMessageType mt;
486 const char *id;
487
488 g_object_get(d->object, "message_type", &mt, NULL);
489
490 if ((id = translate_message_tye(mt))) {
491
492 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
493 CA_PROP_EVENT_ID, id,
494 CA_PROP_EVENT_DESCRIPTION, "Message dialog shown",
495 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
496 NULL);
497 played_sound = TRUE;
498 }
499
500 }
501
502 if (!played_sound &&
503 !is_xembed &&
504 gtk_window_get_decorated(GTK_WINDOW(d->object))) {
505
506 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
507 CA_PROP_EVENT_ID, "window-new",
508 CA_PROP_EVENT_DESCRIPTION, "Window shown",
509 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
510 NULL);
511
512 }
513 }
514 }
515
516 if (GTK_IS_DIALOG(d->object) && d->signal_id == signal_id_dialog_response) {
517
518 int response;
519 const char *id;
520
521 response = g_value_get_int(&d->arg1);
522
523 if ((id = translate_response(response))) {
524
525 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
526 CA_PROP_EVENT_ID, id,
527 CA_PROP_EVENT_DESCRIPTION, "Dialog closed",
528 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
529 NULL);
530 } else {
531 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
532 CA_PROP_EVENT_ID, "window-close",
533 CA_PROP_EVENT_DESCRIPTION, "Window closed",
534 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
535 NULL);
536 }
537
538 } else if (d->signal_id == signal_id_widget_hide) {
539 GdkWindowTypeHint hint;
540
541 hint = gtk_window_get_type_hint(GTK_WINDOW(d->object));
542
543 if (is_menu_hint(hint)) {
544
545 if (GTK_IS_MENU(gtk_bin_get_child(GTK_BIN(d->object)))) {
546
547 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
548 CA_PROP_EVENT_ID, "menu-popdown",
549 CA_PROP_EVENT_DESCRIPTION, "Menu popped down",
550 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
551 NULL);
552 }
553
554 menu_is_popped_up = FALSE;
555
556 } else if (hint == GDK_WINDOW_TYPE_HINT_TOOLTIP) {
557
558 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
559 CA_PROP_EVENT_ID, "tooltip-popdown",
560 CA_PROP_EVENT_DESCRIPTION, "Tooltip popped down",
561 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
562 NULL);
563
564 } else if ((hint == GDK_WINDOW_TYPE_HINT_NORMAL ||
565 hint == GDK_WINDOW_TYPE_HINT_DIALOG)) {
566
567 gboolean is_xembed;
568
569 is_xembed = !!g_object_get_qdata(d->object, is_xembed_quark);
570
571 if (!is_xembed &&
572 gtk_window_get_decorated(GTK_WINDOW(d->object)))
573 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
574 CA_PROP_EVENT_ID, "window-close",
575 CA_PROP_EVENT_DESCRIPTION, "Window closed",
576 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
577 NULL);
578 }
579 }
580
581 if (GTK_IS_WINDOW(d->object) && d->signal_id == signal_id_widget_window_state_event) {
582 GdkEventWindowState *e;
583 gint w_desktop = -1, c_desktop = -1;
584
585 e = (GdkEventWindowState*) d->event;
586
587 /* Unfortunately GDK_WINDOW_STATE_ICONIFIED is used both for
588 * proper minimizing and when a window becomes invisible
589 * because the desktop was switched. To handle this we check
590 * if the window becoming invisible is actually on the current
591 * desktop, and only if that's the case we assume it is being
592 * minimized. We then store this information, so that we know
593 * later on when the window is unminimized again. */
594
595 if (gtk_widget_get_realized(GTK_WIDGET(d->object))) {
596 GdkDisplay *display;
597
598 display = gtk_widget_get_display(GTK_WIDGET(d->object));
599 w_desktop = window_get_desktop(display, gtk_widget_get_window(GTK_WIDGET(d->object)));
600 c_desktop = display_get_desktop(display);
601 }
602
603 if ((e->changed_mask & GDK_WINDOW_STATE_ICONIFIED) &&
604 (e->new_window_state & GDK_WINDOW_STATE_ICONIFIED) &&
605 (w_desktop == c_desktop || w_desktop < 0)) {
606
607 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
608 CA_PROP_EVENT_ID, "window-minimized",
609 CA_PROP_EVENT_DESCRIPTION, "Window minimized",
610 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
611 NULL);
612
613 g_object_set_qdata(d->object, was_iconized_quark, GINT_TO_POINTER(1));
614
615 } else if ((e->changed_mask & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN)) &&
616 (e->new_window_state & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN))) {
617
618 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
619 CA_PROP_EVENT_ID, "window-maximized",
620 CA_PROP_EVENT_DESCRIPTION, "Window maximized",
621 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
622 NULL);
623
624 g_object_set_qdata(d->object, was_iconized_quark, GINT_TO_POINTER(0));
625
626 } else if ((e->changed_mask & GDK_WINDOW_STATE_ICONIFIED) &&
627 !(e->new_window_state & GDK_WINDOW_STATE_ICONIFIED) &&
628 g_object_get_qdata(d->object, was_iconized_quark)) {
629
630 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
631 CA_PROP_EVENT_ID, "window-unminimized",
632 CA_PROP_EVENT_DESCRIPTION, "Window unminimized",
633 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
634 NULL);
635
636 g_object_set_qdata(d->object, was_iconized_quark, GINT_TO_POINTER(0));
637
638 } else if ((e->changed_mask & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN)) &&
639 !(e->new_window_state & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN))) {
640
641 ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0,
642 CA_PROP_EVENT_ID, "window-unmaximized",
643 CA_PROP_EVENT_DESCRIPTION, "Window unmaximized",
644 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
645 NULL);
646 }
647 }
648
649 if (GTK_IS_CHECK_MENU_ITEM(d->object) && d->signal_id == signal_id_check_menu_item_toggled) {
650
651 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(d->object)))
652 ret = ca_gtk_play_for_event(d->event, 0,
653 CA_PROP_EVENT_ID, "button-toggle-on",
654 CA_PROP_EVENT_DESCRIPTION, "Check menu item checked",
655 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
656 NULL);
657 else
658 ret = ca_gtk_play_for_event(d->event, 0,
659 CA_PROP_EVENT_ID, "button-toggle-off",
660 CA_PROP_EVENT_DESCRIPTION, "Check menu item unchecked",
661 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
662 NULL);
663
664 } else if (GTK_IS_MENU_ITEM(d->object) && d->signal_id == signal_id_menu_item_activate) {
665
666 if (!gtk_menu_item_get_submenu(GTK_MENU_ITEM(d->object)))
667 ret = ca_gtk_play_for_event(d->event, 0,
668 CA_PROP_EVENT_ID, "menu-click",
669 CA_PROP_EVENT_DESCRIPTION, "Menu item clicked",
670 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
671 NULL);
672 }
673
674 if (GTK_IS_TOGGLE_BUTTON(d->object)) {
675
676 if (d->signal_id == signal_id_toggle_button_toggled) {
677
678 if (!is_child_of_combo_box(GTK_WIDGET(d->object))) {
679
680 /* We don't want to play this sound if this is a toggle
681 * button belonging to combo box. */
682
683 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->object)))
684 ret = ca_gtk_play_for_event(d->event, 0,
685 CA_PROP_EVENT_ID, "button-toggle-on",
686 CA_PROP_EVENT_DESCRIPTION, "Toggle button checked",
687 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
688 NULL);
689 else
690 ret = ca_gtk_play_for_event(d->event, 0,
691 CA_PROP_EVENT_ID, "button-toggle-off",
692 CA_PROP_EVENT_DESCRIPTION, "Toggle button unchecked",
693 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
694 NULL);
695 }
696 }
697
698 } else if (GTK_IS_LINK_BUTTON(d->object)) {
699
700 if (d->signal_id == signal_id_button_pressed) {
701 ret = ca_gtk_play_for_event(d->event, 0,
702 CA_PROP_EVENT_ID, "link-pressed",
703 CA_PROP_EVENT_DESCRIPTION, "Link pressed",
704 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
705 NULL);
706
707 } else if (d->signal_id == signal_id_button_released) {
708
709 ret = ca_gtk_play_for_event(d->event, 0,
710 CA_PROP_EVENT_ID, "link-released",
711 CA_PROP_EVENT_DESCRIPTION, "Link released",
712 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
713 NULL);
714 }
715
716 } else if (GTK_IS_BUTTON(d->object) && !GTK_IS_TOGGLE_BUTTON(d->object)) {
717
718 if (d->signal_id == signal_id_button_pressed) {
719 ret = ca_gtk_play_for_event(d->event, 0,
720 CA_PROP_EVENT_ID, "button-pressed",
721 CA_PROP_EVENT_DESCRIPTION, "Button pressed",
722 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
723 NULL);
724
725 } else if (d->signal_id == signal_id_button_released) {
726 GtkDialog *dialog;
727 gboolean dont_play = FALSE;
728
729 if ((dialog = find_parent_dialog(GTK_WIDGET(d->object)))) {
730 int response;
731
732 /* Don't play the click sound if this is a response widget
733 * we will generate a dialog-xxx event sound anyway. */
734
735 response = gtk_dialog_get_response_for_widget(dialog, GTK_WIDGET(d->object));
736 dont_play = !!translate_response(response);
737 }
738
739 if (!dont_play)
740 ret = ca_gtk_play_for_event(d->event, 0,
741 CA_PROP_EVENT_ID, "button-released",
742 CA_PROP_EVENT_DESCRIPTION, "Button released",
743 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
744 NULL);
745 }
746 }
747
748 if (GTK_IS_NOTEBOOK(d->object) && d->signal_id == signal_id_notebook_switch_page) {
749 ret = ca_gtk_play_for_event(d->event, 0,
750 CA_PROP_EVENT_ID, "notebook-tab-changed",
751 CA_PROP_EVENT_DESCRIPTION, "Tab changed",
752 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
753 NULL);
754 goto finish;
755 }
756
757 if (GTK_IS_TREE_VIEW(d->object) && d->signal_id == signal_id_tree_view_cursor_changed) {
758 ret = ca_gtk_play_for_event(d->event, 0,
759 CA_PROP_EVENT_ID, "item-selected",
760 CA_PROP_EVENT_DESCRIPTION, "Item selected",
761 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
762 NULL);
763 goto finish;
764 }
765
766 if (GTK_IS_ICON_VIEW(d->object) && d->signal_id == signal_id_icon_view_selection_changed) {
767 ret = ca_gtk_play_for_event(d->event, 0,
768 CA_PROP_EVENT_ID, "item-selected",
769 CA_PROP_EVENT_DESCRIPTION, "Item selected",
770 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
771 NULL);
772 goto finish;
773 }
774
775 if (GTK_IS_EXPANDER(d->object) && d->signal_id == signal_id_expander_activate) {
776
777 if (gtk_expander_get_expanded(GTK_EXPANDER(d->object)))
778 ret = ca_gtk_play_for_event(d->event, 0,
779 CA_PROP_EVENT_ID, "expander-toggle-on",
780 CA_PROP_EVENT_DESCRIPTION, "Expander expanded",
781 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
782 NULL);
783 else
784 ret = ca_gtk_play_for_event(d->event, 0,
785 CA_PROP_EVENT_ID, "expander-toggle-off",
786 CA_PROP_EVENT_DESCRIPTION, "Expander unexpanded",
787 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
788 NULL);
789
790 goto finish;
791 }
792
793 if (GTK_IS_WIDGET(d->object)) {
794
795 if (d->signal_id == signal_id_widget_drag_begin) {
796
797 ret = ca_gtk_play_for_event(d->event, 0,
798 CA_PROP_EVENT_ID, "drag-start",
799 CA_PROP_EVENT_DESCRIPTION, "Drag started",
800 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
801 NULL);
802 goto finish;
803
804 } else if (d->signal_id == signal_id_widget_drag_drop) {
805
806 ret = ca_gtk_play_for_event(d->event, 0,
807 CA_PROP_EVENT_ID, "drag-accept",
808 CA_PROP_EVENT_DESCRIPTION, "Drag accepted",
809 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
810 NULL);
811 goto finish;
812
813 } else if (d->signal_id == signal_id_widget_drag_failed) {
814
815 ret = ca_gtk_play_for_event(d->event, 0,
816 CA_PROP_EVENT_ID, "drag-fail",
817 CA_PROP_EVENT_DESCRIPTION, "Drag failed",
818 CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
819 NULL);
820 goto finish;
821 }
822 }
823
824 finish:
825
826 ;
827 /* if (ret != CA_SUCCESS) */
828 /* g_warning("Failed to play event sound: %s", ca_strerror(ret)); */
829 }
830
dispatch_queue(void)831 static void dispatch_queue(void) {
832 SoundEventData *d;
833
834 while ((d = g_queue_pop_head(&sound_event_queue))) {
835
836 if (!(d = filter_sound_event(d)))
837 continue;
838
839 dispatch_sound_event(d);
840 free_sound_event(d);
841 }
842 }
843
idle_cb(void * userdata)844 static gboolean idle_cb(void *userdata) {
845 idle_id = 0;
846
847 dispatch_queue();
848
849 return FALSE;
850 }
851
852 static void connect_settings(void);
853
emission_hook_cb(GSignalInvocationHint * hint,guint n_param_values,const GValue * param_values,gpointer data)854 static gboolean emission_hook_cb(GSignalInvocationHint *hint, guint n_param_values, const GValue *param_values, gpointer data) {
855 static SoundEventData *d = NULL;
856 GdkEvent *e;
857 GObject *object;
858
859 connect_settings();
860
861 if (disabled)
862 return TRUE;
863
864 object = g_value_get_object(¶m_values[0]);
865
866 /* g_message("signal '%s' on object of type '%s' with name '%s'", */
867 /* g_signal_name(hint->signal_id), */
868 /* G_OBJECT_TYPE_NAME(object), */
869 /* gtk_widget_get_name(GTK_WIDGET(object))); */
870
871 /* if (GTK_IS_WINDOW(object)) */
872 /* g_message("window role='%s' title='%s' type='%u'", */
873 /* gtk_window_get_role(GTK_WINDOW(object)), */
874 /* gtk_window_get_title(GTK_WINDOW(object)), */
875 /* gtk_window_get_type_hint(GTK_WINDOW(object))); */
876
877 /* Filter a few very often occuring signals as quickly as possible */
878 if ((hint->signal_id == signal_id_widget_hide ||
879 hint->signal_id == signal_id_widget_show ||
880 hint->signal_id == signal_id_widget_window_state_event) &&
881 !GTK_IS_WINDOW(object))
882 return TRUE;
883
884 if (hint->signal_id != signal_id_widget_hide &&
885 hint->signal_id != signal_id_dialog_response &&
886 !gtk_widget_is_drawable(GTK_WIDGET (object)))
887 return TRUE;
888
889 d = g_slice_new0(SoundEventData);
890
891 d->object = g_object_ref(object);
892
893 d->signal_id = hint->signal_id;
894
895 if (d->signal_id == signal_id_widget_window_state_event) {
896 d->event = gdk_event_copy(g_value_peek_pointer(¶m_values[1]));
897 } else if ((e = gtk_get_current_event()))
898 d->event = gdk_event_copy(e);
899
900 if (n_param_values > 1) {
901 g_value_init(&d->arg1, G_VALUE_TYPE(¶m_values[1]));
902 g_value_copy(¶m_values[1], &d->arg1);
903 d->arg1_is_set = TRUE;
904 }
905
906 g_queue_push_tail(&sound_event_queue, d);
907
908 if (idle_id == 0)
909 idle_id = gdk_threads_add_idle_full(GDK_PRIORITY_REDRAW-1, (GSourceFunc) idle_cb, NULL, NULL);
910
911 return TRUE;
912 }
913
install_hook(GType type,const char * sig,guint * sn)914 static void install_hook(GType type, const char *sig, guint *sn) {
915 GTypeClass *type_class;
916
917 type_class = g_type_class_ref(type);
918
919 *sn = g_signal_lookup(sig, type);
920 g_signal_add_emission_hook(*sn, 0, emission_hook_cb, NULL, NULL);
921
922 g_type_class_unref(type_class);
923 }
924
read_enable_input_feedback_sounds(GtkSettings * s)925 static void read_enable_input_feedback_sounds(GtkSettings *s) {
926 gboolean enabled = !disabled;
927
928 if (g_getenv("CANBERRA_FORCE_INPUT_FEEDBACK_SOUNDS"))
929 disabled = FALSE;
930 else {
931 g_object_get(G_OBJECT(s), "gtk-enable-input-feedback-sounds", &enabled, NULL);
932 disabled = !enabled;
933 }
934 }
935
enable_input_feedback_sounds_changed(GtkSettings * s,GParamSpec * arg1,gpointer userdata)936 static void enable_input_feedback_sounds_changed(GtkSettings *s, GParamSpec *arg1, gpointer userdata) {
937 read_enable_input_feedback_sounds(s);
938 }
939
connect_settings(void)940 static void connect_settings(void) {
941 GtkSettings *s;
942 static gboolean connected = FALSE;
943
944 if (connected)
945 return;
946
947 if (!(s = gtk_settings_get_default()))
948 return;
949
950 if (g_object_class_find_property(G_OBJECT_GET_CLASS(s), "gtk-enable-input-feedback-sounds")) {
951 g_signal_connect(G_OBJECT(s), "notify::gtk-enable-input-feedback-sounds", G_CALLBACK(enable_input_feedback_sounds_changed), NULL);
952 read_enable_input_feedback_sounds(s);
953 } else
954 g_debug("This Gtk+ version doesn't have the GtkSettings::gtk-enable-input-feedback-sounds property.");
955
956 connected = TRUE;
957 }
958
959 #if GTK_CHECK_VERSION(3,0,0)
960 #warning "We really need a quit handler in Gtk 3.0, https://bugzilla.gnome.org/show_bug.cgi?id=639770"
961 #else
quit_handler(gpointer data)962 static gboolean quit_handler(gpointer data) {
963 dispatch_queue();
964 return FALSE;
965 }
966 #endif
967
gtk_module_init(gint * argc,gchar *** argv[])968 G_MODULE_EXPORT void gtk_module_init(gint *argc, gchar ***argv[]) {
969
970 /* This is the same quark libgnomeui uses! */
971 disable_sound_quark = g_quark_from_string("gnome_disable_sound_events");
972 was_iconized_quark = g_quark_from_string("canberra_was_iconized");
973 is_xembed_quark = g_quark_from_string("canberra_is_xembed");
974
975 /* Hook up the gtk setting */
976 connect_settings();
977
978 install_hook(GTK_TYPE_WINDOW, "show", &signal_id_widget_show);
979 install_hook(GTK_TYPE_WINDOW, "hide", &signal_id_widget_hide);
980 install_hook(GTK_TYPE_DIALOG, "response", &signal_id_dialog_response);
981 install_hook(GTK_TYPE_MENU_ITEM, "activate", &signal_id_menu_item_activate);
982 install_hook(GTK_TYPE_CHECK_MENU_ITEM, "toggled", &signal_id_check_menu_item_toggled);
983 install_hook(GTK_TYPE_TOGGLE_BUTTON, "toggled", &signal_id_toggle_button_toggled);
984 install_hook(GTK_TYPE_BUTTON, "pressed", &signal_id_button_pressed);
985 install_hook(GTK_TYPE_BUTTON, "released", &signal_id_button_released);
986 install_hook(GTK_TYPE_WIDGET, "window-state-event", &signal_id_widget_window_state_event);
987 install_hook(GTK_TYPE_NOTEBOOK, "switch-page", &signal_id_notebook_switch_page);
988 install_hook(GTK_TYPE_TREE_VIEW, "cursor-changed", &signal_id_tree_view_cursor_changed);
989 install_hook(GTK_TYPE_ICON_VIEW, "selection-changed", &signal_id_icon_view_selection_changed);
990 install_hook(GTK_TYPE_WIDGET, "drag-begin", &signal_id_widget_drag_begin);
991 install_hook(GTK_TYPE_WIDGET, "drag-drop", &signal_id_widget_drag_drop);
992 install_hook(GTK_TYPE_WIDGET, "drag-failed", &signal_id_widget_drag_failed);
993 install_hook(GTK_TYPE_EXPANDER, "activate", &signal_id_expander_activate);
994
995 #if !GTK_CHECK_VERSION(3,0,0)
996 gtk_quit_add(1, quit_handler, NULL);
997 #endif
998 }
999
1000 G_MODULE_EXPORT gchar* g_module_check_init(GModule *module);
1001
g_module_check_init(GModule * module)1002 G_MODULE_EXPORT gchar* g_module_check_init(GModule *module) {
1003 g_module_make_resident(module);
1004 return NULL;
1005 }
1006