File 0008-bluetooth-complete-bluetooth-profile-separation.patch of Package pulseaudio

From 698fb3bc26a679063c081cc3089cd9d515d34a4a Mon Sep 17 00:00:00 2001
From: "Igor V. Kovalenko" <igor.v.kovalenko@gmail.com>
Date: Fri, 29 Jan 2021 21:32:09 +0300
Subject: [PATCH 08/11] bluetooth: complete bluetooth profile separation

This is a follow-up change to review of these series on pulseaudio-discuss
https://lists.freedesktop.org/archives/pulseaudio-discuss/2017-September/028801.html

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/491>
---
 src/modules/bluetooth/backend-native.c          |   14 +++---
 src/modules/bluetooth/backend-ofono.c           |    2 
 src/modules/bluetooth/bluez5-util.c             |   11 +++-
 src/modules/bluetooth/bluez5-util.h             |    1 
 src/modules/bluetooth/module-bluetooth-policy.c |   18 ++++----
 src/modules/bluetooth/module-bluez5-device.c    |   54 +++++++++++++++++-------
 6 files changed, 64 insertions(+), 36 deletions(-)

--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -566,7 +566,7 @@ static void set_speaker_gain(pa_bluetoot
     /* If we are in the AG role, we send a command to the head set to change
      * the speaker gain. In the HS role, source and sink are swapped, so
      * in this case we notify the AG that the microphone gain has changed */
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS) {
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS || t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
         len = sprintf(buf, "\r\n+VGS=%d\r\n", gain);
         pa_log_debug("RFCOMM >> +VGS=%d", gain);
     } else {
@@ -593,7 +593,7 @@ static void set_microphone_gain(pa_bluet
     /* If we are in the AG role, we send a command to the head set to change
      * the microphone gain. In the HS role, source and sink are swapped, so
      * in this case we notify the AG that the speaker gain has changed */
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS) {
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS || t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
         len = sprintf(buf, "\r\n+VGM=%d\r\n", gain);
         pa_log_debug("RFCOMM >> +VGM=%d", gain);
     } else {
@@ -628,7 +628,7 @@ static DBusMessage *profile_new_connecti
     if (pa_streq(handler, HSP_AG_PROFILE)) {
         p = PA_BLUETOOTH_PROFILE_HSP_HS;
     } else if (pa_streq(handler, HSP_HS_PROFILE)) {
-        p = PA_BLUETOOTH_PROFILE_HFP_AG;
+        p = PA_BLUETOOTH_PROFILE_HSP_AG;
     } else if (pa_streq(handler, HFP_AG_PROFILE)) {
         p = PA_BLUETOOTH_PROFILE_HFP_HF;
     } else {
@@ -761,7 +761,7 @@ static void profile_init(pa_bluetooth_ba
             object_name = HSP_AG_PROFILE;
             uuid = PA_BLUETOOTH_UUID_HSP_AG;
             break;
-        case PA_BLUETOOTH_PROFILE_HFP_AG:
+        case PA_BLUETOOTH_PROFILE_HSP_AG:
             object_name = HSP_HS_PROFILE;
             uuid = PA_BLUETOOTH_UUID_HSP_HS;
             break;
@@ -785,7 +785,7 @@ static void profile_done(pa_bluetooth_ba
         case PA_BLUETOOTH_PROFILE_HSP_HS:
             dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_AG_PROFILE);
             break;
-        case PA_BLUETOOTH_PROFILE_HFP_AG:
+        case PA_BLUETOOTH_PROFILE_HSP_AG:
             dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_HS_PROFILE);
             break;
         case PA_BLUETOOTH_PROFILE_HFP_HF:
@@ -799,11 +799,11 @@ static void profile_done(pa_bluetooth_ba
 
 static void native_backend_apply_profile_registration_change(pa_bluetooth_backend *native_backend, bool enable_hs_role) {
     if (enable_hs_role) {
-        profile_init(native_backend, PA_BLUETOOTH_PROFILE_HFP_AG);
+        profile_init(native_backend, PA_BLUETOOTH_PROFILE_HSP_AG);
         if (native_backend->enable_hfp_hf)
             profile_init(native_backend, PA_BLUETOOTH_PROFILE_HFP_HF);
     } else {
-        profile_done(native_backend, PA_BLUETOOTH_PROFILE_HFP_AG);
+        profile_done(native_backend, PA_BLUETOOTH_PROFILE_HSP_AG);
         if (native_backend->enable_hfp_hf)
             profile_done(native_backend, PA_BLUETOOTH_PROFILE_HFP_HF);
     }
--- a/src/modules/bluetooth/backend-ofono.c
+++ b/src/modules/bluetooth/backend-ofono.c
@@ -337,7 +337,7 @@ static void hf_audio_agent_card_found(pa
             card->local_address = pa_xstrdup(value);
         } else if (pa_streq(key, "Type")) {
             if (pa_streq(value, "gateway"))
-                p = PA_BLUETOOTH_PROFILE_HSP_HS;
+                p = PA_BLUETOOTH_PROFILE_HFP_HF;
         }
 
         pa_log_debug("%s: %s", key, value);
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -196,11 +196,12 @@ static bool device_supports_profile(pa_b
             return show_hsp
                 && ( !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
                   || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT));
+        case PA_BLUETOOTH_PROFILE_HSP_AG:
+            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG);
         case PA_BLUETOOTH_PROFILE_HFP_HF:
             return show_hfp && !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF);
         case PA_BLUETOOTH_PROFILE_HFP_AG:
-            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG)
-                || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG);
+            return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG);
         case PA_BLUETOOTH_PROFILE_OFF:
             pa_assert_not_reached();
     }
@@ -1293,10 +1294,12 @@ const char *pa_bluetooth_profile_to_stri
             return "a2dp_source";
         case PA_BLUETOOTH_PROFILE_HSP_HS:
             return "headset_head_unit";
+        case PA_BLUETOOTH_PROFILE_HSP_AG:
+            return "headset_audio_gateway";
         case PA_BLUETOOTH_PROFILE_HFP_HF:
-            return "headset_handsfree";
+            return "handsfree_head_unit";
         case PA_BLUETOOTH_PROFILE_HFP_AG:
-            return "headset_audio_gateway";
+            return "handsfree_audio_gateway";
         case PA_BLUETOOTH_PROFILE_OFF:
             return "off";
     }
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -58,6 +58,7 @@ typedef enum profile {
     PA_BLUETOOTH_PROFILE_A2DP_SINK,
     PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
     PA_BLUETOOTH_PROFILE_HSP_HS,
+    PA_BLUETOOTH_PROFILE_HSP_AG,
     PA_BLUETOOTH_PROFILE_HFP_HF,
     PA_BLUETOOTH_PROFILE_HFP_AG,
     PA_BLUETOOTH_PROFILE_OFF
--- a/src/modules/bluetooth/module-bluetooth-policy.c
+++ b/src/modules/bluetooth/module-bluetooth-policy.c
@@ -38,7 +38,7 @@ PA_MODULE_LOAD_ONCE(true);
 PA_MODULE_USAGE(
         "auto_switch=<Switch between hsp and a2dp profile? (0 - never, 1 - media.role=phone, 2 - heuristic> "
         "a2dp_source=<Handle a2dp_source card profile (sink role)?> "
-        "ag=<Handle headset_audio_gateway card profile (headset role)?> ");
+        "ag=<Handle headset_audio_gateway or handsfree_audio_gateway card profile (headset role)?> ");
 
 static const char* const valid_modargs[] = {
     "auto_switch",
@@ -86,7 +86,7 @@ static pa_hook_result_t source_put_hook_
 
     if (u->enable_a2dp_source && pa_streq(s, "a2dp_source"))
         role = "music";
-    else if (u->enable_ag && pa_streq(s, "headset_audio_gateway"))
+    else if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway")))
         role = "phone";
     else {
         pa_log_debug("Profile %s cannot be selected for loopback", s);
@@ -125,7 +125,7 @@ static pa_hook_result_t sink_put_hook_ca
     if (!s)
         return PA_HOOK_OK;
 
-    if (u->enable_ag && pa_streq(s, "headset_audio_gateway"))
+    if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway")))
         role = "phone";
     else {
         pa_log_debug("Profile %s cannot be selected for loopback", s);
@@ -156,7 +156,7 @@ static void card_set_profile(struct user
             if (!pa_streq(profile->name, "a2dp_sink"))
                 continue;
         } else {
-            if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "headset_handsfree"))
+            if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "handsfree_head_unit"))
                 continue;
         }
 
@@ -190,8 +190,8 @@ static void switch_profile(pa_card *card
         if (!pa_hashmap_remove(u->will_need_revert_card_map, card))
             return;
 
-        /* Skip card if does not have active hsp profile */
-        if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "headset_handsfree"))
+        /* Skip card if does not have active headset profile */
+        if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "handsfree_head_unit"))
             return;
 
         /* Skip card if already has active a2dp profile */
@@ -202,8 +202,8 @@ static void switch_profile(pa_card *card
         if (!pa_streq(card->active_profile->name, "a2dp_sink"))
             return;
 
-        /* Skip card if already has active hsp profile */
-        if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "headset_handsfree"))
+        /* Skip card if already has active headset profile */
+        if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "handsfree_head_unit"))
             return;
     }
 
@@ -360,7 +360,7 @@ static pa_hook_result_t profile_availabl
     /* Do not automatically switch profiles for headsets, just in case */
     if (pa_streq(profile->name, "a2dp_sink") ||
         pa_streq(profile->name, "headset_head_unit") ||
-        pa_streq(profile->name, "headset_handsfree"))
+        pa_streq(profile->name, "handsfree_head_unit"))
         return PA_HOOK_OK;
 
     is_active_profile = card->active_profile == profile;
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -254,8 +254,9 @@ static int sco_process_render(struct use
 
     pa_assert(u);
     pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
-                u->profile == PA_BLUETOOTH_PROFILE_HFP_HF ||
-                u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
+              u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
+              u->profile == PA_BLUETOOTH_PROFILE_HFP_HF ||
+              u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
     pa_assert(u->sink);
 
     pa_sink_render_full(u->sink, u->write_block_size, &memchunk);
@@ -325,8 +326,9 @@ static int sco_process_push(struct userd
 
     pa_assert(u);
     pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
-                u->profile == PA_BLUETOOTH_PROFILE_HFP_HF||
-                u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
+              u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
+              u->profile == PA_BLUETOOTH_PROFILE_HFP_HF ||
+              u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
     pa_assert(u->source);
     pa_assert(u->read_smoother);
 
@@ -770,6 +772,7 @@ static void handle_sink_block_size_chang
 /* Run from I/O thread */
 static void transport_config_mtu(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
+        || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) {
         u->read_block_size = u->read_link_mtu;
@@ -988,7 +991,7 @@ static void source_set_volume_cb(pa_sour
     pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume);
 
     /* Set soft volume when in headset role */
-    if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG)
+    if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG)
         pa_cvolume_set(&s->soft_volume, u->decoder_sample_spec.channels, volume);
 
     /* If we are in the AG role, we send a command to the head set to change
@@ -1021,6 +1024,7 @@ static int add_source(struct userdata *u
         switch (u->profile) {
             case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
             case PA_BLUETOOTH_PROFILE_HFP_AG:
+            case PA_BLUETOOTH_PROFILE_HSP_AG:
                 data.suspend_cause = PA_SUSPEND_USER;
                 break;
             case PA_BLUETOOTH_PROFILE_HSP_HS:
@@ -1050,6 +1054,7 @@ static int add_source(struct userdata *u
     u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb;
 
     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
+        || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
@@ -1176,7 +1181,7 @@ static void sink_set_volume_cb(pa_sink *
     pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume);
 
     /* Set soft volume when in headset role */
-    if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG)
+    if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG)
         pa_cvolume_set(&s->soft_volume, u->encoder_sample_spec.channels, volume);
 
     /* If we are in the AG role, we send a command to the head set to change
@@ -1208,6 +1213,7 @@ static int add_sink(struct userdata *u)
     if (!u->transport_acquired)
         switch (u->profile) {
             case PA_BLUETOOTH_PROFILE_HFP_AG:
+            case PA_BLUETOOTH_PROFILE_HSP_AG:
                 data.suspend_cause = PA_SUSPEND_USER;
                 break;
             case PA_BLUETOOTH_PROFILE_HSP_HS:
@@ -1239,6 +1245,7 @@ static int add_sink(struct userdata *u)
     u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
 
     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
+        || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
@@ -1250,6 +1257,7 @@ static int add_sink(struct userdata *u)
 /* Run from main thread */
 static int transport_config(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
+        || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) {
         u->encoder_sample_spec.format = PA_SAMPLE_S16LE;
@@ -1302,7 +1310,7 @@ static int setup_transport(struct userda
 
     u->transport = t;
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG)
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG)
         transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
     else {
         int transport_error;
@@ -1321,6 +1329,7 @@ static pa_direction_t get_profile_direct
         [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
         [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
         [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_HSP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
         [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
         [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
         [PA_BLUETOOTH_PROFILE_OFF] = 0
@@ -1636,7 +1645,7 @@ static int start_thread(struct userdata
 
         /* If we are in the headset role, the sink should not become default
          * unless there is no other sound device available. */
-        if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG)
+        if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG)
             u->sink->priority = 1500;
 
         pa_sink_put(u->sink);
@@ -1652,7 +1661,7 @@ static int start_thread(struct userdata
         /* If we are in the headset role or the device is an a2dp source,
          * the source should not become default unless there is no other
          * sound device available. */
-        if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+        if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
             u->source->priority = 1500;
 
         pa_source_put(u->source);
@@ -1914,7 +1923,7 @@ static pa_card_profile *create_card_prof
         break;
 
     case PA_BLUETOOTH_PROFILE_HSP_HS:
-        cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
+        cp = pa_card_profile_new(name, _("Headset Head Unit (HSP)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 30;
         cp->n_sinks = 1;
         cp->n_sources = 1;
@@ -1926,8 +1935,21 @@ static pa_card_profile *create_card_prof
         p = PA_CARD_PROFILE_DATA(cp);
         break;
 
+    case PA_BLUETOOTH_PROFILE_HSP_AG:
+        cp = pa_card_profile_new(name, _("Headset Audio Gateway (HSP)"), sizeof(pa_bluetooth_profile_t));
+        cp->priority = 10;
+        cp->n_sinks = 1;
+        cp->n_sources = 1;
+        cp->max_sink_channels = 1;
+        cp->max_source_channels = 1;
+        pa_hashmap_put(input_port->profiles, cp->name, cp);
+        pa_hashmap_put(output_port->profiles, cp->name, cp);
+
+        p = PA_CARD_PROFILE_DATA(cp);
+        break;
+
     case PA_BLUETOOTH_PROFILE_HFP_HF:
-         cp = pa_card_profile_new(name, _("Headset Handsfree (HFP)"), sizeof(pa_bluetooth_profile_t));
+         cp = pa_card_profile_new(name, _("Handsfree Head Unit (HFP)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 30;
         cp->n_sinks = 1;
         cp->n_sources = 1;
@@ -1940,7 +1962,7 @@ static pa_card_profile *create_card_prof
         break;
 
     case PA_BLUETOOTH_PROFILE_HFP_AG:
-        cp = pa_card_profile_new(name, _("Headset Audio Gateway (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
+        cp = pa_card_profile_new(name, _("Handsfree Audio Gateway (HFP)"), sizeof(pa_bluetooth_profile_t));
         cp->priority = 10;
         cp->n_sinks = 1;
         cp->n_sources = 1;
@@ -2017,7 +2039,9 @@ static int uuid_to_profile(const char *u
         *_r = PA_BLUETOOTH_PROFILE_HSP_HS;
     else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
         *_r = PA_BLUETOOTH_PROFILE_HFP_HF;
-    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
+    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG))
+        *_r = PA_BLUETOOTH_PROFILE_HSP_AG;
+    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
         *_r = PA_BLUETOOTH_PROFILE_HFP_AG;
     else
         return -PA_ERR_INVALID;
@@ -2233,7 +2257,7 @@ static pa_hook_result_t transport_speake
         volume++;
 
     pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume);
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS)
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS || t->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
         pa_sink_volume_changed(u->sink, &v);
     else
         pa_sink_set_volume(u->sink, &v, true, true);
@@ -2261,7 +2285,7 @@ static pa_hook_result_t transport_microp
 
     pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume);
 
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS)
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS || t->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
         pa_source_volume_changed(u->source, &v);
     else
         pa_source_set_volume(u->source, &v, true, true);
openSUSE Build Service is sponsored by