 2e35439f25
			
		
	
	
		2e35439f25
		
	
	
	
	
		
			
			Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-Id: <20240717171541.201525-2-marcandre.lureau@redhat.com>
		
			
				
	
	
		
			692 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			692 lines
		
	
	
		
			21 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"
 | |
| 
 | |
| #ifdef G_OS_UNIX
 | |
| #include <gio/gunixfdlist.h>
 | |
| #endif
 | |
| 
 | |
| #include "ui/dbus.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;
 | |
|     bool p2p;
 | |
|     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(&vo->rate, &hw->info, *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(vo->buf_pos, vo->buf_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(&vo->rate, &hw->info, 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, Error **errp)
 | |
| {
 | |
|     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,
 | |
| #ifdef G_OS_UNIX
 | |
|                              GUnixFDList *fd_list,
 | |
| #endif
 | |
|                              GVariant *arg_listener,
 | |
|                              bool out)
 | |
| {
 | |
|     DBusAudio *da = s->drv_opaque;
 | |
|     const char *sender =
 | |
|         da->p2p ? "p2p" : 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;
 | |
|     }
 | |
| 
 | |
| #ifdef G_OS_WIN32
 | |
|     if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) {
 | |
|         return DBUS_METHOD_INVOCATION_HANDLED;
 | |
|     }
 | |
| #else
 | |
|     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;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     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);
 | |
| #ifdef G_OS_WIN32
 | |
|         closesocket(fd);
 | |
| #else
 | |
|         close(fd);
 | |
| #endif
 | |
|         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
 | |
| #ifdef G_OS_UNIX
 | |
|             , NULL
 | |
| #endif
 | |
|             );
 | |
|     } else {
 | |
|         qemu_dbus_display1_audio_complete_register_in_listener(
 | |
|             da->iface, invocation
 | |
| #ifdef G_OS_UNIX
 | |
|             , NULL
 | |
| #endif
 | |
|             );
 | |
|     }
 | |
| 
 | |
|     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,
 | |
| #ifdef G_OS_UNIX
 | |
|                                  GUnixFDList *fd_list,
 | |
| #endif
 | |
|                                  GVariant *arg_listener)
 | |
| {
 | |
|     return dbus_audio_register_listener(s, invocation,
 | |
| #ifdef G_OS_UNIX
 | |
|                                         fd_list,
 | |
| #endif
 | |
|                                         arg_listener, true);
 | |
| 
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| dbus_audio_register_in_listener(AudioState *s,
 | |
|                                 GDBusMethodInvocation *invocation,
 | |
| #ifdef G_OS_UNIX
 | |
|                                 GUnixFDList *fd_list,
 | |
| #endif
 | |
|                                 GVariant *arg_listener)
 | |
| {
 | |
|     return dbus_audio_register_listener(s, invocation,
 | |
| #ifdef G_OS_UNIX
 | |
|                                         fd_list,
 | |
| #endif
 | |
|                                         arg_listener, false);
 | |
| }
 | |
| 
 | |
| static void
 | |
| dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server, bool p2p)
 | |
| {
 | |
|     DBusAudio *da = s->drv_opaque;
 | |
| 
 | |
|     g_assert(da);
 | |
|     g_assert(!da->server);
 | |
| 
 | |
|     da->server = g_object_ref(server);
 | |
|     da->p2p = p2p;
 | |
| 
 | |
|     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,
 | |
|     .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")
 |