From 7706a101a6f7928cf7e3fc63b979352b3af214b3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 Jun 2008 17:27:43 +0000 Subject: add complete documentation git-svn-id: file:///home/lennart/svn/public/libcanberra/trunk@57 01b60673-d06a-42c0-afdd-89cb8e0f78ac --- Makefile.am | 4 +- bootstrap.sh | 3 +- configure.ac | 5 + doc/Makefile.am | 87 +++++++++ doc/libcanberra-docs.sgml | 19 ++ doc/libcanberra-sections.txt | 77 ++++++++ doc/libcanberra.types | 0 src/canberra-gtk-module.c | 328 ++++++++++++++++++++++++++------- src/canberra-gtk.c | 100 ++++++++++ src/canberra-gtk.h | 6 +- src/canberra.h | 430 ++++++++++++++++++++++++++++++++----------- src/common.c | 293 ++++++++++++++++++++++++++++- src/malloc.h | 2 +- src/proplist.c | 54 ++++++ src/pulse.c | 2 +- 15 files changed, 1223 insertions(+), 187 deletions(-) create mode 100644 doc/Makefile.am create mode 100644 doc/libcanberra-docs.sgml create mode 100644 doc/libcanberra-sections.txt create mode 100644 doc/libcanberra.types diff --git a/Makefile.am b/Makefile.am index 0c85fe2..efd7442 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,7 +19,7 @@ # . EXTRA_DIST = bootstrap.sh LGPL -SUBDIRS = src +SUBDIRS = src doc MAINTAINERCLEANFILES = noinst_DATA = @@ -30,3 +30,5 @@ pkgconfig_DATA = libcanberra.pc if HAVE_GTK pkgconfig_DATA += libcanberra-gtk.pc endif + +DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc diff --git a/bootstrap.sh b/bootstrap.sh index 187121b..0dc9df4 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -54,6 +54,7 @@ else touch config.rpath test "x$LIBTOOLIZE" = "x" && LIBTOOLIZE=libtoolize + gtkdocize --copy --flavour no-tmpl "$LIBTOOLIZE" -c --force --ltdl run_versioned aclocal "$VERSION" run_versioned autoconf 2.59 -Wall @@ -61,7 +62,7 @@ else run_versioned automake "$VERSION" --copy --foreign --add-missing if test "x$NOCONFIGURE" = "x"; then - CFLAGS="-g -O0" ./configure --sysconfdir=/etc --localstatedir=/var "$@" + CFLAGS="-g -O0" ./configure --sysconfdir=/etc --localstatedir=/var --enable-gtk-doc "$@" make clean fi fi diff --git a/configure.ac b/configure.ac index 0e7f310..a2e0069 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,8 @@ AC_PREREQ(2.57) AC_INIT([libcanberra], 0.1, [mzyvopnaoreen (at) 0pointer (dot) de]) AC_CONFIG_SRCDIR([src/common.c]) AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR(m4) + AM_INIT_AUTOMAKE([foreign -Wall]) AC_SUBST(PACKAGE_URL, [https://tango.0pointer.de/mailman/listinfo/libcanberra-discuss/]) @@ -394,6 +396,8 @@ AM_CONDITIONAL([BUILTIN_PULSE], [test "x$BUILTIN_PULSE" = x1]) AM_CONDITIONAL([BUILTIN_ALSA], [test "x$BUILTIN_ALSA" = x1]) AM_CONDITIONAL([BUILTIN_NULL], [test "x$BUILTIN_NULL" = x1]) +GTK_DOC_CHECK(1.9) + ################################### # Output # ################################### @@ -403,6 +407,7 @@ Makefile src/Makefile libcanberra.pc libcanberra-gtk.pc +doc/Makefile ]) AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..963a93d --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,87 @@ +## Process this file with automake to produce Makefile.in + +# We require automake 1.6 at least. +AUTOMAKE_OPTIONS = 1.6 + +# This is a blank Makefile.am for using gtk-doc. +# Copy this to your project's API docs directory and modify the variables to +# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples +# of using the various options. + +# The name of the module, e.g. 'glib'. +DOC_MODULE=libcanberra + +# The top-level SGML file. You can change this if you want to. +DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml + +# The directory containing the source code. Relative to $(srcdir). +# gtk-doc will search all .c & .h files beneath here for inline comments +# documenting the functions and macros. +# e.g. DOC_SOURCE_DIR=../../../gtk +DOC_SOURCE_DIR=../src + +# Extra options to pass to gtkdoc-scangobj. Not normally needed. +SCANGOBJ_OPTIONS= + +# Extra options to supply to gtkdoc-scan. +# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" +SCAN_OPTIONS= + +# Extra options to supply to gtkdoc-mkdb. +# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml +MKDB_OPTIONS=--sgml-mode --output-format=xml + +# Extra options to supply to gtkdoc-mktmpl +# e.g. MKTMPL_OPTIONS=--only-section-tmpl +MKTMPL_OPTIONS= + +# Extra options to supply to gtkdoc-fixref. Not normally needed. +# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html +FIXXREF_OPTIONS= + +# Used for dependencies. The docs will be rebuilt if any of these change. +# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h +# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c +HFILE_GLOB= +CFILE_GLOB= + +# Header files to ignore when scanning. +# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h +IGNORE_HFILES= + +# Images to copy into HTML directory. +# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png +HTML_IMAGES= + +# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). +# e.g. content_files=running.sgml building.sgml changes-2.0.sgml +content_files= + +# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded +# These files must be listed here *and* in content_files +# e.g. expand_content_files=running.sgml +expand_content_files= + +# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. +# Only needed if you are using gtkdoc-scangobj to dynamically query widget +# signals and properties. +# e.g. INCLUDES=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) +# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) +INCLUDES= +GTKDOC_LIBS= + +# This includes the standard gtk-doc make rules, copied by gtkdocize. +include $(top_srcdir)/gtk-doc.make + +# Other files to distribute +# e.g. EXTRA_DIST += version.xml.in +EXTRA_DIST += + +# Files not to distribute +# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types +# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt +#DISTCLEANFILES += + +# Comment this out if you want your docs-status tested during 'make check' +#TESTS = $(GTKDOC_CHECK) + diff --git a/doc/libcanberra-docs.sgml b/doc/libcanberra-docs.sgml new file mode 100644 index 0000000..6505314 --- /dev/null +++ b/doc/libcanberra-docs.sgml @@ -0,0 +1,19 @@ + + + + + libcanberra Reference Manual + + for libcanberra 0.1 + The latest version of this documentation can be found on-line at + http://0pointer.de/projects/libcanberra/doc/index.html. + + + + + libcanberra + + + + diff --git a/doc/libcanberra-sections.txt b/doc/libcanberra-sections.txt new file mode 100644 index 0000000..326faa1 --- /dev/null +++ b/doc/libcanberra-sections.txt @@ -0,0 +1,77 @@ +
+canberra-gtk +ca_gtk_context_get +ca_gtk_proplist_set_for_widget +ca_gtk_play_for_widget +ca_gtk_proplist_set_for_event +ca_gtk_play_for_event +ca_gtk_widget_disable_sounds +
+ +
+canberra +CA_PROP_MEDIA_NAME +CA_PROP_MEDIA_TITLE +CA_PROP_MEDIA_ARTIST +CA_PROP_MEDIA_LANGUAGE +CA_PROP_MEDIA_FILENAME +CA_PROP_MEDIA_ICON +CA_PROP_MEDIA_ICON_NAME +CA_PROP_MEDIA_ROLE +CA_PROP_EVENT_ID +CA_PROP_EVENT_DESCRIPTION +CA_PROP_EVENT_MOUSE_X +CA_PROP_EVENT_MOUSE_Y +CA_PROP_EVENT_MOUSE_HPOS +CA_PROP_EVENT_MOUSE_VPOS +CA_PROP_EVENT_MOUSE_BUTTON +CA_PROP_WINDOW_NAME +CA_PROP_WINDOW_ID +CA_PROP_WINDOW_ICON +CA_PROP_WINDOW_ICON_NAME +CA_PROP_WINDOW_X11_DISPLAY +CA_PROP_WINDOW_X11_SCREEN +CA_PROP_WINDOW_X11_MONITOR +CA_PROP_WINDOW_X11_XID +CA_PROP_APPLICATION_NAME +CA_PROP_APPLICATION_ID +CA_PROP_APPLICATION_VERSION +CA_PROP_APPLICATION_ICON +CA_PROP_APPLICATION_ICON_NAME +CA_PROP_APPLICATION_LANGUAGE +CA_PROP_APPLICATION_PROCESS_ID +CA_PROP_APPLICATION_PROCESS_BINARY +CA_PROP_APPLICATION_PROCESS_USER +CA_PROP_APPLICATION_PROCESS_HOST +CA_PROP_CANBERRA_CACHE_CONTROL +CA_PROP_CANBERRA_VOLUME +CA_PROP_CANBERRA_XDG_THEME_NAME +CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE + + +ca_context +ca_finish_callback_t +ca_context_create +ca_context_destroy +ca_context_open +ca_context_set_driver +ca_context_change_device +ca_context_change_props +ca_context_change_props_full +ca_context_play +ca_context_play_full +ca_context_cancel +ca_context_cache +ca_context_cache_full + + +ca_strerror + + +ca_proplist +ca_proplist_create +ca_proplist_destroy +ca_proplist_sets +ca_proplist_setf +ca_proplist_set +
diff --git a/doc/libcanberra.types b/doc/libcanberra.types new file mode 100644 index 0000000..e69de29 diff --git a/src/canberra-gtk-module.c b/src/canberra-gtk-module.c index 2efe957..c241f1f 100644 --- a/src/canberra-gtk-module.c +++ b/src/canberra-gtk-module.c @@ -36,6 +36,41 @@ typedef struct { GdkEvent *event; } SoundEventData; +/* + We generate these sounds: + + dialog-error + dialog-warning + dialog-information + dialog-question + window-new + window-close + window-minimized + window-unminimized + window-maximized + window-unmaximized + notebook-tab-changed + dialog-ok + dialog-cancel + item-selected + link-pressed + link-released + button-pressed + button-released + menu-click + button-toggle-on + button-toggle-off + menu-popup + menu-popdown + menu-replace + + TODO: + drag-start + drag-accept + drag-fail + +*/ + static GQueue sound_event_queue = G_QUEUE_INIT; static int idle_id = 0; @@ -47,7 +82,11 @@ static guint signal_id_check_menu_item_toggled, signal_id_menu_item_activate, signal_id_toggle_button_toggled, - signal_id_button_clicked; + signal_id_button_pressed, + signal_id_button_released, + signal_id_widget_window_state_event, + signal_id_notebook_switch_page, + signal_id_tree_view_move_cursor; static GQuark disable_sound_quark; @@ -133,14 +172,50 @@ static void free_sound_event(SoundEventData *d) { g_slice_free(SoundEventData, d); } -static void filter_sound_events(SoundEventData *d) { +static SoundEventData* filter_sound_event(SoundEventData *d) { + GList *i; + + do { + + for (i = sound_event_queue.head; i; i = i->next) { + SoundEventData *j = i->data; + + if (d->object == j->object) { + + /* Let's drop widget hide events in favour of dialog + * response */ + + if (d->signal_id == signal_id_widget_hide && + j->signal_id == signal_id_dialog_response) { + + free_sound_event(d); + d = j; + g_queue_delete_link(&sound_event_queue, i); + break; + } + + + if (d->signal_id == signal_id_dialog_response && + j->signal_id == signal_id_widget_hide) { + + free_sound_event(j); + g_queue_delete_link(&sound_event_queue, i); + } + } + } + + /* If we exited the iteration early, let's retry. */ + } while (i); + /* FIXME: Filter menu hide on menu show */ + return d; } static void dispatch_sound_event(SoundEventData *d) { int ret = CA_SUCCESS; + static gboolean menu_is_popped_up = FALSE; if (!GTK_WIDGET_DRAWABLE(d->object)) return; @@ -171,12 +246,22 @@ static void dispatch_sound_event(SoundEventData *d) { } else if (GTK_IS_MENU(gtk_bin_get_child(GTK_BIN(d->object)))) { - ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, - CA_PROP_EVENT_ID, "menu-popup", - CA_PROP_EVENT_DESCRIPTION, "Menu popped up", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); + if (menu_is_popped_up) { + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, "menu-popup", + CA_PROP_EVENT_DESCRIPTION, "Menu popped up", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } else { + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, "menu-replace", + CA_PROP_EVENT_DESCRIPTION, "Menu replaced", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } + played_sound = TRUE; + menu_is_popped_up = TRUE; } if (!played_sound) @@ -187,18 +272,41 @@ static void dispatch_sound_event(SoundEventData *d) { NULL); } - if (d->signal_id == signal_id_widget_hide) { + if (GTK_IS_DIALOG(d->object) && d->signal_id == signal_id_dialog_response) { + + int response; + const char *id; + + response = g_value_get_int(&d->arg1); + + if ((id = translate_response(response))) { + + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, id, + CA_PROP_EVENT_DESCRIPTION, "Dialog closed", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } else { + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, "window-close", + CA_PROP_EVENT_DESCRIPTION, "Window closed", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } + + } else if (d->signal_id == signal_id_widget_hide) { gboolean played_sound = FALSE; if (GTK_IS_MENU(gtk_bin_get_child(GTK_BIN(d->object)))) { ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, CA_PROP_EVENT_ID, "menu-popdown", - CA_PROP_EVENT_DESCRIPTION, "Menu popped up", + CA_PROP_EVENT_DESCRIPTION, "Menu popped down", CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", NULL); played_sound = TRUE; + menu_is_popped_up = FALSE; } if (!played_sound) @@ -209,18 +317,39 @@ static void dispatch_sound_event(SoundEventData *d) { NULL); } - if (GTK_IS_DIALOG(d->object) && d->signal_id == signal_id_dialog_response) { + if (GTK_IS_WINDOW(d->object) && d->signal_id == signal_id_widget_window_state_event) { + GdkEventWindowState *e; - int response; - const char *id; + e = (GdkEventWindowState*) d->event; - response = g_value_get_int(&d->arg1); + if ((e->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && (e->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) { - if ((id = translate_response(response))) { + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, "window-minimized", + CA_PROP_EVENT_DESCRIPTION, "Window minimized", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + + } else if ((e->changed_mask & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN)) && (e->new_window_state & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN))) { ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, - CA_PROP_EVENT_ID, id, - CA_PROP_EVENT_DESCRIPTION, "Dialog closed", + CA_PROP_EVENT_ID, "window-maximized", + CA_PROP_EVENT_DESCRIPTION, "Window maximized", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + + } else if ((e->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && !(e->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) { + + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, "window-unminimized", + CA_PROP_EVENT_DESCRIPTION, "Window unminimized", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } else if ((e->changed_mask & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN)) && !(e->new_window_state & (GDK_WINDOW_STATE_MAXIMIZED|GDK_WINDOW_STATE_FULLSCREEN))) { + + ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, + CA_PROP_EVENT_ID, "window-unmaximized", + CA_PROP_EVENT_DESCRIPTION, "Window unmaximized", CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", NULL); } @@ -229,69 +358,116 @@ static void dispatch_sound_event(SoundEventData *d) { if (GTK_IS_CHECK_MENU_ITEM(d->object) && d->signal_id == signal_id_check_menu_item_toggled) { if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(d))) - ret = ca_gtk_play_for_widget(GTK_WIDGET(d), 0, - CA_PROP_EVENT_ID, "button-toggle-on", - CA_PROP_EVENT_DESCRIPTION, "Check menu item checked", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "button-toggle-on", + CA_PROP_EVENT_DESCRIPTION, "Check menu item checked", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); else - ret = ca_gtk_play_for_widget(GTK_WIDGET(d), 0, - CA_PROP_EVENT_ID, "button-toggle-off", - CA_PROP_EVENT_DESCRIPTION, "Check menu item unchecked", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "button-toggle-off", + CA_PROP_EVENT_DESCRIPTION, "Check menu item unchecked", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); } else if (GTK_IS_MENU_ITEM(d->object) && d->signal_id == signal_id_menu_item_activate) { if (!GTK_MENU_ITEM(d->object)->submenu) - ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, - CA_PROP_EVENT_ID, "menu-click", - CA_PROP_EVENT_DESCRIPTION, "Menu item clicked", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "menu-click", + CA_PROP_EVENT_DESCRIPTION, "Menu item clicked", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); } - if (GTK_IS_TOGGLE_BUTTON(d->object) && d->signal_id == signal_id_toggle_button_toggled) { + if (GTK_IS_TOGGLE_BUTTON(d->object)) { - if (!is_child_of_combo_box(GTK_WIDGET(d->object))) { + if (d->signal_id == signal_id_toggle_button_toggled) { - /* We don't want to play this sound if this is a toggle - * button belonging to combo box. */ + if (!is_child_of_combo_box(GTK_WIDGET(d->object))) { - if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->object))) - ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, - CA_PROP_EVENT_ID, "button-toggle-on", - CA_PROP_EVENT_DESCRIPTION, "Toggle button checked", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); - else - ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, - CA_PROP_EVENT_ID, "button-toggle-off", - CA_PROP_EVENT_DESCRIPTION, "Toggle button unchecked", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); + /* We don't want to play this sound if this is a toggle + * button belonging to combo box. */ + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->object))) + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "button-toggle-on", + CA_PROP_EVENT_DESCRIPTION, "Toggle button checked", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + else + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "button-toggle-off", + CA_PROP_EVENT_DESCRIPTION, "Toggle button unchecked", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } } - } else if (GTK_IS_BUTTON(d->object) && !GTK_IS_TOGGLE_BUTTON(d->object) && d->signal_id == signal_id_button_clicked) { - GtkDialog *dialog; - gboolean dont_play = FALSE; + } else if (GTK_IS_LINK_BUTTON(d->object)) { - if ((dialog = find_parent_dialog(GTK_WIDGET(d->object)))) { - int response; + if (d->signal_id == signal_id_button_pressed) { + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "link-pressed", + CA_PROP_EVENT_DESCRIPTION, "Link pressed", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); - /* Don't play the click sound if this is a response widget - * we will generate a dialog-xxx event sound anyway. */ + } else if (d->signal_id == signal_id_button_released) { - response = gtk_dialog_get_response_for_widget(dialog, GTK_WIDGET(d->object)); - dont_play = !!translate_response(response); + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "link-released", + CA_PROP_EVENT_DESCRIPTION, "Link released", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); } - if (!dont_play) - ret = ca_gtk_play_for_widget(GTK_WIDGET(d->object), 0, - CA_PROP_EVENT_ID, "button-click", - CA_PROP_EVENT_DESCRIPTION, "Button clicked", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - NULL); + } else if (GTK_IS_BUTTON(d->object) && !GTK_IS_TOGGLE_BUTTON(d->object)) { + + if (d->signal_id == signal_id_button_pressed) { + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "button-pressed", + CA_PROP_EVENT_DESCRIPTION, "Button pressed", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + + } else if (d->signal_id == signal_id_button_released) { + GtkDialog *dialog; + gboolean dont_play = FALSE; + + if ((dialog = find_parent_dialog(GTK_WIDGET(d->object)))) { + int response; + + /* Don't play the click sound if this is a response widget + * we will generate a dialog-xxx event sound anyway. */ + + response = gtk_dialog_get_response_for_widget(dialog, GTK_WIDGET(d->object)); + dont_play = !!translate_response(response); + } + + if (!dont_play) + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "button-released", + CA_PROP_EVENT_DESCRIPTION, "Button released", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } + } + + if (GTK_IS_NOTEBOOK(d->object) && d->signal_id == signal_id_notebook_switch_page) { + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "notebook-tab-changed", + CA_PROP_EVENT_DESCRIPTION, "Tab changed", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); + } + + if (GTK_IS_TREE_VIEW(d->object) && d->signal_id == signal_id_tree_view_move_cursor) { + ret = ca_gtk_play_for_event(d->event, 0, + CA_PROP_EVENT_ID, "item-selected", + CA_PROP_EVENT_DESCRIPTION, "Item selected", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + NULL); } if (ret != CA_SUCCESS) @@ -303,8 +479,14 @@ static gboolean idle_cb(void *userdata) { idle_id = 0; + g_message("idle_cb()"); + while ((d = g_queue_pop_head(&sound_event_queue))) { - filter_sound_events(d); + d = filter_sound_event(d); + + g_message("Dispatching signal %s on %s", g_signal_name(d->signal_id), g_type_name(G_OBJECT_TYPE(d->object))); + + dispatch_sound_event(d); free_sound_event(d); } @@ -319,20 +501,24 @@ static gboolean emission_hook_cb(GSignalInvocationHint *hint, guint n_param_valu object = g_value_get_object(¶m_values[0]); - g_message("signal %s on %s", g_signal_name(hint->signal_id), g_type_name(G_OBJECT_TYPE(object))); - + /* Filter a few very often occuring signals as quickly as possible */ if ((hint->signal_id == signal_id_widget_hide || - hint->signal_id == signal_id_widget_show) && + hint->signal_id == signal_id_widget_show || + hint->signal_id == signal_id_widget_window_state_event) && !GTK_IS_WINDOW(object)) return TRUE; + g_message("signal %s on %s", g_signal_name(hint->signal_id), g_type_name(G_OBJECT_TYPE(object))); + d = g_slice_new0(SoundEventData); d->object = g_object_ref(object); d->signal_id = hint->signal_id; - if ((e = gtk_get_current_event())) + if (d->signal_id == signal_id_widget_window_state_event) { + d->event = gdk_event_copy(g_value_peek_pointer(¶m_values[1])); + } else if ((e = gtk_get_current_event())) d->event = gdk_event_copy(e); if (n_param_values > 1) { @@ -343,6 +529,8 @@ static gboolean emission_hook_cb(GSignalInvocationHint *hint, guint n_param_valu g_queue_push_tail(&sound_event_queue, e); + g_message("enqueuing"); + if (idle_id == 0) idle_id = g_idle_add_full(GTK_PRIORITY_REDRAW-1, (GSourceFunc) idle_cb, NULL, NULL); @@ -370,5 +558,9 @@ void gtk_module_init(gint *argc, gchar ***argv[]) { install_hook(GTK_TYPE_MENU_ITEM, "activate", &signal_id_menu_item_activate); install_hook(GTK_TYPE_CHECK_MENU_ITEM, "toggled", &signal_id_check_menu_item_toggled); install_hook(GTK_TYPE_TOGGLE_BUTTON, "toggled", &signal_id_toggle_button_toggled); - install_hook(GTK_TYPE_BUTTON, "clicked", &signal_id_button_clicked); + install_hook(GTK_TYPE_BUTTON, "pressed", &signal_id_button_pressed); + install_hook(GTK_TYPE_BUTTON, "released", &signal_id_button_released); + install_hook(GTK_TYPE_WIDGET, "window-state-event", &signal_id_widget_window_state_event); + install_hook(GTK_TYPE_NOTEBOOK, "switch-page", &signal_id_notebook_switch_page); + install_hook(GTK_TYPE_TREE_VIEW, "move-cursor", &signal_id_tree_view_move_cursor); } diff --git a/src/canberra-gtk.c b/src/canberra-gtk.c index 2d3edb4..ed6f029 100644 --- a/src/canberra-gtk.c +++ b/src/canberra-gtk.c @@ -33,6 +33,29 @@ #include "common.h" #include "malloc.h" +/** + * SECTION:canberra-gtk + * @short_description: Gtk+ libcanberra Bindings + * + * libcanberra-gtk provides a few functions that simplify libcanberra + * usage from Gtk+ programs. It maintains a single application-global + * ca_context object that is made accessible via + * ca_gtk_context_get(). More importantly, it provides a few functions + * to compile event sound property lists based on GtkWidget objects or + * GdkEvent events. + */ + +/** + * ca_gtk_context_get: + * + * libcanberra-gtk maintains a single application-global ca_context + * object. Use this function to access it. The + * %CA_PROP_CANBERRA_XDG_THEME_NAME of this context property is dynamically bound to + * the XSETTINGS setting for the XDG theme name. + * + * Returns: a pa_context object + */ + ca_context *ca_gtk_context_get(void) { static GStaticPrivate context_private = G_STATIC_PRIVATE_INIT; ca_context *c = NULL; @@ -61,6 +84,18 @@ static GtkWindow* get_toplevel(GtkWidget *w) { return GTK_WINDOW(w); } +/** + * ca_gtk_proplist_set_for_widget: + * @p: The proplist to store these sound event properties in + * @w: The Gtk widget to base these sound event properties on + * + * Fill in a ca_proplist object for a sound event that shall originate + * from the specified Gtk Widget. This will fill in properties like + * %CA_PROP_WINDOW_NAME or %CA_PROP_WINDOW_X11_DISPLAY for you. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_gtk_proplist_set_for_widget(ca_proplist *p, GtkWidget *widget) { GtkWindow *w; int ret; @@ -118,6 +153,20 @@ int ca_gtk_proplist_set_for_widget(ca_proplist *p, GtkWidget *widget) { return CA_SUCCESS; } +/** + * ca_gtk_proplist_set_for_event: + * @p: The proplist to store these sound event properties in + * @e: The Gdk event to base these sound event properties on + * + * Fill in a ca_proplist object for a sound event that is being + * triggered by the specified Gdk Event. This will fill in properties + * like %CA_PROP_EVENT_MOUSE_X or %CA_PROP_EVENT_MOUSE_BUTTON for + * you. This will internally also cal ca_gtk_proplist_set_for_widget() + * on the widget this event belongs to. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_gtk_proplist_set_for_event(ca_proplist *p, GdkEvent *e) { gdouble x, y; GdkWindow *gw; @@ -169,6 +218,26 @@ int ca_gtk_proplist_set_for_event(ca_proplist *p, GdkEvent *e) { return CA_SUCCESS; } +/** + * ca_gtk_play_for_widget: + * @w: The Gtk widget to base these sound event properties on + * @id: The event id that can later be used to cancel this event sound + * using ca_context_cancel(). This can be any integer and shall be + * chosen be the client program. It is a good idea to pass 0 here if + * cancelling the sound later is not needed. If the same id is passed + * to multiple sounds they can be canceled with a single + * ca_context_cancel() call. + * @...: additional event properties as pairs of strings, terminated by NULL. + * + * Play a sound event for the specified widget. This will internally + * call ca_gtk_proplist_set_for_widget() and then merge them with the + * properties passed in via the NULL terminated argument + * list. Finally, it will call ca_context_play_full() to actually play + * the event sound. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_gtk_play_for_widget(GtkWidget *w, uint32_t id, ...) { va_list ap; int ret; @@ -198,6 +267,26 @@ fail: return ret; } +/** + * ca_gtk_play_for_event: + * @e: The Gdk event to base these sound event properties on + * @id: The event id that can later be used to cancel this event sound + * using ca_context_cancel(). This can be any integer and shall be + * chosen be the client program. It is a good idea to pass 0 here if + * cancelling the sound later is not needed. If the same id is passed + * to multiple sounds they can be canceled with a single + * ca_context_cancel() call. + * @...: additional event properties as pairs of strings, terminated by NULL. + * + * Play a sound event for the specified event. This will internally + * call ca_gtk_proplist_set_for_event() and then merge them with the + * properties passed in via the NULL terminated argument + * list. Finally, it will call ca_context_play_full() to actually play + * the event sound. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_gtk_play_for_event(GdkEvent *e, uint32_t id, ...) { va_list ap; int ret; @@ -227,6 +316,17 @@ fail: return ret; } +/** + * ca_gtk_widget_disable_sounds: + * @w: The Gtk widget to disable automatic event sounds for. + * @enable: Boolean specifying whether sound events shall be enabled or disabled for this widget. + * + * By default sound events are automatically generated for all kinds + * of input events. Use this function to disable this. This is + * intended to be used for widgets which directly generate sound + * events. + */ + void ca_gtk_widget_disable_sounds(GtkWidget *w, gboolean enable) { static GQuark disable_sound_quark = 0; diff --git a/src/canberra-gtk.h b/src/canberra-gtk.h index 3c6fa2c..74c3388 100644 --- a/src/canberra-gtk.h +++ b/src/canberra-gtk.h @@ -29,10 +29,12 @@ ca_context *ca_gtk_context_get(void); int ca_gtk_proplist_set_for_widget(ca_proplist *p, GtkWidget *w); -int ca_gtk_play_for_widget(GtkWidget *w, uint32_t id, ...) CA_GCC_SENTINEL; + +int ca_gtk_play_for_widget(GtkWidget *w, uint32_t id, ...) G_GNUC_NULL_TERMINATED; int ca_gtk_proplist_set_for_event(ca_proplist *p, GdkEvent *e); -int ca_gtk_play_for_event(GdkEvent *e, uint32_t id, ...) CA_GCC_SENTINEL; + +int ca_gtk_play_for_event(GdkEvent *e, uint32_t id, ...) G_GNUC_NULL_TERMINATED; void ca_gtk_widget_disable_sounds(GtkWidget *w, gboolean enable); diff --git a/src/canberra.h b/src/canberra.h index 86e83a9..348cfef 100644 --- a/src/canberra.h +++ b/src/canberra.h @@ -27,113 +27,359 @@ #include #include -/* - - Requirements & General observations: - - - Property set extensible. To be kept in sync with PulseAudio and libsydney. - - Property keys need to be valid UTF-8, text values, too. - - Will warn if application.name or application.id not set. - - Will fail if event.id not set - - Fully thread safe, not async-signal safe - - Error codes are returned immediately, as negative integers - - If the control.cache property is set it will control whether the - specific sample will be cached in the server: - - * permanent: install the sample permanently in the server (for usage in gnome-session) - * volatile: install the sample temporarily in the server (will be expelled from cache on cache pressure or after timeout) - * never: never cache the sample in the server, always stream - - control.cache will default to "volatile" for ca_context_cache() and "never" for ca_context_play(). - control.cache is only a hint, the server may ignore this value - - application.process.* will be filled in automatically but may be overwritten by the client. - They thus should not be used for authentication purposes. - - The property list attached to the client object in the sound - server will be those specified via ca_context_prop_xx(). - - The property list attached to cached samples in the sound server - will be those specified via ca_context_prop_xx() at sample upload time, - combined with those specified directly at the _cache() function call - (the latter potentially overwriting the former). - - The property list attached to sample streams in the sound server - will be those attached to the cached sample (only if the event - sound is cached, of course) combined (i.e. potentially - overwritten by) those set via ca_context_prop_xx() at play time, - combined (i.e. potentially overwritten by) those specified - directly at the _play() function call. - - It is recommended to set application.* once before calling - _open(), and media.* event.* at both cache and play time. - -*/ - -#ifdef __GNUC__ -#define CA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b))) -#else -/** If we're in GNU C, use some magic for detecting invalid format strings */ -#define CA_GCC_PRINTF_ATTR(a,b) +#ifndef __GNUC__ +/* Make sure __attribute__ works on non-gcc systems. Yes, might be a bit ugly */ +#define __attribute__(x) #endif -#if defined(__GNUC__) && (__GNUC__ >= 4) -#define CA_GCC_SENTINEL __attribute__ ((sentinel)) -#else -/** Macro for usage of GCC's sentinel compilation warnings */ -#define CA_GCC_SENTINEL -#endif - -/** Properties for the media that is being played, about the event - * that caused the media to play, the window on which behalf this - * media is being played, the application that this window belongs to - * and finally properties for libcanberra specific usage. */ +/** + * CA_PROP_MEDIA_NAME: + * + * A name describing the media being played. + */ #define CA_PROP_MEDIA_NAME "media.name" + +/** + * CA_PROP_MEDIA_TITLE: + * + * A (song) title describing the media being played. + */ #define CA_PROP_MEDIA_TITLE "media.title" + +/** + * CA_PROP_MEDIA_ARTIST: + * + * The artist of this media + */ #define CA_PROP_MEDIA_ARTIST "media.artist" + +/** + * CA_PROP_MEDIA_LANGUAGE: + * + * The language this media is in, in some standard POSIX locale string, such as "de_DE". + */ #define CA_PROP_MEDIA_LANGUAGE "media.language" + +/** + * CA_PROP_MEDIA_FILENAME: + * + * The file name this media was or can be loaded from. + */ #define CA_PROP_MEDIA_FILENAME "media.filename" + +/** + * CA_PROP_MEDIA_ICON: + * + * An icon for this media in binary PNG format. + */ #define CA_PROP_MEDIA_ICON "media.icon" + +/** + * CA_PROP_MEDIA_ICON_NAME: + * + * An icon name as defined in the XDG icon naming specifcation. + */ #define CA_PROP_MEDIA_ICON_NAME "media.icon_name" + +/** + * CA_PROP_MEDIA_ROLE: + * + * The "role" this media is played in. For event sounds the string + * "event". For other cases strings like "music", "video", "game", ... + */ #define CA_PROP_MEDIA_ROLE "media.role" + +/** + * CA_PROP_EVENT_ID: + * + * A textual id for an event sound, as mandated by the XDG sound naming specification. + */ #define CA_PROP_EVENT_ID "event.id" + +/** + * CA_PROP_EVENT_DESCRIPTION: + * + * A descriptive string for the sound event. + */ #define CA_PROP_EVENT_DESCRIPTION "event.description" + +/** + * CA_PROP_EVENT_MOUSE_X: + * + * If this sound event was triggered by a mouse input event, the X + * position of the mouse cursor on the screen, formatted as string. + */ #define CA_PROP_EVENT_MOUSE_X "event.mouse.x" + +/** + * CA_PROP_EVENT_MOUSE_Y: + * + * If this sound event was triggered by a mouse input event, the Y + * position of the mouse cursor on the screen, formatted as string. + */ #define CA_PROP_EVENT_MOUSE_Y "event.mouse.y" + +/** + * CA_PROP_EVENT_MOUSE_HPOS: + * + * If this sound event was triggered by a mouse input event, the X + * position of the mouse cursor as fractional value between 0 and 1, + * formatted as string, 0 reflecting the left side of the screen, 1 + * the right side. + */ #define CA_PROP_EVENT_MOUSE_HPOS "event.mouse.hpos" + +/** + * CA_PROP_EVENT_MOUSE_VPOS: + * + * If this sound event was triggered by a mouse input event, the Y + * position of the mouse cursor as fractional value between 0 and 1, + * formatted as string, 0 reflecting the top end of the screen, 1 + * the bottom end. + */ #define CA_PROP_EVENT_MOUSE_VPOS "event.mouse.vpos" + +/** + * CA_PROP_EVENT_MOUSE_BUTTON: + * + * If this sound event was triggered by a mouse input event, the + * number of the mouse button that triggered it, formatted as string. 1 + * for left mouse button, 3 for right, 2 for middle. + */ #define CA_PROP_EVENT_MOUSE_BUTTON "event.mouse.button" + +/** + * CA_PROP_WINDOW_NAME: + * + * If this sound event was triggered by a window on the screen, the + * name of this window as human readable string. + */ #define CA_PROP_WINDOW_NAME "window.name" + +/** + * CA_PROP_WINDOW_ID: + * + * If this sound event was triggered by a window on the screen, some + * identification string for this window, so that the sound system can + * recognize specific windows. + */ #define CA_PROP_WINDOW_ID "window.id" + +/** + * CA_PROP_WINDOW_ICON: + * + * If this sound event was triggered by a window on the screen, binary + * icon data in PNG format for this window. + */ #define CA_PROP_WINDOW_ICON "window.icon" + +/** + * CA_PROP_WINDOW_ICON_NAME: + * + * If this sound event was triggered by a window on the screen, an + * icon name for this window, as defined in the XDG icon naming + * specification. + */ #define CA_PROP_WINDOW_ICON_NAME "window.icon_name" + +/** + * CA_PROP_WINDOW_X11_DISPLAY: + * + * If this sound event was triggered by a window on the screen and the + * windowing system is X11, the X display name of the window (e.g. ":0"). + */ #define CA_PROP_WINDOW_X11_DISPLAY "window.x11.display" + +/** + * CA_PROP_WINDOW_X11_SCREEN: + * + * If this sound event was triggered by a window on the screen and the + * windowing system is X11, the X screen id of the window formatted as + * string (e.g. "0"). + */ #define CA_PROP_WINDOW_X11_SCREEN "window.x11.screen" + +/** + * CA_PROP_WINDOW_X11_MONITOR: + * + * If this sound event was triggered by a window on the screen and the + * windowing system is X11, the X monitor id of the window formatted as + * string (e.g. "0"). + */ #define CA_PROP_WINDOW_X11_MONITOR "window.x11.monitor" + +/** + * CA_PROP_WINDOW_X11_XID: + * + * If this sound event was triggered by a window on the screen and the + * windowing system is X11, the XID of the window formatted as string. + */ #define CA_PROP_WINDOW_X11_XID "window.x11.xid" + +/** + * CA_PROP_APPLICATION_NAME: + * + * The name of the application this sound event was triggered + * by as human readable string. (e.g. "GNU Emacs") + */ #define CA_PROP_APPLICATION_NAME "application.name" + +/** + * CA_PROP_APPLICATION_ID: + * + * An identifier for the program this sound event was triggered + * by. (e.g. "org.gnu.emacs"). + */ #define CA_PROP_APPLICATION_ID "application.id" + +/** + * CA_PROP_APPLICATION_VERSION: + * + * A version number for the program this sound event was triggered + * by. (e.g. "22.2") + */ #define CA_PROP_APPLICATION_VERSION "application.version" + +/** + * CA_PROP_APPLICATION_ICON: + * + * Binary icon data in PNG format for the application this sound event + * is triggered by. + */ #define CA_PROP_APPLICATION_ICON "application.icon" + +/** + * CA_PROP_APPLICATION_ICON_NAME: + * + * An icon name for the application this sound event is triggered by, + * as defined in the XDG icon naming specification. + */ #define CA_PROP_APPLICATION_ICON_NAME "application.icon_name" + +/** + * CA_PROP_APPLICATION_LANGUAGE: + * + * The locale string the application that is triggering this sound + * event is running in. A POSIX locale string such as de_DE@euro. + */ #define CA_PROP_APPLICATION_LANGUAGE "application.language" + +/** + * CA_PROP_APPLICATION_PROCESS_ID: + * + * The unix PID of the process that is triggering this sound event, formatted as string. + */ #define CA_PROP_APPLICATION_PROCESS_ID "application.process.id" + +/** + * CA_PROP_APPLICATION_PROCESS_BINARY: + * + * The path to the process binary of the process that is triggering this sound event. + */ #define CA_PROP_APPLICATION_PROCESS_BINARY "application.process.binary" + +/** + * CA_PROP_APPLICATION_PROCESS_USER: + * + * The user that owns the process that is triggering this sound event. + */ #define CA_PROP_APPLICATION_PROCESS_USER "application.process.user" + +/** + * CA_PROP_APPLICATION_PROCESS_HOST: + * + * The host name of the host the process that is triggering this sound event runs on. + */ #define CA_PROP_APPLICATION_PROCESS_HOST "application.process.host" -#define CA_PROP_CANBERRA_CACHE_CONTROL "canberra.cache-control" /* permanent, volatile, never */ -#define CA_PROP_CANBERRA_VOLUME "canberra.volume" /* decibel */ -#define CA_PROP_CANBERRA_XDG_THEME_NAME "canberra.xdg-theme.name" /* XDG theme name */ -#define CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE "canberra.xdg-theme.output-profile" /* XDG theme profile */ -/* Context object */ +/** + * CA_PROP_CANBERRA_CACHE_CONTROL: + * + * A special property that can be used to control the automatic sound + * caching of sounds in the sound server. One of "permanent", + * "volatile", "never". "permanent" will cause this sample to be + * cached in the server permanently. This is useful for very + * frequently used sound events such as those used for input + * feedback. "volatile" may be used for cacheing sounds in the sound + * server temporarily. They will expire after some time or on cache + * pressure. Finally, "never" may be used for sounds that should never + * be cached, because they are only generated very seldomly or even + * only once at most (such as desktop login sounds). + * + * If this property is not explicitly passed to ca_context_play() it + * will default to "never". If it is not explicitly passed to + * ca_context_cache() it will default to "permanent". + * + * If the list of properties is handed on to the sound server this + * property is stripped from it. + */ +#define CA_PROP_CANBERRA_CACHE_CONTROL "canberra.cache-control" + +/** + * CA_PROP_CANBERRA_VOLUME: + * + * A special property that can be used to control the volume this + * sound event is played in if the backend supports it. A floating + * point value for the decibel multiplier for the sound. 0 dB relates + * to zero gain, and is the default volume these sounds are played in. + * + * If the list of properties is handed on to the sound server this + * property is stripped from it. + */ +#define CA_PROP_CANBERRA_VOLUME "canberra.volume" + +/** + * CA_PROP_CANBERRA_XDG_THEME_NAME: + * + * A special property that can be used to control the XDG sound theme that + * is used for this sample. + * + * If the list of properties is handed on to the sound server this + * property is stripped from it. + */ +#define CA_PROP_CANBERRA_XDG_THEME_NAME "canberra.xdg-theme.name" + +/** + * CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE: + * + * A special property that can be used to control the XDG sound theme + * output profile that is used for this sample. + * + * If the list of properties is handed on to the sound server this + * property is stripped from it. + */ +#define CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE "canberra.xdg-theme.output-profile" + +/** + * ca_context: + * + * A libcanberra context object. + */ typedef struct ca_context ca_context; -/** Playback completion event callback. The context this callback is +/** + * ca_finish_callback_t: + * @c: The libcanberra context this callback is called for + * @id: The numerical id passed to the ca_context_play_full() when starting the event sound playback. + * @error_code: A numerical error code describing the reason this callback is called. If CA_SUCCESS is passed in the playback of the event sound was successfully completed. + * @userdata: Some arbitrary user data the caller of ca_context_play_full() passed in. + * + * Playback completion event callback. The context this callback is * called in is undefined, it might or might not be called from a - * background thread, and from any strackframe. The code implementing + * background thread, and from any stack frame. The code implementing * this function may not call any libcanberra API call from this * callback -- this might result in a deadlock. Instead it may only be * used to asynchronously signal some kind of notification object - * (semaphore, message queue, ...). */ + * (semaphore, message queue, ...). + */ typedef void (*ca_finish_callback_t)(ca_context *c, uint32_t id, int error_code, void *userdata); -/** Error codes */ +/** + * Error codes: + * @CA_SUCCESS: Success + * + * Error codes + */ enum { CA_SUCCESS = 0, CA_ERROR_NOTSUPPORTED = -1, @@ -154,68 +400,32 @@ enum { _CA_ERROR_MAX = -16 }; +/** + * ca_proplist: + * + * A canberra property list object. Basically a hashtable. + */ typedef struct ca_proplist ca_proplist; -int ca_proplist_create(ca_proplist **c); -int ca_proplist_destroy(ca_proplist *c); +int ca_proplist_create(ca_proplist **p); +int ca_proplist_destroy(ca_proplist *p); int ca_proplist_sets(ca_proplist *p, const char *key, const char *value); -int ca_proplist_setf(ca_proplist *p, const char *key, const char *format, ...) CA_GCC_PRINTF_ATTR(3,4); +int ca_proplist_setf(ca_proplist *p, const char *key, const char *format, ...) __attribute__((format(printf, 3, 4))); int ca_proplist_set(ca_proplist *p, const char *key, const void *data, size_t nbytes); -/** Create an (unconnected) context object */ int ca_context_create(ca_context **c); - int ca_context_set_driver(ca_context *c, const char *driver); - int ca_context_change_device(ca_context *c, const char *device); - -/** Connect the context. This call is implicitly called if necessary. It - * is recommended to initialize the application.* properties before - * issuing this call */ int ca_context_open(ca_context *c); - -/** Destroy a (connected or unconnected) cntext object. */ int ca_context_destroy(ca_context *c); - -/** Write one or more string properties to the context - * object. Requires final NULL sentinel. Properties set like this will - * be attached to both the client object of the sound server and to - * all event sounds played or cached. */ -int ca_context_change_props(ca_context *c, ...) CA_GCC_SENTINEL; - -/** Write an arbitrary data property to the context object. */ +int ca_context_change_props(ca_context *c, ...) __attribute__((sentinel)); int ca_context_change_props_full(ca_context *c, ca_proplist *p); - -/** Play one event sound. id can be any numeric value which later can - * be used to cancel an event sound that is currently being - * played. You may use the same id twice or more times if you want to - * cancel multiple event sounds with a single ca_context_cancel() call - * at once. It is recommended to pass 0 for the id if the event sound - * shall never be canceled. If the requested sound is not cached in - * the server yet this call might result in the sample being uploaded - * temporarily or permanently. This function will only start playback - * in the background. It will not wait until playback completed. */ -int ca_context_play(ca_context *c, uint32_t id, ...) CA_GCC_SENTINEL; - -/** Play one event sound, and call the specified callback function - when completed. The callback will be called from a background - thread. Other arguments identical to ca_context_play(). */ int ca_context_play_full(ca_context *c, uint32_t id, ca_proplist *p, ca_finish_callback_t cb, void *userdata); - -/** Upload the specified sample into the server and attach the - * specified properties to it. This function will only return after - * the sample upload was finished. */ -int ca_context_cache(ca_context *c, ...) CA_GCC_SENTINEL; - -/** Upload the specified sample into the server and attach the - * specified properties to it */ +int ca_context_play(ca_context *c, uint32_t id, ...) __attribute__((sentinel)); int ca_context_cache_full(ca_context *c, ca_proplist *p); - -/** Cancel one or more event sounds that have been started via - * ca_context_play(). */ +int ca_context_cache(ca_context *c, ...) __attribute__((sentinel)); int ca_context_cancel(ca_context *c, uint32_t id); -/** Return a human readable error string */ const char *ca_strerror(int code); #endif diff --git a/src/common.c b/src/common.c index 80be86c..a7b6945 100644 --- a/src/common.c +++ b/src/common.c @@ -33,6 +33,97 @@ #include "proplist.h" #include "macro.h" +/** + * SECTION:canberra + * @short_description: General libcanberra API + * + * libcanberra defines a simple abstract interface for playing event sounds. + * + * libcanberra relies on the XDG sound naming specification for + * identifying event sounds. On Unix/Linux the right sound to play is + * found via the mechanisms defined in the XDG sound themeing + * specification. On other systems the XDG sound name is translated to + * the native sound id for the operating system. + * + * An event sound is triggered via libcanberra by calling the + * ca_context_play() function on a previously created ca_context + * object. The ca_context_play() takes a list of key-value pairs that + * describe the event sound to generate as closely as possible. The + * most important property is %CA_PROP_EVENT_ID which defines the XDG + * sound name for the sound to play. + * + * libcanberra is not a generic event abstraction system. It's only + * purpose is playing sounds -- however in a very elaborate way. As + * much information about the context the sound is triggered from + * shall be supplied to the sound system as possible, so that it can + * replace the sound with some other kind of feedback for a11y + * cases. Also this additional information can be used to enhance user + * experience (e.g. by positioning sounds in space depending on the + * place on the screen the sound was triggered from, and similar + * uses). + * + * The set of properties defined for event sounds is extensible and + * shared with other audio systems, such as PulseAudio. Some of + * the properties that may be set are specific to an application, to a + * window, to an input event or to the media being played back. + * + * The user can attach a set of properties to the context itself, + * which is than automatically inherited by each sample being played + * back. (ca_context_change_props()). + * + * Some of the properties can be filled in by libcanberra or one of + * its backends automatically and thus need not be be filled in by the + * application (such as %CA_PROP_APPLICATION_PROCESS_ID and + * friends). However the application can always overwrite any of these + * implicit properties. + * + * libcanberra is thread-safe and OOM-safe (as far as the backend + * allows this). It is not async-signal safe. + * + * Most libcanberra functions return an integer that indicates success + * when 0 (%CA_SUCCESS) or an error when negative. In the latter case + * ca_strerror() can be used to convert this code into a human + * readable string. + * + * libcanberra property names need to be in 7bit ASCII, string + * property values UTF8. + * + * Optionally a libcanberra backend can support caching of sounds in a + * sound system. If this functionality is used, the latencies for + * event sound playback can be much smaller and fewer resources are + * needed to start playback. If a backend does not support cacheing, + * the respective functions will return an error code of + * %CA_ERROR_NOTSUPPORTED. + * + * It is highly recommended that the application sets the + * %CA_PROP_APPLICATION_NAME, %CA_PROP_APPLICATION_ID, + * %CA_PROP_APPLICATION_ICON_NAME/%CA_PROP_APPLICATION_ICON properties + * immediately after creating the ca_context, before calling + * ca_context_open() or ca_context_play(). + * + * Its is highly recommended to pass at least %CA_PROP_EVENT_ID, + * %CA_PROP_EVENT_DESCRIPTION to ca_context_play() for each event + * sound generated. For sound events based on mouse inputs events + * %CA_PROP_EVENT_MOUSE_X, %CA_PROP_EVENT_MOUSE_Y, %CA_PROP_EVENT_MOUSE_HPOS, + * %CA_PROP_EVENT_MOUSE_VPOS, %CA_PROP_EVENT_MOUSE_BUTTON should be + * passed. For sound events attached to a widget on the screen, the + * %CA_PROP_WINDOW_xxx properties should be set. + * + * + */ + +/** + * ca_context_create: + * @c: A pointer wheere to fill in the newly created context object. + * + * Create an (unconnected) context object. This call will not connect + * to the sound system, calling this function might even suceed if no + * working driver backend is available. To find out if one is + * available call ca_context_open(). + * + * Returns: 0 on success, negative error code on error. + */ + int ca_context_create(ca_context **_c) { ca_context *c; int ret; @@ -71,6 +162,14 @@ int ca_context_create(ca_context **_c) { return CA_SUCCESS; } +/** + * ca_context_destroy: + * @c: the context to destroy. + * + * Destroy a (connected or unconnected) context object. + * + * Returns: 0 on success, negative error code on error. + */ int ca_context_destroy(ca_context *c) { int ret = CA_SUCCESS; @@ -96,6 +195,18 @@ int ca_context_destroy(ca_context *c) { return ret; } +/** + * ca_context_set_driver: + * @c: the context to change the backend driver for + * @driver: the backend driver to use (e.g. "alsa", "pulse", "null", ...) + * + * Specify the backend driver used. This function may not be called again after + * ca_context_open() suceeded. This function might suceed even when + * the specified driver backend is not available. Use + * ca_context_open() to find out whether the backend is available. + * + * Returns: 0 on success, negative error code on error. + */ int ca_context_set_driver(ca_context *c, const char *driver) { char *n; int ret; @@ -104,7 +215,9 @@ int ca_context_set_driver(ca_context *c, const char *driver) { ca_mutex_lock(c->mutex); ca_return_val_if_fail_unlock(!c->opened, CA_ERROR_STATE, c->mutex); - if (!(n = ca_strdup(driver))) { + if (!driver) + n = NULL; + else if (!(n = ca_strdup(driver))) { ret = CA_ERROR_OOM; goto fail; } @@ -120,6 +233,21 @@ fail: return ret; } +/** + * ca_context_change_device: + * @c: the context to change the backend device for + * @device: the backend device to use, in a format that is specific to the backend. + * + * Specify the backend device to use. This function may be called not be called after + * ca_context_open() suceeded. This function might suceed even when + * the specified driver backend is not available. Use + * ca_context_open() to find out whether the backend is available + * + * Depending on the backend use this might or might not cause all + * currently playing event sounds to be moved to the new device.. + * + * Returns: 0 on success, negative error code on error. + */ int ca_context_change_device(ca_context *c, const char *device) { char *n; int ret; @@ -127,7 +255,9 @@ int ca_context_change_device(ca_context *c, const char *device) { ca_return_val_if_fail(c, CA_ERROR_INVALID); ca_mutex_lock(c->mutex); - if (!(n = ca_strdup(device))) { + if (!device) + n = NULL; + else if (!(n = ca_strdup(device))) { ret = CA_ERROR_OOM; goto fail; } @@ -160,6 +290,17 @@ static int context_open_unlocked(ca_context *c) { return ret; } +/** + * ca_context_open: + * @c: the context to connect. + * + * Connect the context to the sound system. This call is implicitly + * called in ca_context_play() or ca_context_cache() if not called + * explicitly. It is recommended to initialize application properties + * with ca_context_change_props() before calling this function. + * + * Returns: 0 on success, negative error code on error. + */ int ca_context_open(ca_context *c) { int ret; @@ -217,6 +358,24 @@ fail: return ret; } +/** + * ca_context_change_props: + * @c: the context to set the properties on. + * @...: the list of string pairs for the properties. Needs to be a NULL terminated list. + * + * Write one or more string properties to the context object. Requires + * final NULL sentinel. Properties set like this will be attached to + * both the client object of the sound server and to all event sounds + * played or cached. It is recommended to call this function at least + * once before calling ca_context_open(), so that the initial + * application properties are set properly before the initial + * connection to the sound system. This function can be called both + * before and after the ca_context_open() call. Properties that have + * already been set before will be overwritten. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_context_change_props(ca_context *c, ...) { va_list ap; int ret; @@ -238,6 +397,18 @@ int ca_context_change_props(ca_context *c, ...) { return ret; } +/** + * ca_context_change_props_full: + * @c: the context to set the properties on. + * @p: the property list to set. + * + * Similar to ca_context_change_props(), but takes a ca_proplist + * instead of a variable list of properties. Can be used to set binary + * properties such as %CA_PROP_APPLICATION_ICON. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_context_change_props_full(ca_context *c, ca_proplist *p) { int ret; ca_proplist *merged; @@ -265,6 +436,48 @@ finish: return ret; } +/** + * ca_context_play: + * @c: the context to play the event sound on + * @id: an integer id this sound can later be identified with when calling ca_context_cancel() + * @...: additional properties for this sound event. + * + * Play one event sound. id can be any numeric value which later can + * be used to cancel an event sound that is currently being + * played. You may use the same id twice or more times if you want to + * cancel multiple event sounds with a single ca_context_cancel() call + * at once. It is recommended to pass 0 for the id if the event sound + * shall never be canceled. If the requested sound is not cached in + * the server yet this call might result in the sample being uploaded + * temporarily or permanently (this may be controlled with %CA_PROP_CANBERRA_CACHE_CONTROL). This function will start playback + * in the background. It will not wait until playback + * completed. Depending on the backend used a sound that is started + * shortly before your application terminates might or might not continue to + * play after your application terminated. If you want to make sure + * that all sounds finish to play you need to wait synchronously for + * the callback function of ca_context_play_full() to be called before you + * terminate your application. + * + * The sample to play is identified by the %CA_PROP_EVENT_ID + * property. If it is already cached in the server the cached version + * is played. The properties passed in this call are merged with the + * properties supplied when the sample was cached (if applicable) + * and the context properties as set with ca_context_change_props(). + * + * If %CA_PROP_EVENT_ID is not defined the sound file passed in the + * %CA_PROP_MEDIA_FILENAME is played. + * + * On Linux/Unix the right sound to play is determined according to + * %CA_PROP_EVENT_ID, + * %CA_PROP_APPLICATION_LANGUAGE/%CA_PROP_MEDIA_LANGUAGE, the system + * locale, %CA_PROP_CANBERRA_XDG_THEME_NAME and + * %CA_PROP_CANBERRA_XDG_THEME_OUTPUT_PROFILE, following the XDG Sound + * Theming Specification. On non-Unix systems the native event sound + * that matches the XDG sound name in %CA_PROP_EVENT_ID is played. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_context_play(ca_context *c, uint32_t id, ...) { int ret; va_list ap; @@ -286,6 +499,25 @@ int ca_context_play(ca_context *c, uint32_t id, ...) { return ret; } +/** + * ca_context_play_full: + * @c: the context to play the event sound on + * @id: an integer id this sound can be later be identified with when calling ca_context_cancel() or when the callback is called. + * @p: A property list of properties for this event sound + * @cb: A callback to call when this sound event sucessfully finished playing or when an error occured during playback. + * + * Play one event sound, and call the specified callback function when + * completed. See ca_finish_callback_t for the semantics the callback + * is called in. Also see ca_context_play(). + * + * It is guaranteed that the callback is called exactly once if + * ca_context_play_full() returns CA_SUCCESS. You thus may safely pass + * allocated memory to the callback and assume that it is freed + * properly. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_context_play_full(ca_context *c, uint32_t id, ca_proplist *p, ca_finish_callback_t cb, void *userdata) { int ret; @@ -303,6 +535,8 @@ int ca_context_play_full(ca_context *c, uint32_t id, ca_proplist *p, ca_finish_c ca_assert(c->opened); + fprintf(stderr, "Playing %s\n", ca_proplist_gets_unlocked(p, CA_PROP_EVENT_ID)); + ret = driver_play(c, id, p, cb, userdata); finish: @@ -312,6 +546,20 @@ finish: return ret; } +/** + * + * ca_context_cancel: + * @c: the context to cancel the sounds on + * @id: the id that identify the sounds to cancel. + * + * Cancel one or more event sounds that have been started via + * ca_context_play(). If the sound was started with + * ca_context_play_full() and a callback function was passed this + * might cause this function to be called with %CA_ERROR_CANCELED as + * error code. + * + * Returns: 0 on success, negative error code on error. + */ int ca_context_cancel(ca_context *c, uint32_t id) { int ret; @@ -326,6 +574,24 @@ int ca_context_cancel(ca_context *c, uint32_t id) { return ret; } +/** + * ca_context_cache: + * @c: The context to use for uploading. + * @...: The properties for this event sound. Terminated with NULL. + * + * Upload the specified sample into the audio server and attach the + * specified properties to it. This function will only return after + * the sample upload was finished. + * + * The sound to cache is found with the same algorithm that is used to + * find the sounds for ca_context_play(). + * + * If the backend doesn't support caching sound samples this function + * will return %CA_ERROR_NOTSUPPORTED. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_context_cache(ca_context *c, ...) { int ret; va_list ap; @@ -347,6 +613,20 @@ int ca_context_cache(ca_context *c, ...) { return ret; } +/** + * ca_context_cache_full: + * @c: The context to use for uploading. + * @p: The property list for this event sound. + * + * Upload the specified sample into the server and attach the + * specified properties to it. Similar to ca_context_cache() but takes + * a ca_proplist instead of a variable number of arguments. + * + * If the backend doesn't support caching sound samples this function + * will return CA_ERROR_NOTSUPPORTED. + * + * Returns: 0 on success, negative error code on error. + */ int ca_context_cache_full(ca_context *c, ca_proplist *p) { int ret; @@ -372,7 +652,14 @@ finish: return ret; } -/** Return a human readable error */ +/** + * ca_strerror: + * @code: Numerical error code as returned by a canberra API function + * + * Converts a numerical error code as returned by most canberra API functions into a human readable error string. + * + * Returns: a human readable error string. + */ const char *ca_strerror(int code) { const char * const error_table[-_CA_ERROR_MAX] = { diff --git a/src/malloc.h b/src/malloc.h index 264dd48..c4ebcc8 100644 --- a/src/malloc.h +++ b/src/malloc.h @@ -41,6 +41,6 @@ void* ca_memdup(const void* p, size_t size); #define ca_new0(t, n) ((t*) ca_malloc0(sizeof(t)*(n))) #define ca_newdup(t, p, n) ((t*) ca_memdup(p, sizeof(t)*(n))) -char *ca_sprintf_malloc(const char *format, ...) CA_GCC_PRINTF_ATTR(1,2); +char *ca_sprintf_malloc(const char *format, ...) __attribute__((format(printf, 1, 2))); #endif diff --git a/src/proplist.c b/src/proplist.c index 53eebf6..470d0fc 100644 --- a/src/proplist.c +++ b/src/proplist.c @@ -40,6 +40,14 @@ static unsigned calc_hash(const char *c) { return hash; } +/** + * ca_proplist_create: + * @p: A pointer where to fill in a pointer for the new property list. + * + * Allocate a new empty property list. + * + * Returns: 0 on success, negative error code on error. + */ int ca_proplist_create(ca_proplist **_p) { ca_proplist *p; ca_return_val_if_fail(_p, CA_ERROR_INVALID); @@ -91,6 +99,17 @@ static int _unset(ca_proplist *p, const char *key) { return CA_SUCCESS; } +/** + * ca_proplist_sets: + * @p: The property list to add this key/value pair to + * @key: The key for this key/value pair + * @value: The value for this key/value pair + * + * Add a new string key/value pair to the property list. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_proplist_sets(ca_proplist *p, const char *key, const char *value) { ca_return_val_if_fail(p, CA_ERROR_INVALID); ca_return_val_if_fail(key, CA_ERROR_INVALID); @@ -99,6 +118,20 @@ int ca_proplist_sets(ca_proplist *p, const char *key, const char *value) { return ca_proplist_set(p, key, value, strlen(value)+1); } +/** + * ca_proplist_setf: + * @p: The property list to add this key/value pair to + * @key: The key for this key/value pair + * @format: The format string for the value for this key/value pair + * @...: The parameters for the format string + * + * Much like ca_proplist_sets(): add a new string key/value pair to + * the property list. Takes a standard C format string plus arguments + * and formats a string of it. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_proplist_setf(ca_proplist *p, const char *key, const char *format, ...) { int ret; char *k; @@ -168,6 +201,18 @@ finish: return ret; } +/** + * ca_proplist_set: + * @p: The property list to add this key/value pair to + * @key: The key for this key/value pair + * @data: The binary value for this key value pair + * @nbytes: The size of thebinary value for this key value pair. + * + * Add a new binary key/value pair to the property list. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_proplist_set(ca_proplist *p, const char *key, const void *data, size_t nbytes) { int ret; char *k; @@ -247,6 +292,15 @@ const char* ca_proplist_gets_unlocked(ca_proplist *p, const char *key) { return CA_PROP_DATA(prop); } +/** + * ca_proplist_destroy: + * @p: The property list to destroy + * + * Destroys a property list that was created with ca_proplist_create() earlier. + * + * Returns: 0 on success, negative error code on error. + */ + int ca_proplist_destroy(ca_proplist *p) { ca_prop *prop, *nprop; diff --git a/src/pulse.c b/src/pulse.c index a720231..41b745e 100644 --- a/src/pulse.c +++ b/src/pulse.c @@ -861,7 +861,7 @@ int driver_cache(ca_context *c, ca_proplist *proplist) { const char *n, *ct; char *name = NULL; pa_sample_spec ss; - ca_cache_control_t cache_control = CA_CACHE_CONTROL_NEVER; + ca_cache_control_t cache_control = CA_CACHE_CONTROL_PERMANENT; struct outstanding *out; int ret; -- cgit