The Mer Wiki now uses your Mer user account and password (create account on https://bugs.merproject.org/)


Nemo/Audio/MainVolume

From Mer Wiki
Jump to: navigation, search

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.
  • CallState : String (r)
    • Voice call state, "active" or "inactive"
  • MediaState : String (r)
    • Media playback state, "inactive", "background", "foreground", "active"

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.
  • CallStateChanged (State: String)
    • Whenever voice call is state changes to active or inactive, this signal is sent with string "active" and "inactive", respectively.
  • MediaStateChanged (State: String)
    • Signal is sent whenever media playback state changes.

D-Bus C API example

/* MainVolume D-Bus C API example version 0.4
 *
 * Compile with
 * gcc -Wall $(pkg-config --libs --cflags dbus-glib-1) mainvolume-c-example.c -o mainvolume-c-example
 *
 * This example code is free to be used in any way.
 */

#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_STATE_MEMBER           "CallStateChanged"

#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_STATE_SIGNAL        MAINVOLUME_IF "." CALL_STATE_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              (gboolean wait);
static void              get_address_reply_cb       (DBusPendingCall *pending, void *data);

static int               update_current_step        (uint32_t step);
static gboolean          get_value_uint32           (const char *property_name, uint32_t *value_reply);
static void              listen_for_signal          (const char *signal, const char **objects);
static void              stop_listen_for_signal     (const char *signal);


static void retry_connect(gboolean wait)
{
    if (wait)
        reconnect_timeout_id = g_timeout_add_seconds(RETRY_TIMEOUT,
                                                     retry_timeout_cb, NULL);
    else
        reconnect_timeout_id = g_idle_add(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(FALSE);
}

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_state;
    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(TRUE);
    }
    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, &current_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_STATE_MEMBER))
    {
        if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &call_state,
                                              DBUS_TYPE_INVALID)) {
            DBG("failed to get arguments for mainvolume call state");
            goto end;
        }

        DBG("call state %s", call_state);
    }

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;
    DBusMessageIter  iter;
    DBusMessageIter  variant;
    gboolean         ret         = FALSE;

    g_assert(peer_to_peer_bus);

    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);

    // If you want to get the reply from MainVolume when setting a step,
    // do not use sync call.
    dbus_connection_send(peer_to_peer_bus, msg, NULL);

    ret = TRUE;

done:
    if (reply) dbus_message_unref(reply);
    if (msg)   dbus_message_unref(msg);

    return ret;
}

static gboolean get_value_uint32(const char *property_name, uint32_t *value_reply)
{
    DBusMessage     *msg         = NULL;
    DBusMessage     *reply       = NULL;
    gboolean         ret         = FALSE;
    const gchar     *iface       = MAINVOLUME_IF;
    DBusError        error;
    DBusMessageIter  iter;
    DBusMessageIter  sub;
    int              current_type;

    g_assert(peer_to_peer_bus);
    g_assert(property_name);
    g_assert(value_reply);

    dbus_error_init(&error);

    msg = dbus_message_new_method_call(MAINVOLUME_IF,
                                       MAINVOLUME_PATH,
                                       DBUS_PROPERTIES_IF,
                                       "Get");

    if (msg == NULL)
        goto done;

    if (!dbus_message_append_args(msg,
                                  DBUS_TYPE_STRING, &iface,
                                  DBUS_TYPE_STRING, &property_name,
                                  DBUS_TYPE_INVALID))
        goto done;

    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 get value for property %s: %s", property_name, error.message);

        goto done;
    }

    dbus_message_iter_init(reply, &iter);

    // Reply value 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_UINT32) {
                    dbus_message_iter_get_basic(&sub, value_reply);
                    ret = TRUE;
                }
                dbus_message_iter_next(&sub);
            }
        }

        dbus_message_iter_next(&iter);
    }

    if (!ret) {
        DBG("failed to get property value");
        goto done;
    }

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;
    uint32_t value;

    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_STATE_SIGNAL, NULL);

    DBG("query currently active values");

    // Get current active values
    if (get_value_uint32("StepCount", &value))
        DBG("current StepCount: %u", value);
    if (get_value_uint32("CurrentStep", &value))
        DBG("current CurrentStep: %u", value);
    if (get_value_uint32("HighVolumeStep", &value))
        DBG("current HighVolumeStep: %u", value);

    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(TRUE);
}

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;
}
Personal tools