The Mer Wiki now uses your Mer user account and password (create account on https://bugs.merproject.org/)
Nemo/Audio/MainVolume
From Mer Wiki
Contents |
MainVolume D-Bus interface
For PulseAudio's D-Bus interface see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/
Overview
MainVolume is module for controlling system volume from single point with easy interface, without need to know about the volume internals of PulseAudio. When module-meego-mainvolume module is loaded, following interface is exposed through PulseAudio's peer-to-peer D-Bus interface (see notes about connecting from PulseAudio documentation, http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/ConnectingToServer/).
MainVolume functionality
- Object /com/meego/mainvolume2 implements interface com.Meego.MainVolume2.
Interface major version 2, minor version 1
com.Meego.MainVolume2
Properties
- InterfaceRevision : Uint32 (r)
- Return current interface minor revision number
- StepCount : Uint32 (r)
- Return steps in currently active routing (step count may change whenever routing changes)
- CurrentStep : Uint32 (rw)
- Set/get currently active step. Range is 0..StepCount-1
- HighVolumeStep : Uint32 (r)
- If currently active routing has high volume step defined (for example with headphones, there might be some high steps that are deemed as harmful in long term listening, so these steps can be defined as "high volume steps"), this has value greater than 0. 0 means high volume steps aren't in use. High volume step is the first step above safe volume, so last safe volume step is HighVolumeStep - 1.
Signals
- StepsUpdated (StepCount: Uint32, CurrentStep: Uint32)
- This signal is sent whenever either StepCount or CurrentStep or both changes. If StepCount changes that is usually also an indication of changed routing, but volume controller should not care about the routing.
- NotifyListeningTime (ListeningTime: Uint32)
- Signal is sent when listening time warning should be issued to user. For example with headphones, one may define maximum safe continuous listening time before user should be notified. This signal is sent both when device with listening time connects for the first time and then continuously after listening time is full.
- NotifyHighVolume (SafeStep: Uint32)
- If routing has high volume step defined and routing changes, this signal is sent with SafeStep > 0. If routing does not have high volume step defined, this signal is sent with SafeStep 0.
- CallStatus (Status: String)
- Whenever voice call is state changes to active or inactive, this signal is sent with string "active" and "inactive", respectively.
D-Bus C API example
/* MainVolume D-Bus C API example version 0.1 * * Compile with * gcc -Wall $(pkg-config --libs --cflags dbus-glib-1) mainvolume-c-example.c -o mainvolume-c-example * */ #include <stdlib.h> #include <stdio.h> #include <stdint.h> #include <dbus/dbus.h> #include <dbus/dbus-glib-lowlevel.h> #define DBG(format, ...) printf("mainvolume: " format "\n", ##__VA_ARGS__) #define PULSE_CORE_PATH "/org/pulseaudio/core1" #define PULSE_CORE_IF "org.PulseAudio.Core1" #define MAINVOLUME_PATH "/com/meego/mainvolume2" #define MAINVOLUME_IF "com.Meego.MainVolume2" #define CURRENT_STEP_PROPERTY "CurrentStep" #define STEPS_UPDATED_MEMBER "StepsUpdated" #define LISTENING_TIME_MEMBER "NotifyListeningTime" #define HIGH_VOLUME_MEMBER "NotifyHighVolume" #define CALL_STATUS_MEMBER "CallStatus" #define MV_STEPS_UPDATED_SIGNAL MAINVOLUME_IF "." STEPS_UPDATED_MEMBER #define MV_LISTENING_TIME_SIGNAL MAINVOLUME_IF "." LISTENING_TIME_MEMBER #define MV_HIGH_VOLUME_SIGNAL MAINVOLUME_IF "." HIGH_VOLUME_MEMBER #define MV_CALL_STATUS_SIGNAL MAINVOLUME_IF "." CALL_STATUS_MEMBER #define DBUS_PROPERTIES_IF "org.freedesktop.DBus.Properties" #define PULSE_LOOKUP_DEST "org.PulseAudio1" #define PULSE_LOOKUP_PATH "/org/pulseaudio/server_lookup1" #define PULSE_LOOKUP_IF "org.PulseAudio.ServerLookup1" #define PULSE_LOOKUP_ADDRESS "Address" #define LISTEN_FOR_METHOD "ListenForSignal" #define STOP_LISTEN_FOR_METHOD "StopListeningForSignal" #define DISCONNECTED_SIG "Disconnected" #define RETRY_TIMEOUT 2 typedef struct _QueueItem { uint32_t step; } QueueItem; static GQueue *volume_queue = NULL; static DBusConnection *peer_to_peer_bus = NULL; static guint reconnect_timeout_id = 0; // Session bus is used to get PulseAudio dbus socket address when PULSE_DBUS_SERVER environment // variable is not set static DBusConnection *volume_session_bus = NULL; static gchar *peer_to_peer_pulse_address = NULL; static gboolean retry_timeout_cb (gpointer userdata); static DBusHandlerResult filter_cb (DBusConnection *connection, DBusMessage *msg, void *data); static gboolean set_current_step (uint32_t step); static void setup_mainvolume (); static void connect_to_pulseaudio (); static void disconnect_from_pulseaudio (); static void retry_connect (); static void get_address_reply_cb (DBusPendingCall *pending, void *data); static int update_current_step (uint32_t step); static void listen_for_signal (const char *signal, const char **objects); static void stop_listen_for_signal (const char *signal); static void retry_connect() { reconnect_timeout_id = g_timeout_add_seconds(RETRY_TIMEOUT, retry_timeout_cb, NULL); } static void get_address_reply_cb(DBusPendingCall *pending, void *data) { DBusMessageIter iter; DBusMessageIter sub; int current_type; char *address = NULL; DBusMessage *msg = NULL; msg = dbus_pending_call_steal_reply(pending); if (!msg) { DBG("Could not get reply from pending call."); goto retry; } dbus_message_iter_init(msg, &iter); // Reply string is inside DBUS_TYPE_VARIANT while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) { if (current_type == DBUS_TYPE_VARIANT) { dbus_message_iter_recurse(&iter, &sub); while ((current_type = dbus_message_iter_get_arg_type(&sub)) != DBUS_TYPE_INVALID) { if (current_type == DBUS_TYPE_STRING) dbus_message_iter_get_basic(&sub, &address); dbus_message_iter_next(&sub); } } dbus_message_iter_next(&iter); } if (address) { DBG("Got PulseAudio DBus address: %s", address); peer_to_peer_pulse_address = g_strdup(address); // Unref sesssion bus connection, it is not needed anymore. // Real communication is done with peer-to-peer connection. dbus_connection_unref(volume_session_bus); volume_session_bus = NULL; } // Always retry connection, if address was determined, it is used // to get peer-to-peer connection, if address wasn't determined, // we'll need to reconnect and retry anyway. retry: if (msg) dbus_message_unref(msg); if (pending) dbus_pending_call_unref(pending); retry_connect(); } static gboolean retry_timeout_cb(gpointer userdata) { DBG("Retry connecting to PulseAudio"); disconnect_from_pulseaudio(); connect_to_pulseaudio(); return FALSE; } static DBusHandlerResult filter_cb(DBusConnection *connection, DBusMessage *msg, void *data) { const char *call_status; uint32_t current_step; uint32_t step_count; uint32_t listening_time; uint32_t high_volume; if (dbus_message_has_interface(msg, DBUS_INTERFACE_LOCAL) && dbus_message_has_path (msg, DBUS_PATH_LOCAL) && dbus_message_has_member (msg, DISCONNECTED_SIG)) { DBG("pulseaudio disconnected, reconnecting in %d seconds", RETRY_TIMEOUT); disconnect_from_pulseaudio(); // If PulseAudio is restarting path to runtime files may change so we'll // have to request DBus address again. if (peer_to_peer_pulse_address) { g_free(peer_to_peer_pulse_address); peer_to_peer_pulse_address = NULL; } retry_connect(); } else if (dbus_message_has_interface(msg, MAINVOLUME_IF) && dbus_message_has_path (msg, MAINVOLUME_PATH) && dbus_message_has_member (msg, STEPS_UPDATED_MEMBER)) { if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &step_count, DBUS_TYPE_UINT32, ¤t_step, DBUS_TYPE_INVALID)) { DBG("failed to get arguments for new mainvolume steps updated"); goto end; } DBG("step count %u current step %u", step_count, current_step); } else if (dbus_message_has_interface(msg, MAINVOLUME_IF) && dbus_message_has_path (msg, MAINVOLUME_PATH) && dbus_message_has_member (msg, LISTENING_TIME_MEMBER)) { if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &listening_time, DBUS_TYPE_INVALID)) { DBG("failed to get arguments for mainvolume listening time"); goto end; } DBG("listening time notify %u", listening_time); } else if (dbus_message_has_interface(msg, MAINVOLUME_IF) && dbus_message_has_path (msg, MAINVOLUME_PATH) && dbus_message_has_member (msg, HIGH_VOLUME_MEMBER)) { if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &high_volume, DBUS_TYPE_INVALID)) { DBG("failed to get arguments for mainvolume high volume"); goto end; } DBG("high volume step %u", high_volume); } else if (dbus_message_has_interface(msg, MAINVOLUME_IF) && dbus_message_has_path (msg, MAINVOLUME_PATH) && dbus_message_has_member (msg, CALL_STATUS_MEMBER)) { if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &call_status, DBUS_TYPE_INVALID)) { DBG("failed to get arguments for mainvolume call status"); goto end; } DBG("call status %s", call_status); } end: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void listen_for_signal(const char *signal, const char **objects) { DBusMessage *msg = NULL; const char *empty[1] = { NULL }; int count; g_assert(peer_to_peer_bus); g_assert(signal); msg = dbus_message_new_method_call(NULL, PULSE_CORE_PATH, PULSE_CORE_IF, LISTEN_FOR_METHOD); if (!msg) goto done; if (!objects) objects = empty; for (count = 0; objects[count]; count++); dbus_message_append_args(msg, DBUS_TYPE_STRING, &signal, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objects, count, DBUS_TYPE_INVALID); if (dbus_connection_send(peer_to_peer_bus, msg, NULL)) { int i; DBG("listen for signal %s", signal); for (i = 0; i < count; i++) DBG("- object path: %s", objects[i]); } done: if (msg) dbus_message_unref(msg); } static void stop_listen_for_signal(const char *signal) { DBusMessage *msg = NULL; g_assert(peer_to_peer_bus); g_assert(signal); msg = dbus_message_new_method_call(NULL, PULSE_CORE_PATH, PULSE_CORE_IF, STOP_LISTEN_FOR_METHOD); if (!msg) goto done; dbus_message_append_args(msg, DBUS_TYPE_STRING, &signal, DBUS_TYPE_INVALID); if (dbus_connection_send(peer_to_peer_bus, msg, NULL)) DBG("stop listening for signal %s", signal); done: if (msg) dbus_message_unref(msg); } static gboolean set_current_step(uint32_t step) { DBusMessage *msg = NULL; DBusMessage *reply = NULL; const gchar *iface = MAINVOLUME_IF; const gchar *addr = CURRENT_STEP_PROPERTY; DBusError error; DBusMessageIter iter; DBusMessageIter variant; gboolean ret = FALSE; g_assert(peer_to_peer_bus); dbus_error_init(&error); msg = dbus_message_new_method_call(MAINVOLUME_IF, MAINVOLUME_PATH, DBUS_PROPERTIES_IF, "Set"); if (msg == NULL) goto done; dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &addr); dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "u", &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_UINT32, &step); dbus_message_iter_close_container(&iter, &variant); reply = dbus_connection_send_with_reply_and_block(peer_to_peer_bus, msg, -1, &error); if (!reply) { if (dbus_error_is_set(&error)) DBG("couldn't set current step: %s", error.message); goto done; } ret = TRUE; done: dbus_error_free(&error); if (reply) dbus_message_unref(reply); if (msg) dbus_message_unref(msg); return ret; } static void setup_mainvolume() { QueueItem *queued_volume = NULL; listen_for_signal(MV_STEPS_UPDATED_SIGNAL, NULL); listen_for_signal(MV_LISTENING_TIME_SIGNAL, NULL); listen_for_signal(MV_HIGH_VOLUME_SIGNAL, NULL); listen_for_signal(MV_CALL_STATUS_SIGNAL, NULL); while ((queued_volume = g_queue_pop_head(volume_queue)) != NULL) { DBG("processing queued step %u", queued_volume->step); set_current_step(queued_volume->step); g_slice_free(QueueItem, queued_volume); } } static gboolean connect_peer_to_peer() { DBusError error; dbus_error_init(&error); peer_to_peer_bus = dbus_connection_open(peer_to_peer_pulse_address, &error); if (dbus_error_is_set(&error)) { DBG("failed to open connection to pulseaudio: %s", error.message); dbus_error_free(&error); return FALSE; } dbus_connection_setup_with_g_main(peer_to_peer_bus, NULL); if (!dbus_connection_add_filter(peer_to_peer_bus, filter_cb, NULL, NULL)) { DBG("failed to add filter"); return FALSE; } setup_mainvolume(); return TRUE; } static gboolean connect_get_address() { DBusError error; DBusMessage *msg = NULL; DBusPendingCall *pending = NULL; const char *iface = PULSE_LOOKUP_IF; const char *addr = PULSE_LOOKUP_ADDRESS; dbus_error_init(&error); if (volume_session_bus && !dbus_connection_get_is_connected(volume_session_bus)) { dbus_connection_unref(volume_session_bus); volume_session_bus = NULL; } if (!volume_session_bus) volume_session_bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { DBG("failed to open connection to session bus: %s", error.message); dbus_error_free(&error); goto fail; } dbus_connection_setup_with_g_main(volume_session_bus, NULL); if (!(msg = dbus_message_new_method_call(PULSE_LOOKUP_DEST, PULSE_LOOKUP_PATH, DBUS_PROPERTIES_IF, "Get"))) goto fail; if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &addr, DBUS_TYPE_INVALID)) goto fail; if (!dbus_connection_send_with_reply(volume_session_bus, msg, &pending, DBUS_TIMEOUT_USE_DEFAULT)) goto fail; if (!pending) goto fail; if (!dbus_pending_call_set_notify(pending, get_address_reply_cb, NULL, NULL)) goto fail; dbus_message_unref(msg); return TRUE; fail: if (pending) { dbus_pending_call_cancel(pending); dbus_pending_call_unref(pending); } if (msg) dbus_message_unref(msg); return FALSE; } static void connect_to_pulseaudio() { const char *pulse_address = NULL; gboolean success; if (!peer_to_peer_pulse_address && (pulse_address = getenv("PULSE_DBUS_SERVER"))) peer_to_peer_pulse_address = g_strdup(pulse_address); if (peer_to_peer_pulse_address) success = connect_peer_to_peer(); else success = connect_get_address(); if (!success) retry_connect(); } static void disconnect_from_pulseaudio() { if (reconnect_timeout_id > 0) { g_source_remove(reconnect_timeout_id); reconnect_timeout_id = 0; } if (peer_to_peer_bus) { dbus_connection_unref(peer_to_peer_bus); peer_to_peer_bus = NULL; } } static int update_current_step(uint32_t step) { QueueItem *item = NULL; if (!peer_to_peer_bus) { DBG("volume controller not ready, queueing op."); item = g_slice_new0(QueueItem); item->step = step; g_queue_push_tail(volume_queue, item); return TRUE; } return set_current_step(step); } int main(int argc, char **argv) { GMainLoop *mainloop = g_main_loop_new(NULL, FALSE); if ((volume_queue = g_queue_new()) == NULL) return FALSE; peer_to_peer_pulse_address = NULL; if (argc > 1) { int i; for (i = 1; i < argc; i++) update_current_step(atoi(argv[i])); } connect_to_pulseaudio(); g_main_loop_run(mainloop); disconnect_from_pulseaudio(); if (volume_queue) { g_queue_free(volume_queue); volume_queue = NULL; } if (volume_session_bus) { dbus_connection_unref(volume_session_bus); volume_session_bus = NULL; } if (peer_to_peer_pulse_address) { g_free(peer_to_peer_pulse_address); peer_to_peer_pulse_address = NULL; } return 0; }