diff --git a/audio/pwaudio.c b/audio/pwaudio.c index 1d108bdebb..b6a38738ee 100644 --- a/audio/pwaudio.c +++ b/audio/pwaudio.c @@ -1,5 +1,5 @@ /* - * QEMU Pipewire audio driver + * QEMU PipeWire audio driver * * Copyright (c) 2023 Red Hat Inc. * @@ -66,6 +66,9 @@ typedef struct PWVoiceIn { PWVoice v; } PWVoiceIn; +#define PW_VOICE_IN(v) ((PWVoiceIn *)v) +#define PW_VOICE_OUT(v) ((PWVoiceOut *)v) + static void stream_destroy(void *data) { @@ -197,16 +200,6 @@ on_stream_state_changed(void *data, enum pw_stream_state old, trace_pw_state_changed(pw_stream_get_node_id(v->stream), pw_stream_state_as_string(state)); - - switch (state) { - case PW_STREAM_STATE_ERROR: - case PW_STREAM_STATE_UNCONNECTED: - break; - case PW_STREAM_STATE_PAUSED: - case PW_STREAM_STATE_CONNECTING: - case PW_STREAM_STATE_STREAMING: - break; - } } static const struct pw_stream_events capture_stream_events = { @@ -424,8 +417,8 @@ pw_to_audfmt(enum spa_audio_format fmt, int *endianness, } static int -create_stream(pwaudio *c, PWVoice *v, const char *stream_name, - const char *name, enum spa_direction dir) +qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name, + const char *name, enum spa_direction dir) { int res; uint32_t n_params; @@ -436,6 +429,10 @@ create_stream(pwaudio *c, PWVoice *v, const char *stream_name, struct pw_properties *props; props = pw_properties_new(NULL, NULL); + if (!props) { + error_report("Failed to create PW properties: %s", g_strerror(errno)); + return -1; + } /* 75% of the timer period for faster updates */ buf_samples = (uint64_t)v->g->dev->timer_period * v->info.rate @@ -448,8 +445,8 @@ create_stream(pwaudio *c, PWVoice *v, const char *stream_name, pw_properties_set(props, PW_KEY_TARGET_OBJECT, name); } v->stream = pw_stream_new(c->core, stream_name, props); - if (v->stream == NULL) { + error_report("Failed to create PW stream: %s", g_strerror(errno)); return -1; } @@ -477,6 +474,7 @@ create_stream(pwaudio *c, PWVoice *v, const char *stream_name, PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params); if (res < 0) { + error_report("Failed to connect PW stream: %s", g_strerror(errno)); pw_stream_destroy(v->stream); return -1; } @@ -484,71 +482,37 @@ create_stream(pwaudio *c, PWVoice *v, const char *stream_name, return 0; } -static int -qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name, - const char *name, enum spa_direction dir) +static void +qpw_set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) { - int r; - - switch (v->info.channels) { + memcpy(position, (uint32_t[SPA_AUDIO_MAX_CHANNELS]) { SPA_AUDIO_CHANNEL_UNKNOWN, }, + sizeof(uint32_t) * SPA_AUDIO_MAX_CHANNELS); + /* + * TODO: This currently expects the only frontend supporting more than 2 + * channels is the usb-audio. We will need some means to set channel + * order when a new frontend gains multi-channel support. + */ + switch (channels) { case 8: - v->info.position[0] = SPA_AUDIO_CHANNEL_FL; - v->info.position[1] = SPA_AUDIO_CHANNEL_FR; - v->info.position[2] = SPA_AUDIO_CHANNEL_FC; - v->info.position[3] = SPA_AUDIO_CHANNEL_LFE; - v->info.position[4] = SPA_AUDIO_CHANNEL_RL; - v->info.position[5] = SPA_AUDIO_CHANNEL_RR; - v->info.position[6] = SPA_AUDIO_CHANNEL_SL; - v->info.position[7] = SPA_AUDIO_CHANNEL_SR; - break; + position[6] = SPA_AUDIO_CHANNEL_SL; + position[7] = SPA_AUDIO_CHANNEL_SR; + /* fallthrough */ case 6: - v->info.position[0] = SPA_AUDIO_CHANNEL_FL; - v->info.position[1] = SPA_AUDIO_CHANNEL_FR; - v->info.position[2] = SPA_AUDIO_CHANNEL_FC; - v->info.position[3] = SPA_AUDIO_CHANNEL_LFE; - v->info.position[4] = SPA_AUDIO_CHANNEL_RL; - v->info.position[5] = SPA_AUDIO_CHANNEL_RR; - break; - case 5: - v->info.position[0] = SPA_AUDIO_CHANNEL_FL; - v->info.position[1] = SPA_AUDIO_CHANNEL_FR; - v->info.position[2] = SPA_AUDIO_CHANNEL_FC; - v->info.position[3] = SPA_AUDIO_CHANNEL_LFE; - v->info.position[4] = SPA_AUDIO_CHANNEL_RC; - break; - case 4: - v->info.position[0] = SPA_AUDIO_CHANNEL_FL; - v->info.position[1] = SPA_AUDIO_CHANNEL_FR; - v->info.position[2] = SPA_AUDIO_CHANNEL_FC; - v->info.position[3] = SPA_AUDIO_CHANNEL_RC; - break; - case 3: - v->info.position[0] = SPA_AUDIO_CHANNEL_FL; - v->info.position[1] = SPA_AUDIO_CHANNEL_FR; - v->info.position[2] = SPA_AUDIO_CHANNEL_LFE; - break; + position[2] = SPA_AUDIO_CHANNEL_FC; + position[3] = SPA_AUDIO_CHANNEL_LFE; + position[4] = SPA_AUDIO_CHANNEL_RL; + position[5] = SPA_AUDIO_CHANNEL_RR; + /* fallthrough */ case 2: - v->info.position[0] = SPA_AUDIO_CHANNEL_FL; - v->info.position[1] = SPA_AUDIO_CHANNEL_FR; + position[0] = SPA_AUDIO_CHANNEL_FL; + position[1] = SPA_AUDIO_CHANNEL_FR; break; case 1: - v->info.position[0] = SPA_AUDIO_CHANNEL_MONO; + position[0] = SPA_AUDIO_CHANNEL_MONO; break; default: - for (size_t i = 0; i < v->info.channels; i++) { - v->info.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; - } - break; + dolog("Internal error: unsupported channel count %d\n", channels); } - - /* create a new unconnected pwstream */ - r = create_stream(c, v, stream_name, name, dir); - if (r < 0) { - AUD_log(AUDIO_CAP, "Failed to create stream."); - return -1; - } - - return r; } static int @@ -566,6 +530,7 @@ qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) v->info.format = audfmt_to_pw(as->fmt, as->endianness); v->info.channels = as->nchannels; + qpw_set_position(as->nchannels, v->info.position); v->info.rate = as->freq; obt_as.fmt = @@ -579,7 +544,6 @@ qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, ppdo->name, SPA_DIRECTION_OUTPUT); if (r < 0) { - error_report("qpw_stream_new for playback failed"); pw_thread_loop_unlock(c->thread_loop); return -1; } @@ -613,6 +577,7 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) v->info.format = audfmt_to_pw(as->fmt, as->endianness); v->info.channels = as->nchannels; + qpw_set_position(as->nchannels, v->info.position); v->info.rate = as->freq; obt_as.fmt = @@ -623,7 +588,6 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, ppdo->name, SPA_DIRECTION_INPUT); if (r < 0) { - error_report("qpw_stream_new for recording failed"); pw_thread_loop_unlock(c->thread_loop); return -1; } @@ -639,106 +603,86 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) return 0; } +static void +qpw_voice_fini(PWVoice *v) +{ + pwaudio *c = v->g; + + if (!v->stream) { + return; + } + pw_thread_loop_lock(c->thread_loop); + pw_stream_destroy(v->stream); + v->stream = NULL; + pw_thread_loop_unlock(c->thread_loop); +} + static void qpw_fini_out(HWVoiceOut *hw) { - PWVoiceOut *pw = (PWVoiceOut *) hw; - PWVoice *v = &pw->v; - - if (v->stream) { - pwaudio *c = v->g; - pw_thread_loop_lock(c->thread_loop); - pw_stream_destroy(v->stream); - v->stream = NULL; - pw_thread_loop_unlock(c->thread_loop); - } + qpw_voice_fini(&PW_VOICE_OUT(hw)->v); } static void qpw_fini_in(HWVoiceIn *hw) { - PWVoiceIn *pw = (PWVoiceIn *) hw; - PWVoice *v = &pw->v; - - if (v->stream) { - pwaudio *c = v->g; - pw_thread_loop_lock(c->thread_loop); - pw_stream_destroy(v->stream); - v->stream = NULL; - pw_thread_loop_unlock(c->thread_loop); - } + qpw_voice_fini(&PW_VOICE_IN(hw)->v); } static void -qpw_enable_out(HWVoiceOut *hw, bool enable) +qpw_voice_set_enabled(PWVoice *v, bool enable) { - PWVoiceOut *po = (PWVoiceOut *) hw; - PWVoice *v = &po->v; pwaudio *c = v->g; pw_thread_loop_lock(c->thread_loop); pw_stream_set_active(v->stream, enable); pw_thread_loop_unlock(c->thread_loop); } +static void +qpw_enable_out(HWVoiceOut *hw, bool enable) +{ + qpw_voice_set_enabled(&PW_VOICE_OUT(hw)->v, enable); +} + static void qpw_enable_in(HWVoiceIn *hw, bool enable) { - PWVoiceIn *pi = (PWVoiceIn *) hw; - PWVoice *v = &pi->v; + qpw_voice_set_enabled(&PW_VOICE_IN(hw)->v, enable); +} + +static void +qpw_voice_set_volume(PWVoice *v, Volume *vol) +{ pwaudio *c = v->g; + int i, ret; + pw_thread_loop_lock(c->thread_loop); - pw_stream_set_active(v->stream, enable); + v->volume.channels = vol->channels; + + for (i = 0; i < vol->channels; ++i) { + v->volume.values[i] = (float)vol->vol[i] / 255; + } + + ret = pw_stream_set_control(v->stream, + SPA_PROP_channelVolumes, v->volume.channels, v->volume.values, 0); + trace_pw_vol(ret == 0 ? "success" : "failed"); + + v->muted = vol->mute; + float val = v->muted ? 1.f : 0.f; + ret = pw_stream_set_control(v->stream, SPA_PROP_mute, 1, &val, 0); pw_thread_loop_unlock(c->thread_loop); } static void qpw_volume_out(HWVoiceOut *hw, Volume *vol) { - PWVoiceOut *pw = (PWVoiceOut *) hw; - PWVoice *v = &pw->v; - pwaudio *c = v->g; - int i, ret; - - pw_thread_loop_lock(c->thread_loop); - v->volume.channels = vol->channels; - - for (i = 0; i < vol->channels; ++i) { - v->volume.values[i] = (float)vol->vol[i] / 255; - } - - ret = pw_stream_set_control(v->stream, - SPA_PROP_channelVolumes, v->volume.channels, v->volume.values, 0); - trace_pw_vol(ret == 0 ? "success" : "failed"); - - v->muted = vol->mute; - float val = v->muted ? 1.f : 0.f; - ret = pw_stream_set_control(v->stream, SPA_PROP_mute, 1, &val, 0); - pw_thread_loop_unlock(c->thread_loop); + qpw_voice_set_volume(&PW_VOICE_OUT(hw)->v, vol); } static void qpw_volume_in(HWVoiceIn *hw, Volume *vol) { - PWVoiceIn *pw = (PWVoiceIn *) hw; - PWVoice *v = &pw->v; - pwaudio *c = v->g; - int i, ret; - - pw_thread_loop_lock(c->thread_loop); - v->volume.channels = vol->channels; - - for (i = 0; i < vol->channels; ++i) { - v->volume.values[i] = (float)vol->vol[i] / 255; - } - - ret = pw_stream_set_control(v->stream, - SPA_PROP_channelVolumes, v->volume.channels, v->volume.values, 0); - trace_pw_vol(ret == 0 ? "success" : "failed"); - - v->muted = vol->mute; - float val = v->muted ? 1.f : 0.f; - ret = pw_stream_set_control(v->stream, SPA_PROP_mute, 1, &val, 0); - pw_thread_loop_unlock(c->thread_loop); + qpw_voice_set_volume(&PW_VOICE_IN(hw)->v, vol); } static int wait_resync(pwaudio *pw) @@ -760,6 +704,7 @@ static int wait_resync(pwaudio *pw) } return 0; } + static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { @@ -794,27 +739,28 @@ static void * qpw_audio_init(Audiodev *dev) { g_autofree pwaudio *pw = g_new0(pwaudio, 1); + + assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE); + trace_pw_audio_init(); + pw_init(NULL, NULL); - trace_pw_audio_init(); - assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE); - pw->dev = dev; - pw->thread_loop = pw_thread_loop_new("Pipewire thread loop", NULL); + pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL); if (pw->thread_loop == NULL) { - error_report("Could not create Pipewire loop"); + error_report("Could not create PipeWire loop: %s", g_strerror(errno)); goto fail; } pw->context = pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), NULL, 0); if (pw->context == NULL) { - error_report("Could not create Pipewire context"); + error_report("Could not create PipeWire context: %s", g_strerror(errno)); goto fail; } if (pw_thread_loop_start(pw->thread_loop) < 0) { - error_report("Could not start Pipewire loop"); + error_report("Could not start PipeWire loop: %s", g_strerror(errno)); goto fail; } @@ -844,12 +790,8 @@ fail: if (pw->thread_loop) { pw_thread_loop_stop(pw->thread_loop); } - if (pw->context) { - g_clear_pointer(&pw->context, pw_context_destroy); - } - if (pw->thread_loop) { - g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy); - } + g_clear_pointer(&pw->context, pw_context_destroy); + g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy); return NULL; } diff --git a/audio/trace-events b/audio/trace-events index 85dbb506b2..ab04f020ce 100644 --- a/audio/trace-events +++ b/audio/trace-events @@ -24,7 +24,7 @@ pw_read(int32_t avail, uint32_t index, size_t len) "avail=%d index=%u len=%zu" pw_write(int32_t filled, int32_t avail, uint32_t index, size_t len) "filled=%d avail=%d index=%u len=%zu" pw_vol(const char *ret) "set volume: %s" pw_period(uint64_t quantum, uint32_t rate) "period =%" PRIu64 "/%u" -pw_audio_init(void) "Initialize Pipewire context" +pw_audio_init(void) "Initialize PipeWire context" # audio.c audio_timer_start(int interval) "interval %d ms" diff --git a/hw/display/virtio-gpu-udmabuf.c b/hw/display/virtio-gpu-udmabuf.c index ef1a740de5..d51184d658 100644 --- a/hw/display/virtio-gpu-udmabuf.c +++ b/hw/display/virtio-gpu-udmabuf.c @@ -181,13 +181,13 @@ static VGPUDMABuf } dmabuf = g_new0(VGPUDMABuf, 1); - dmabuf->buf.width = fb->width; - dmabuf->buf.height = fb->height; + dmabuf->buf.width = r->width; + dmabuf->buf.height = r->height; dmabuf->buf.stride = fb->stride; dmabuf->buf.x = r->x; dmabuf->buf.y = r->y; - dmabuf->buf.scanout_width = r->width; - dmabuf->buf.scanout_height = r->height; + dmabuf->buf.backing_width = fb->width; + dmabuf->buf.backing_height = fb->height; dmabuf->buf.fourcc = qemu_pixman_to_drm_format(fb->format); dmabuf->buf.fd = res->dmabuf_fd; dmabuf->buf.allow_fences = true; @@ -218,8 +218,8 @@ int virtio_gpu_update_dmabuf(VirtIOGPU *g, g->dmabuf.primary[scanout_id] = new_primary; qemu_console_resize(scanout->con, - new_primary->buf.scanout_width, - new_primary->buf.scanout_height); + new_primary->buf.width, + new_primary->buf.height); dpy_gl_scanout_dmabuf(scanout->con, &new_primary->buf); if (old_primary) { diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index befa7d6d78..e8603d78ca 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -303,10 +303,11 @@ static void virtio_gpu_resource_create_2d(VirtIOGPU *g, goto end; } #endif - res->image = pixman_image_create_bits(pformat, - c2d.width, - c2d.height, - bits, res->hostmem / c2d.height); + res->image = pixman_image_create_bits( + pformat, + c2d.width, + c2d.height, + bits, c2d.height ? res->hostmem / c2d.height : 0); #ifdef WIN32 if (res->image) { pixman_image_set_destroy_function(res->image, win32_pixman_image_destroy, res->handle); @@ -1272,9 +1273,10 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size, return -EINVAL; } #endif - res->image = pixman_image_create_bits(pformat, - res->width, res->height, - bits, res->hostmem / res->height); + res->image = pixman_image_create_bits( + pformat, + res->width, res->height, + bits, res->height ? res->hostmem / res->height : 0); if (!res->image) { g_free(res); return -EINVAL; @@ -1395,6 +1397,7 @@ void virtio_gpu_reset(VirtIODevice *vdev) VirtIOGPU *g = VIRTIO_GPU(vdev); struct virtio_gpu_simple_resource *res, *tmp; struct virtio_gpu_ctrl_command *cmd; + int i = 0; QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) { virtio_gpu_resource_destroy(g, res); @@ -1413,6 +1416,10 @@ void virtio_gpu_reset(VirtIODevice *vdev) g_free(cmd); } + for (i = 0; i < g->parent_obj.conf.max_outputs; i++) { + dpy_gfx_replace_surface(g->parent_obj.scanout[i].con, NULL); + } + virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev)); } diff --git a/include/ui/console.h b/include/ui/console.h index f27b2aad4f..3e8b22d6c6 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -201,8 +201,8 @@ typedef struct QemuDmaBuf { uint32_t texture; uint32_t x; uint32_t y; - uint32_t scanout_width; - uint32_t scanout_height; + uint32_t backing_width; + uint32_t backing_height; bool y0_top; void *sync; int fence_fd; diff --git a/meson.build b/meson.build index 5fcdb37a71..98e68ef0b1 100644 --- a/meson.build +++ b/meson.build @@ -4251,7 +4251,7 @@ if targetos == 'linux' summary_info += {'ALSA support': alsa} summary_info += {'PulseAudio support': pulse} endif -summary_info += {'Pipewire support': pipewire} +summary_info += {'PipeWire support': pipewire} summary_info += {'JACK support': jack} summary(summary_info, bool_yn: true, section: 'Audio backends') diff --git a/meson_options.txt b/meson_options.txt index bbb5c7e886..aaea5ddd77 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -267,7 +267,7 @@ option('oss', type: 'feature', value: 'auto', option('pa', type: 'feature', value: 'auto', description: 'PulseAudio sound support') option('pipewire', type: 'feature', value: 'auto', - description: 'Pipewire sound support') + description: 'PipeWire sound support') option('sndio', type: 'feature', value: 'auto', description: 'sndio sound support') diff --git a/qapi/audio.json b/qapi/audio.json index 534f10d8b1..519697c0cd 100644 --- a/qapi/audio.json +++ b/qapi/audio.json @@ -328,17 +328,17 @@ ## # @AudiodevPipewirePerDirectionOptions: # -# Options of the Pipewire backend that are used for both playback and +# Options of the PipeWire backend that are used for both playback and # recording. # # @name: name of the sink/source to use # -# @stream-name: name of the Pipewire stream created by qemu. Can be -# used to identify the stream in Pipewire when you create multiple -# Pipewire devices or run multiple qemu instances (default: +# @stream-name: name of the PipeWire stream created by qemu. Can be +# used to identify the stream in PipeWire when you create multiple +# PipeWire devices or run multiple qemu instances (default: # audiodev's id) # -# @latency: latency you want Pipewire to achieve in microseconds +# @latency: latency you want PipeWire to achieve in microseconds # (default 46000) # # Since: 8.1 @@ -353,7 +353,7 @@ ## # @AudiodevPipewireOptions: # -# Options of the Pipewire audio backend. +# Options of the PipeWire audio backend. # # @in: options of the capture stream # diff --git a/qemu-options.hx b/qemu-options.hx index f8f384e551..29b98c3d4c 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -963,10 +963,10 @@ SRST to honor this value but actual latencies may be lower or higher. ``-audiodev pipewire,id=id[,prop[=value][,...]]`` - Creates a backend using Pipewire. This backend is available on + Creates a backend using PipeWire. This backend is available on most systems. - Pipewire specific options are: + PipeWire specific options are: ``in|out.latency=usecs`` Desired latency in microseconds. diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 7dd5709ef4..9da3fe299b 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -145,7 +145,7 @@ meson_options_help() { printf "%s\n" ' oss OSS sound support' printf "%s\n" ' pa PulseAudio sound support' printf "%s\n" ' parallels parallels image format support' - printf "%s\n" ' pipewire Pipewire sound support' + printf "%s\n" ' pipewire PipeWire sound support' printf "%s\n" ' png PNG support with libpng' printf "%s\n" ' pvrdma Enable PVRDMA support' printf "%s\n" ' qcow1 qcow1 image format support' diff --git a/tests/docker/dockerfiles/alpine.docker b/tests/docker/dockerfiles/alpine.docker index 43370f7b36..fa455f1474 100644 --- a/tests/docker/dockerfiles/alpine.docker +++ b/tests/docker/dockerfiles/alpine.docker @@ -77,6 +77,7 @@ RUN apk update && \ numactl-dev \ openssh-client \ pcre-dev \ + pipewire-dev \ pixman-dev \ pkgconf \ pulseaudio-dev \ diff --git a/tests/docker/dockerfiles/centos8.docker b/tests/docker/dockerfiles/centos8.docker index 78f454b782..da7dc818fb 100644 --- a/tests/docker/dockerfiles/centos8.docker +++ b/tests/docker/dockerfiles/centos8.docker @@ -90,6 +90,7 @@ RUN dnf distro-sync -y && \ openssh-clients \ pam-devel \ pcre-static \ + pipewire-devel \ pixman-devel \ pkgconfig \ pulseaudio-libs-devel \ diff --git a/tests/docker/dockerfiles/debian-amd64-cross.docker b/tests/docker/dockerfiles/debian-amd64-cross.docker index 016c2321f1..b7bdc01243 100644 --- a/tests/docker/dockerfiles/debian-amd64-cross.docker +++ b/tests/docker/dockerfiles/debian-amd64-cross.docker @@ -116,6 +116,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:amd64 \ libnuma-dev:amd64 \ libpam0g-dev:amd64 \ + libpipewire-0.3-dev:amd64 \ libpixman-1-dev:amd64 \ libpmem-dev:amd64 \ libpng-dev:amd64 \ diff --git a/tests/docker/dockerfiles/debian-amd64.docker b/tests/docker/dockerfiles/debian-amd64.docker index e39871c7bb..6d2fa38e3e 100644 --- a/tests/docker/dockerfiles/debian-amd64.docker +++ b/tests/docker/dockerfiles/debian-amd64.docker @@ -69,6 +69,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnuma-dev \ libpam0g-dev \ libpcre2-dev \ + libpipewire-0.3-dev \ libpixman-1-dev \ libpmem-dev \ libpng-dev \ diff --git a/tests/docker/dockerfiles/debian-arm64-cross.docker b/tests/docker/dockerfiles/debian-arm64-cross.docker index 3c114efa11..68165c2f23 100644 --- a/tests/docker/dockerfiles/debian-arm64-cross.docker +++ b/tests/docker/dockerfiles/debian-arm64-cross.docker @@ -116,6 +116,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:arm64 \ libnuma-dev:arm64 \ libpam0g-dev:arm64 \ + libpipewire-0.3-dev:arm64 \ libpixman-1-dev:arm64 \ libpng-dev:arm64 \ libpulse-dev:arm64 \ diff --git a/tests/docker/dockerfiles/debian-armel-cross.docker b/tests/docker/dockerfiles/debian-armel-cross.docker index dfbd47db89..2fb65308c7 100644 --- a/tests/docker/dockerfiles/debian-armel-cross.docker +++ b/tests/docker/dockerfiles/debian-armel-cross.docker @@ -116,6 +116,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:armel \ libnuma-dev:armel \ libpam0g-dev:armel \ + libpipewire-0.3-dev:armel \ libpixman-1-dev:armel \ libpng-dev:armel \ libpulse-dev:armel \ diff --git a/tests/docker/dockerfiles/debian-armhf-cross.docker b/tests/docker/dockerfiles/debian-armhf-cross.docker index 4e0084e896..df77ccb57b 100644 --- a/tests/docker/dockerfiles/debian-armhf-cross.docker +++ b/tests/docker/dockerfiles/debian-armhf-cross.docker @@ -116,6 +116,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:armhf \ libnuma-dev:armhf \ libpam0g-dev:armhf \ + libpipewire-0.3-dev:armhf \ libpixman-1-dev:armhf \ libpng-dev:armhf \ libpulse-dev:armhf \ diff --git a/tests/docker/dockerfiles/debian-mips64el-cross.docker b/tests/docker/dockerfiles/debian-mips64el-cross.docker index 88adf333e9..63a3d7aa3b 100644 --- a/tests/docker/dockerfiles/debian-mips64el-cross.docker +++ b/tests/docker/dockerfiles/debian-mips64el-cross.docker @@ -115,6 +115,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:mips64el \ libnuma-dev:mips64el \ libpam0g-dev:mips64el \ + libpipewire-0.3-dev:mips64el \ libpixman-1-dev:mips64el \ libpng-dev:mips64el \ libpulse-dev:mips64el \ diff --git a/tests/docker/dockerfiles/debian-mipsel-cross.docker b/tests/docker/dockerfiles/debian-mipsel-cross.docker index 256e8b5dfe..ac87bbb095 100644 --- a/tests/docker/dockerfiles/debian-mipsel-cross.docker +++ b/tests/docker/dockerfiles/debian-mipsel-cross.docker @@ -115,6 +115,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:mipsel \ libnuma-dev:mipsel \ libpam0g-dev:mipsel \ + libpipewire-0.3-dev:mipsel \ libpixman-1-dev:mipsel \ libpng-dev:mipsel \ libpulse-dev:mipsel \ diff --git a/tests/docker/dockerfiles/debian-ppc64el-cross.docker b/tests/docker/dockerfiles/debian-ppc64el-cross.docker index 4d19cd2bd7..def11f1693 100644 --- a/tests/docker/dockerfiles/debian-ppc64el-cross.docker +++ b/tests/docker/dockerfiles/debian-ppc64el-cross.docker @@ -116,6 +116,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:ppc64el \ libnuma-dev:ppc64el \ libpam0g-dev:ppc64el \ + libpipewire-0.3-dev:ppc64el \ libpixman-1-dev:ppc64el \ libpng-dev:ppc64el \ libpulse-dev:ppc64el \ diff --git a/tests/docker/dockerfiles/debian-s390x-cross.docker b/tests/docker/dockerfiles/debian-s390x-cross.docker index 642bbde3d1..80028e1eea 100644 --- a/tests/docker/dockerfiles/debian-s390x-cross.docker +++ b/tests/docker/dockerfiles/debian-s390x-cross.docker @@ -116,6 +116,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnfs-dev:s390x \ libnuma-dev:s390x \ libpam0g-dev:s390x \ + libpipewire-0.3-dev:s390x \ libpixman-1-dev:s390x \ libpng-dev:s390x \ libpulse-dev:s390x \ diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker index 8a35a17617..c5b6c96943 100644 --- a/tests/docker/dockerfiles/fedora.docker +++ b/tests/docker/dockerfiles/fedora.docker @@ -98,6 +98,7 @@ exec "$@"\n' > /usr/bin/nosync && \ openssh-clients \ pam-devel \ pcre-static \ + pipewire-devel \ pixman-devel \ pkgconfig \ pulseaudio-libs-devel \ diff --git a/tests/docker/dockerfiles/opensuse-leap.docker b/tests/docker/dockerfiles/opensuse-leap.docker index 185abe57d8..37c83e5e4e 100644 --- a/tests/docker/dockerfiles/opensuse-leap.docker +++ b/tests/docker/dockerfiles/opensuse-leap.docker @@ -88,6 +88,7 @@ RUN zypper update -y && \ openssh \ pam-devel \ pcre-devel-static \ + pipewire-devel \ pkgconfig \ python39-base \ python39-pip \ diff --git a/tests/docker/dockerfiles/ubuntu2204.docker b/tests/docker/dockerfiles/ubuntu2204.docker index 1d442cdfe6..8f939870ae 100644 --- a/tests/docker/dockerfiles/ubuntu2204.docker +++ b/tests/docker/dockerfiles/ubuntu2204.docker @@ -69,6 +69,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libnuma-dev \ libpam0g-dev \ libpcre2-dev \ + libpipewire-0.3-dev \ libpixman-1-dev \ libpmem-dev \ libpng-dev \ diff --git a/tests/lcitool/libvirt-ci b/tests/lcitool/libvirt-ci index b0f44f929a..9bff3b763b 160000 --- a/tests/lcitool/libvirt-ci +++ b/tests/lcitool/libvirt-ci @@ -1 +1 @@ -Subproject commit b0f44f929a81c0a604fb7fbf8afc34d37ab0eae9 +Subproject commit 9bff3b763b5531a1490e238bfbf77306dc3a6dbb diff --git a/tests/lcitool/projects/qemu.yml b/tests/lcitool/projects/qemu.yml index 21fd3d2cf9..d452a891ee 100644 --- a/tests/lcitool/projects/qemu.yml +++ b/tests/lcitool/projects/qemu.yml @@ -85,6 +85,7 @@ packages: - pam - pcre-static - pixman + - pipewire - pkg-config - pulseaudio - python3 diff --git a/ui/console.c b/ui/console.c index c1544e0fb8..8da2170a7e 100644 --- a/ui/console.c +++ b/ui/console.c @@ -1898,6 +1898,7 @@ void dpy_gfx_replace_surface(QemuConsole *con, static const char placeholder_msg[] = "Display output is not active."; DisplayState *s = con->ds; DisplaySurface *old_surface = con->surface; + DisplaySurface *new_surface = surface; DisplayChangeListener *dcl; int width; int height; @@ -1911,19 +1912,19 @@ void dpy_gfx_replace_surface(QemuConsole *con, height = 480; } - surface = qemu_create_placeholder_surface(width, height, placeholder_msg); + new_surface = qemu_create_placeholder_surface(width, height, placeholder_msg); } - assert(old_surface != surface); + assert(old_surface != new_surface); con->scanout.kind = SCANOUT_SURFACE; - con->surface = surface; - dpy_gfx_create_texture(con, surface); + con->surface = new_surface; + dpy_gfx_create_texture(con, new_surface); QLIST_FOREACH(dcl, &s->listeners, next) { if (con != (dcl->con ? dcl->con : active_console)) { continue; } - displaychangelistener_gfx_switch(dcl, surface, FALSE); + displaychangelistener_gfx_switch(dcl, new_surface, surface ? FALSE : TRUE); } dpy_gfx_destroy_texture(con, old_surface); qemu_free_displaysurface(old_surface); diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c index 0240c39510..68ff343799 100644 --- a/ui/dbus-listener.c +++ b/ui/dbus-listener.c @@ -415,13 +415,13 @@ static void dbus_scanout_texture(DisplayChangeListener *dcl, backing_width, backing_height, x, y, w, h); #ifdef CONFIG_GBM QemuDmaBuf dmabuf = { - .width = backing_width, - .height = backing_height, + .width = w, + .height = h, .y0_top = backing_y_0_top, .x = x, .y = y, - .scanout_width = w, - .scanout_height = h, + .backing_width = backing_width, + .backing_height = backing_height, }; assert(tex_id); diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index 8f9fbf583e..3d19dbe382 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -148,8 +148,8 @@ void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip) if (src->dmabuf) { x1 = src->dmabuf->x; y1 = src->dmabuf->y; - w = src->dmabuf->scanout_width; - h = src->dmabuf->scanout_height; + w = src->dmabuf->width; + h = src->dmabuf->height; } w = (x1 + w) > src->width ? src->width - x1 : w; @@ -314,9 +314,9 @@ void egl_dmabuf_import_texture(QemuDmaBuf *dmabuf) } attrs[i++] = EGL_WIDTH; - attrs[i++] = dmabuf->width; + attrs[i++] = dmabuf->backing_width; attrs[i++] = EGL_HEIGHT; - attrs[i++] = dmabuf->height; + attrs[i++] = dmabuf->backing_height; attrs[i++] = EGL_LINUX_DRM_FOURCC_EXT; attrs[i++] = dmabuf->fourcc; diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c index d59b8cd7d7..4c29ac10d0 100644 --- a/ui/gtk-egl.c +++ b/ui/gtk-egl.c @@ -32,6 +32,8 @@ static void gtk_egl_set_scanout_mode(VirtualConsole *vc, bool scanout) vc->gfx.scanout_mode = scanout; if (!vc->gfx.scanout_mode) { + eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, + vc->gfx.esurface, vc->gfx.ectx); egl_fb_destroy(&vc->gfx.guest_fb); if (vc->gfx.surface) { surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds); @@ -135,6 +137,8 @@ void gd_egl_update(DisplayChangeListener *dcl, vc->gfx.esurface, vc->gfx.ectx); surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h); vc->gfx.glupdates++; + eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT); } void gd_egl_refresh(DisplayChangeListener *dcl) @@ -144,6 +148,10 @@ void gd_egl_refresh(DisplayChangeListener *dcl) gd_update_monitor_refresh_rate( vc, vc->window ? vc->window : vc->gfx.drawing_area); + if (vc->gfx.guest_fb.dmabuf && vc->gfx.guest_fb.dmabuf->draw_submitted) { + return; + } + if (!vc->gfx.esurface) { gd_egl_init(vc); if (!vc->gfx.esurface) { @@ -238,7 +246,6 @@ void gd_egl_scanout_texture(DisplayChangeListener *dcl, eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, vc->gfx.esurface, vc->gfx.ectx); - gtk_egl_set_scanout_mode(vc, true); egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height, backing_id, false); } @@ -258,9 +265,10 @@ void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, } gd_egl_scanout_texture(dcl, dmabuf->texture, - dmabuf->y0_top, dmabuf->width, dmabuf->height, - dmabuf->x, dmabuf->y, dmabuf->scanout_width, - dmabuf->scanout_height, NULL); + dmabuf->y0_top, + dmabuf->backing_width, dmabuf->backing_height, + dmabuf->x, dmabuf->y, dmabuf->width, + dmabuf->height, NULL); if (dmabuf->allow_fences) { vc->gfx.guest_fb.dmabuf = dmabuf; @@ -280,7 +288,8 @@ void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl, if (!dmabuf->texture) { return; } - egl_fb_setup_for_tex(&vc->gfx.cursor_fb, dmabuf->width, dmabuf->height, + egl_fb_setup_for_tex(&vc->gfx.cursor_fb, + dmabuf->backing_width, dmabuf->backing_height, dmabuf->texture, false); } else { egl_fb_destroy(&vc->gfx.cursor_fb); @@ -347,6 +356,7 @@ void gd_egl_flush(DisplayChangeListener *dcl, if (vc->gfx.guest_fb.dmabuf && !vc->gfx.guest_fb.dmabuf->draw_submitted) { graphic_hw_gl_block(vc->gfx.dcl.con, true); vc->gfx.guest_fb.dmabuf->draw_submitted = true; + gtk_egl_set_scanout_mode(vc, true); gtk_widget_queue_draw_area(area, x, y, w, h); return; } diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c index 7367dfd793..1ce34a249e 100644 --- a/ui/gtk-gl-area.c +++ b/ui/gtk-gl-area.c @@ -26,6 +26,7 @@ static void gtk_gl_area_set_scanout_mode(VirtualConsole *vc, bool scanout) vc->gfx.scanout_mode = scanout; if (!vc->gfx.scanout_mode) { + gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); egl_fb_destroy(&vc->gfx.guest_fb); if (vc->gfx.surface) { surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds); @@ -115,6 +116,7 @@ void gd_gl_area_update(DisplayChangeListener *dcl, gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h); vc->gfx.glupdates++; + gdk_gl_context_clear_current(); } void gd_gl_area_refresh(DisplayChangeListener *dcl) @@ -123,6 +125,10 @@ void gd_gl_area_refresh(DisplayChangeListener *dcl) gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : vc->gfx.drawing_area); + if (vc->gfx.guest_fb.dmabuf && vc->gfx.guest_fb.dmabuf->draw_submitted) { + return; + } + if (!vc->gfx.gls) { if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { return; @@ -262,7 +268,6 @@ void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, return; } - gtk_gl_area_set_scanout_mode(vc, true); egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height, backing_id, false); } @@ -282,6 +287,7 @@ void gd_gl_area_scanout_flush(DisplayChangeListener *dcl, if (vc->gfx.guest_fb.dmabuf && !vc->gfx.guest_fb.dmabuf->draw_submitted) { graphic_hw_gl_block(vc->gfx.dcl.con, true); vc->gfx.guest_fb.dmabuf->draw_submitted = true; + gtk_gl_area_set_scanout_mode(vc, true); } gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); } @@ -299,9 +305,10 @@ void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, } gd_gl_area_scanout_texture(dcl, dmabuf->texture, - dmabuf->y0_top, dmabuf->width, dmabuf->height, - dmabuf->x, dmabuf->y, dmabuf->scanout_width, - dmabuf->scanout_height, NULL); + dmabuf->y0_top, + dmabuf->backing_width, dmabuf->backing_height, + dmabuf->x, dmabuf->y, dmabuf->width, + dmabuf->height, NULL); if (dmabuf->allow_fences) { vc->gfx.guest_fb.dmabuf = dmabuf; diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c index 8aeadfaa21..c759be3438 100644 --- a/ui/vnc-clipboard.c +++ b/ui/vnc-clipboard.c @@ -50,8 +50,11 @@ static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size) ret = inflate(&stream, Z_FINISH); switch (ret) { case Z_OK: - case Z_STREAM_END: break; + case Z_STREAM_END: + *size = stream.total_out; + inflateEnd(&stream); + return out; case Z_BUF_ERROR: out_len <<= 1; if (out_len > (1 << 20)) { @@ -66,11 +69,6 @@ static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size) } } - *size = stream.total_out; - inflateEnd(&stream); - - return out; - err_end: inflateEnd(&stream); err: