Replace a config-time define with a compile time condition define (compatible with clang and gcc) that must be declared prior to its usage. This avoids having a global configure time define, but also prevents from bad usage, if the config header wasn't included before. This can help to make some code independent from qemu too. gcc supports __BYTE_ORDER__ from about 4.6 and clang from 3.2. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> [ For the s390x parts I'm involved in ] Acked-by: Halil Pasic <pasic@linux.ibm.com> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-Id: <20220323155743.1585078-7-marcandre.lureau@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
		
			
				
	
	
		
			655 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			655 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * QEMU DBus audio
 | 
						|
 *
 | 
						|
 * Copyright (c) 2021 Red Hat, Inc.
 | 
						|
 *
 | 
						|
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
 * of this software and associated documentation files (the "Software"), to deal
 | 
						|
 * in the Software without restriction, including without limitation the rights
 | 
						|
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
						|
 * copies of the Software, and to permit persons to whom the Software is
 | 
						|
 * furnished to do so, subject to the following conditions:
 | 
						|
 *
 | 
						|
 * The above copyright notice and this permission notice shall be included in
 | 
						|
 * all copies or substantial portions of the Software.
 | 
						|
 *
 | 
						|
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 | 
						|
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						|
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
						|
 * THE SOFTWARE.
 | 
						|
 */
 | 
						|
 | 
						|
#include "qemu/osdep.h"
 | 
						|
#include "qemu/error-report.h"
 | 
						|
#include "qemu/host-utils.h"
 | 
						|
#include "qemu/module.h"
 | 
						|
#include "qemu/timer.h"
 | 
						|
#include "qemu/dbus.h"
 | 
						|
 | 
						|
#include <gio/gunixfdlist.h>
 | 
						|
#include "ui/dbus-display1.h"
 | 
						|
 | 
						|
#define AUDIO_CAP "dbus"
 | 
						|
#include "audio.h"
 | 
						|
#include "audio_int.h"
 | 
						|
#include "trace.h"
 | 
						|
 | 
						|
#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
 | 
						|
 | 
						|
#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
 | 
						|
 | 
						|
typedef struct DBusAudio {
 | 
						|
    GDBusObjectManagerServer *server;
 | 
						|
    GDBusObjectSkeleton *audio;
 | 
						|
    QemuDBusDisplay1Audio *iface;
 | 
						|
    GHashTable *out_listeners;
 | 
						|
    GHashTable *in_listeners;
 | 
						|
} DBusAudio;
 | 
						|
 | 
						|
typedef struct DBusVoiceOut {
 | 
						|
    HWVoiceOut hw;
 | 
						|
    bool enabled;
 | 
						|
    RateCtl rate;
 | 
						|
 | 
						|
    void *buf;
 | 
						|
    size_t buf_pos;
 | 
						|
    size_t buf_size;
 | 
						|
 | 
						|
    bool has_volume;
 | 
						|
    Volume volume;
 | 
						|
} DBusVoiceOut;
 | 
						|
 | 
						|
typedef struct DBusVoiceIn {
 | 
						|
    HWVoiceIn hw;
 | 
						|
    bool enabled;
 | 
						|
    RateCtl rate;
 | 
						|
 | 
						|
    bool has_volume;
 | 
						|
    Volume volume;
 | 
						|
} DBusVoiceIn;
 | 
						|
 | 
						|
static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
 | 
						|
{
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
 | 
						|
    if (!vo->buf) {
 | 
						|
        vo->buf_size = hw->samples * hw->info.bytes_per_frame;
 | 
						|
        vo->buf = g_malloc(vo->buf_size);
 | 
						|
        vo->buf_pos = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    *size = MIN(vo->buf_size - vo->buf_pos, *size);
 | 
						|
    *size = audio_rate_get_bytes(&hw->info, &vo->rate, *size);
 | 
						|
 | 
						|
    return vo->buf + vo->buf_pos;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioOutListener *listener = NULL;
 | 
						|
    g_autoptr(GBytes) bytes = NULL;
 | 
						|
    g_autoptr(GVariant) v_data = NULL;
 | 
						|
 | 
						|
    assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
 | 
						|
    vo->buf_pos += size;
 | 
						|
 | 
						|
    trace_dbus_audio_put_buffer_out(size);
 | 
						|
 | 
						|
    if (vo->buf_pos < vo->buf_size) {
 | 
						|
        return size;
 | 
						|
    }
 | 
						|
 | 
						|
    bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);
 | 
						|
    v_data = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
 | 
						|
    g_variant_ref_sink(v_data);
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->out_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        qemu_dbus_display1_audio_out_listener_call_write(
 | 
						|
            listener,
 | 
						|
            (uintptr_t)hw,
 | 
						|
            v_data,
 | 
						|
            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
#if HOST_BIG_ENDIAN
 | 
						|
#define AUDIO_HOST_BE TRUE
 | 
						|
#else
 | 
						|
#define AUDIO_HOST_BE FALSE
 | 
						|
#endif
 | 
						|
 | 
						|
static void
 | 
						|
dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
 | 
						|
                       HWVoiceOut *hw)
 | 
						|
{
 | 
						|
    qemu_dbus_display1_audio_out_listener_call_init(
 | 
						|
        listener,
 | 
						|
        (uintptr_t)hw,
 | 
						|
        hw->info.bits,
 | 
						|
        hw->info.is_signed,
 | 
						|
        hw->info.is_float,
 | 
						|
        hw->info.freq,
 | 
						|
        hw->info.nchannels,
 | 
						|
        hw->info.bytes_per_frame,
 | 
						|
        hw->info.bytes_per_second,
 | 
						|
        hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
 | 
						|
        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioOutListener *listener = NULL;
 | 
						|
 | 
						|
    audio_pcm_init_info(&hw->info, as);
 | 
						|
    hw->samples = DBUS_AUDIO_NSAMPLES;
 | 
						|
    audio_rate_start(&vo->rate);
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->out_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        dbus_init_out_listener(listener, hw);
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_fini_out(HWVoiceOut *hw)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioOutListener *listener = NULL;
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->out_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        qemu_dbus_display1_audio_out_listener_call_fini(
 | 
						|
            listener,
 | 
						|
            (uintptr_t)hw,
 | 
						|
            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    g_clear_pointer(&vo->buf, g_free);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_enable_out(HWVoiceOut *hw, bool enable)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioOutListener *listener = NULL;
 | 
						|
 | 
						|
    vo->enabled = enable;
 | 
						|
    if (enable) {
 | 
						|
        audio_rate_start(&vo->rate);
 | 
						|
    }
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->out_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        qemu_dbus_display1_audio_out_listener_call_set_enabled(
 | 
						|
            listener, (uintptr_t)hw, enable,
 | 
						|
            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_volume_out_listener(HWVoiceOut *hw,
 | 
						|
                         QemuDBusDisplay1AudioOutListener *listener)
 | 
						|
{
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
    Volume *vol = &vo->volume;
 | 
						|
    g_autoptr(GBytes) bytes = NULL;
 | 
						|
    GVariant *v_vol = NULL;
 | 
						|
 | 
						|
    if (!vo->has_volume) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    assert(vol->channels < sizeof(vol->vol));
 | 
						|
    bytes = g_bytes_new(vol->vol, vol->channels);
 | 
						|
    v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
 | 
						|
    qemu_dbus_display1_audio_out_listener_call_set_volume(
 | 
						|
        listener, (uintptr_t)hw, vol->mute, v_vol,
 | 
						|
        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_volume_out(HWVoiceOut *hw, Volume *vol)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioOutListener *listener = NULL;
 | 
						|
 | 
						|
    vo->has_volume = true;
 | 
						|
    vo->volume = *vol;
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->out_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        dbus_volume_out_listener(hw, listener);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)
 | 
						|
{
 | 
						|
    qemu_dbus_display1_audio_in_listener_call_init(
 | 
						|
        listener,
 | 
						|
        (uintptr_t)hw,
 | 
						|
        hw->info.bits,
 | 
						|
        hw->info.is_signed,
 | 
						|
        hw->info.is_float,
 | 
						|
        hw->info.freq,
 | 
						|
        hw->info.nchannels,
 | 
						|
        hw->info.bytes_per_frame,
 | 
						|
        hw->info.bytes_per_second,
 | 
						|
        hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
 | 
						|
        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioInListener *listener = NULL;
 | 
						|
 | 
						|
    audio_pcm_init_info(&hw->info, as);
 | 
						|
    hw->samples = DBUS_AUDIO_NSAMPLES;
 | 
						|
    audio_rate_start(&vo->rate);
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->in_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        dbus_init_in_listener(listener, hw);
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_fini_in(HWVoiceIn *hw)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioInListener *listener = NULL;
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->in_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        qemu_dbus_display1_audio_in_listener_call_fini(
 | 
						|
            listener,
 | 
						|
            (uintptr_t)hw,
 | 
						|
            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_volume_in_listener(HWVoiceIn *hw,
 | 
						|
                         QemuDBusDisplay1AudioInListener *listener)
 | 
						|
{
 | 
						|
    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
 | 
						|
    Volume *vol = &vo->volume;
 | 
						|
    g_autoptr(GBytes) bytes = NULL;
 | 
						|
    GVariant *v_vol = NULL;
 | 
						|
 | 
						|
    if (!vo->has_volume) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    assert(vol->channels < sizeof(vol->vol));
 | 
						|
    bytes = g_bytes_new(vol->vol, vol->channels);
 | 
						|
    v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
 | 
						|
    qemu_dbus_display1_audio_in_listener_call_set_volume(
 | 
						|
        listener, (uintptr_t)hw, vol->mute, v_vol,
 | 
						|
        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_volume_in(HWVoiceIn *hw, Volume *vol)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioInListener *listener = NULL;
 | 
						|
 | 
						|
    vo->has_volume = true;
 | 
						|
    vo->volume = *vol;
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->in_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        dbus_volume_in_listener(hw, listener);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static size_t
 | 
						|
dbus_read(HWVoiceIn *hw, void *buf, size_t size)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioInListener *listener = NULL;
 | 
						|
 | 
						|
    trace_dbus_audio_read(size);
 | 
						|
 | 
						|
    /* size = audio_rate_get_bytes(&hw->info, &vo->rate, size); */
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->in_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        g_autoptr(GVariant) v_data = NULL;
 | 
						|
        const char *data;
 | 
						|
        gsize n = 0;
 | 
						|
 | 
						|
        if (qemu_dbus_display1_audio_in_listener_call_read_sync(
 | 
						|
                listener,
 | 
						|
                (uintptr_t)hw,
 | 
						|
                size,
 | 
						|
                G_DBUS_CALL_FLAGS_NONE, -1,
 | 
						|
                &v_data, NULL, NULL)) {
 | 
						|
            data = g_variant_get_fixed_array(v_data, &n, 1);
 | 
						|
            g_warn_if_fail(n <= size);
 | 
						|
            size = MIN(n, size);
 | 
						|
            memcpy(buf, data, size);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return size;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_enable_in(HWVoiceIn *hw, bool enable)
 | 
						|
{
 | 
						|
    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
 | 
						|
    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
 | 
						|
    GHashTableIter iter;
 | 
						|
    QemuDBusDisplay1AudioInListener *listener = NULL;
 | 
						|
 | 
						|
    vo->enabled = enable;
 | 
						|
    if (enable) {
 | 
						|
        audio_rate_start(&vo->rate);
 | 
						|
    }
 | 
						|
 | 
						|
    g_hash_table_iter_init(&iter, da->in_listeners);
 | 
						|
    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
 | 
						|
        qemu_dbus_display1_audio_in_listener_call_set_enabled(
 | 
						|
            listener, (uintptr_t)hw, enable,
 | 
						|
            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void *
 | 
						|
dbus_audio_init(Audiodev *dev)
 | 
						|
{
 | 
						|
    DBusAudio *da = g_new0(DBusAudio, 1);
 | 
						|
 | 
						|
    da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
 | 
						|
                                                g_free, g_object_unref);
 | 
						|
    da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
 | 
						|
                                               g_free, g_object_unref);
 | 
						|
    return da;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_audio_fini(void *opaque)
 | 
						|
{
 | 
						|
    DBusAudio *da = opaque;
 | 
						|
 | 
						|
    if (da->server) {
 | 
						|
        g_dbus_object_manager_server_unexport(da->server,
 | 
						|
                                              DBUS_DISPLAY1_AUDIO_PATH);
 | 
						|
    }
 | 
						|
    g_clear_object(&da->audio);
 | 
						|
    g_clear_object(&da->iface);
 | 
						|
    g_clear_pointer(&da->in_listeners, g_hash_table_unref);
 | 
						|
    g_clear_pointer(&da->out_listeners, g_hash_table_unref);
 | 
						|
    g_clear_object(&da->server);
 | 
						|
    g_free(da);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
listener_out_vanished_cb(GDBusConnection *connection,
 | 
						|
                         gboolean remote_peer_vanished,
 | 
						|
                         GError *error,
 | 
						|
                         DBusAudio *da)
 | 
						|
{
 | 
						|
    char *name = g_object_get_data(G_OBJECT(connection), "name");
 | 
						|
 | 
						|
    g_hash_table_remove(da->out_listeners, name);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
listener_in_vanished_cb(GDBusConnection *connection,
 | 
						|
                        gboolean remote_peer_vanished,
 | 
						|
                        GError *error,
 | 
						|
                        DBusAudio *da)
 | 
						|
{
 | 
						|
    char *name = g_object_get_data(G_OBJECT(connection), "name");
 | 
						|
 | 
						|
    g_hash_table_remove(da->in_listeners, name);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
dbus_audio_register_listener(AudioState *s,
 | 
						|
                             GDBusMethodInvocation *invocation,
 | 
						|
                             GUnixFDList *fd_list,
 | 
						|
                             GVariant *arg_listener,
 | 
						|
                             bool out)
 | 
						|
{
 | 
						|
    DBusAudio *da = s->drv_opaque;
 | 
						|
    const char *sender = g_dbus_method_invocation_get_sender(invocation);
 | 
						|
    g_autoptr(GDBusConnection) listener_conn = NULL;
 | 
						|
    g_autoptr(GError) err = NULL;
 | 
						|
    g_autoptr(GSocket) socket = NULL;
 | 
						|
    g_autoptr(GSocketConnection) socket_conn = NULL;
 | 
						|
    g_autofree char *guid = g_dbus_generate_guid();
 | 
						|
    GHashTable *listeners = out ? da->out_listeners : da->in_listeners;
 | 
						|
    GObject *listener;
 | 
						|
    int fd;
 | 
						|
 | 
						|
    trace_dbus_audio_register(sender, out ? "out" : "in");
 | 
						|
 | 
						|
    if (g_hash_table_contains(listeners, sender)) {
 | 
						|
        g_dbus_method_invocation_return_error(invocation,
 | 
						|
                                              DBUS_DISPLAY_ERROR,
 | 
						|
                                              DBUS_DISPLAY_ERROR_INVALID,
 | 
						|
                                              "`%s` is already registered!",
 | 
						|
                                              sender);
 | 
						|
        return DBUS_METHOD_INVOCATION_HANDLED;
 | 
						|
    }
 | 
						|
 | 
						|
    fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
 | 
						|
    if (err) {
 | 
						|
        g_dbus_method_invocation_return_error(invocation,
 | 
						|
                                              DBUS_DISPLAY_ERROR,
 | 
						|
                                              DBUS_DISPLAY_ERROR_FAILED,
 | 
						|
                                              "Couldn't get peer fd: %s",
 | 
						|
                                              err->message);
 | 
						|
        return DBUS_METHOD_INVOCATION_HANDLED;
 | 
						|
    }
 | 
						|
 | 
						|
    socket = g_socket_new_from_fd(fd, &err);
 | 
						|
    if (err) {
 | 
						|
        g_dbus_method_invocation_return_error(invocation,
 | 
						|
                                              DBUS_DISPLAY_ERROR,
 | 
						|
                                              DBUS_DISPLAY_ERROR_FAILED,
 | 
						|
                                              "Couldn't make a socket: %s",
 | 
						|
                                              err->message);
 | 
						|
        return DBUS_METHOD_INVOCATION_HANDLED;
 | 
						|
    }
 | 
						|
    socket_conn = g_socket_connection_factory_create_connection(socket);
 | 
						|
    if (out) {
 | 
						|
        qemu_dbus_display1_audio_complete_register_out_listener(
 | 
						|
            da->iface, invocation, NULL);
 | 
						|
    } else {
 | 
						|
        qemu_dbus_display1_audio_complete_register_in_listener(
 | 
						|
            da->iface, invocation, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    listener_conn =
 | 
						|
        g_dbus_connection_new_sync(
 | 
						|
            G_IO_STREAM(socket_conn),
 | 
						|
            guid,
 | 
						|
            G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
 | 
						|
            NULL, NULL, &err);
 | 
						|
    if (err) {
 | 
						|
        error_report("Failed to setup peer connection: %s", err->message);
 | 
						|
        return DBUS_METHOD_INVOCATION_HANDLED;
 | 
						|
    }
 | 
						|
 | 
						|
    listener = out ?
 | 
						|
        G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
 | 
						|
            listener_conn,
 | 
						|
            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 | 
						|
            NULL,
 | 
						|
            "/org/qemu/Display1/AudioOutListener",
 | 
						|
            NULL,
 | 
						|
            &err)) :
 | 
						|
        G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
 | 
						|
            listener_conn,
 | 
						|
            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 | 
						|
            NULL,
 | 
						|
            "/org/qemu/Display1/AudioInListener",
 | 
						|
            NULL,
 | 
						|
            &err));
 | 
						|
    if (!listener) {
 | 
						|
        error_report("Failed to setup proxy: %s", err->message);
 | 
						|
        return DBUS_METHOD_INVOCATION_HANDLED;
 | 
						|
    }
 | 
						|
 | 
						|
    if (out) {
 | 
						|
        HWVoiceOut *hw;
 | 
						|
 | 
						|
        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
 | 
						|
            DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
 | 
						|
            QemuDBusDisplay1AudioOutListener *l =
 | 
						|
                QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener);
 | 
						|
 | 
						|
            dbus_init_out_listener(l, hw);
 | 
						|
            qemu_dbus_display1_audio_out_listener_call_set_enabled(
 | 
						|
                l, (uintptr_t)hw, vo->enabled,
 | 
						|
                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        HWVoiceIn *hw;
 | 
						|
 | 
						|
        QLIST_FOREACH(hw, &s->hw_head_in, entries) {
 | 
						|
            DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
 | 
						|
            QemuDBusDisplay1AudioInListener *l =
 | 
						|
                QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener);
 | 
						|
 | 
						|
            dbus_init_in_listener(
 | 
						|
                QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);
 | 
						|
            qemu_dbus_display1_audio_in_listener_call_set_enabled(
 | 
						|
                l, (uintptr_t)hw, vo->enabled,
 | 
						|
                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    g_object_set_data_full(G_OBJECT(listener_conn), "name",
 | 
						|
                           g_strdup(sender), g_free);
 | 
						|
    g_hash_table_insert(listeners, g_strdup(sender), listener);
 | 
						|
    g_object_connect(listener_conn,
 | 
						|
                     "signal::closed",
 | 
						|
                     out ? listener_out_vanished_cb : listener_in_vanished_cb,
 | 
						|
                     da,
 | 
						|
                     NULL);
 | 
						|
 | 
						|
    return DBUS_METHOD_INVOCATION_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
dbus_audio_register_out_listener(AudioState *s,
 | 
						|
                                 GDBusMethodInvocation *invocation,
 | 
						|
                                 GUnixFDList *fd_list,
 | 
						|
                                 GVariant *arg_listener)
 | 
						|
{
 | 
						|
    return dbus_audio_register_listener(s, invocation,
 | 
						|
                                        fd_list, arg_listener, true);
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
dbus_audio_register_in_listener(AudioState *s,
 | 
						|
                                GDBusMethodInvocation *invocation,
 | 
						|
                                GUnixFDList *fd_list,
 | 
						|
                                GVariant *arg_listener)
 | 
						|
{
 | 
						|
    return dbus_audio_register_listener(s, invocation,
 | 
						|
                                        fd_list, arg_listener, false);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)
 | 
						|
{
 | 
						|
    DBusAudio *da = s->drv_opaque;
 | 
						|
 | 
						|
    g_assert(da);
 | 
						|
    g_assert(!da->server);
 | 
						|
 | 
						|
    da->server = g_object_ref(server);
 | 
						|
 | 
						|
    da->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH);
 | 
						|
    da->iface = qemu_dbus_display1_audio_skeleton_new();
 | 
						|
    g_object_connect(da->iface,
 | 
						|
                     "swapped-signal::handle-register-in-listener",
 | 
						|
                     dbus_audio_register_in_listener, s,
 | 
						|
                     "swapped-signal::handle-register-out-listener",
 | 
						|
                     dbus_audio_register_out_listener, s,
 | 
						|
                     NULL);
 | 
						|
 | 
						|
    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),
 | 
						|
                                         G_DBUS_INTERFACE_SKELETON(da->iface));
 | 
						|
    g_dbus_object_manager_server_export(da->server, da->audio);
 | 
						|
}
 | 
						|
 | 
						|
static struct audio_pcm_ops dbus_pcm_ops = {
 | 
						|
    .init_out = dbus_init_out,
 | 
						|
    .fini_out = dbus_fini_out,
 | 
						|
    .write    = audio_generic_write,
 | 
						|
    .get_buffer_out = dbus_get_buffer_out,
 | 
						|
    .put_buffer_out = dbus_put_buffer_out,
 | 
						|
    .enable_out = dbus_enable_out,
 | 
						|
    .volume_out = dbus_volume_out,
 | 
						|
 | 
						|
    .init_in  = dbus_init_in,
 | 
						|
    .fini_in  = dbus_fini_in,
 | 
						|
    .read     = dbus_read,
 | 
						|
    .run_buffer_in = audio_generic_run_buffer_in,
 | 
						|
    .enable_in = dbus_enable_in,
 | 
						|
    .volume_in = dbus_volume_in,
 | 
						|
};
 | 
						|
 | 
						|
static struct audio_driver dbus_audio_driver = {
 | 
						|
    .name            = "dbus",
 | 
						|
    .descr           = "Timer based audio exposed with DBus interface",
 | 
						|
    .init            = dbus_audio_init,
 | 
						|
    .fini            = dbus_audio_fini,
 | 
						|
    .set_dbus_server = dbus_audio_set_server,
 | 
						|
    .pcm_ops         = &dbus_pcm_ops,
 | 
						|
    .can_be_default  = 1,
 | 
						|
    .max_voices_out  = INT_MAX,
 | 
						|
    .max_voices_in   = INT_MAX,
 | 
						|
    .voice_size_out  = sizeof(DBusVoiceOut),
 | 
						|
    .voice_size_in   = sizeof(DBusVoiceIn)
 | 
						|
};
 | 
						|
 | 
						|
static void register_audio_dbus(void)
 | 
						|
{
 | 
						|
    audio_driver_register(&dbus_audio_driver);
 | 
						|
}
 | 
						|
type_init(register_audio_dbus);
 | 
						|
 | 
						|
module_dep("ui-dbus")
 |