Add D-Bus display backend
-----BEGIN PGP SIGNATURE----- iQJQBAABCAA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmHBes4cHG1hcmNhbmRy ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5VLnD/41Z7+j7POjutV+RoA2 bVCyqn7O5qhzr1vZIZ5f4cgSk3WSuUnwcZbezNqX6jsA4AP0Zyh0kI4GEC9v/2zs FH2oJJiTePaEchgXFDoGCJ9W61mrt9ZqTlA7m6XBvnd5JFZsOaOTo06vgLTopBq0 pBB5bbFNjuSIpQr7cSx8knlzn9cJcNzm5sgHoxXyK3O+yINfKi2nr8+OGHLcwbfv X+ljjYDgNLz4g6SyvTtZKREJ7RE/9E29KVFsNboYQpCmV4Tf4I8iIv3NeiXh2x6B B+rIEfpy9kaCIMkQYClKdnldk9/RMoMFmPs990ORgRjjRS7zL+m86cHHNAWHuBF2 j3rgJNvQw+HwMsw2YeLxZOHLK4jzoU/y/9YncL+PUw4evhAbduzW9p9Pb7l8jI3A q9M++Dw7xYVxjGx81eABKwBn1TtJrG8O7KIQpkKrZX9fXzxLXp9I0r3nKxHvp8Wy W5FKHIUkxeeUO5aaIUl/7QKEatQK7c6eHkMcNmw+eTrs/jIud20MRiHWbiA0EGQB VaXatcXG+P+tri3RYVN01jjF00iiZf0DsZY3Rd/5FllCefQ73IhCOQSZpETWcqmj W6eoQLwz6gzAynOB2JUOlQxshzDEEXL6W4skW+mvLAa5v2Pi5vTlT+8fbZJnxTL3 NGUoq2NIEUgtAYi24YpX4NUdrQ== =tJo9 -----END PGP SIGNATURE----- Merge tag 'dbus-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging Add D-Bus display backend # gpg: Signature made Mon 20 Dec 2021 10:57:18 PM PST # gpg: using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5 # gpg: issuer "marcandre.lureau@redhat.com" # gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [unknown] # gpg: aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: 87A9 BD93 3F87 C606 D276 F62D DAE8 E109 7596 9CE5 * tag 'dbus-pull-request' of https://gitlab.com/marcandre.lureau/qemu: (36 commits) MAINTAINERS: update D-Bus section ui/dbus: register D-Bus VC handler ui/dbus: add chardev backend & interface option: add g_auto for QemuOpts chardev: make socket derivable chardev: teach socket to accept no addresses ui/dbus: add clipboard interface audio: add "dbus" audio backend tests: start dbus-display-test tests/qtests: add qtest_qmp_add_client() ui/dbus: add p2p=on/off option ui: add a D-Bus display backend build-sys: set glib dependency version docs: add dbus-display documentation docs: move D-Bus VMState documentation to source XML backends: move dbus-vmstate1.xml to backends/ docs/sphinx: add sphinx modules to include D-Bus documentation scripts: teach modinfo to skip non-C sources console: save current scanout details ui: move qemu_spice_fill_device_address to ui/util.c ... Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
5316e12bb2
10
MAINTAINERS
10
MAINTAINERS
@ -2873,11 +2873,15 @@ D-Bus
|
||||
M: Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
S: Maintained
|
||||
F: backends/dbus-vmstate.c
|
||||
F: tests/dbus-vmstate*
|
||||
F: ui/dbus*
|
||||
F: audio/dbus*
|
||||
F: util/dbus.c
|
||||
F: include/ui/dbus*
|
||||
F: include/qemu/dbus.h
|
||||
F: docs/interop/dbus.rst
|
||||
F: docs/interop/dbus-vmstate.rst
|
||||
F: docs/interop/dbus*
|
||||
F: docs/sphinx/dbus*
|
||||
F: docs/sphinx/fakedbusdoc.py
|
||||
F: tests/qtest/dbus*
|
||||
|
||||
Seccomp
|
||||
M: Eduardo Otubo <otubo@redhat.com>
|
||||
|
@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)
|
||||
CASE(NONE, none, );
|
||||
CASE(ALSA, alsa, Alsa);
|
||||
CASE(COREAUDIO, coreaudio, Coreaudio);
|
||||
CASE(DBUS, dbus, );
|
||||
CASE(DSOUND, dsound, );
|
||||
CASE(JACK, jack, Jack);
|
||||
CASE(OSS, oss, Oss);
|
||||
|
@ -31,6 +31,10 @@
|
||||
#endif
|
||||
#include "mixeng.h"
|
||||
|
||||
#ifdef CONFIG_GIO
|
||||
#include <gio/gio.h>
|
||||
#endif
|
||||
|
||||
struct audio_pcm_ops;
|
||||
|
||||
struct audio_callback {
|
||||
@ -140,6 +144,9 @@ struct audio_driver {
|
||||
const char *descr;
|
||||
void *(*init) (Audiodev *);
|
||||
void (*fini) (void *);
|
||||
#ifdef CONFIG_GIO
|
||||
void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
|
||||
#endif
|
||||
struct audio_pcm_ops *pcm_ops;
|
||||
int can_be_default;
|
||||
int max_voices_out;
|
||||
|
@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
|
||||
case AUDIODEV_DRIVER_COREAUDIO:
|
||||
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
|
||||
dev->u.coreaudio.TYPE);
|
||||
case AUDIODEV_DRIVER_DBUS:
|
||||
return dev->u.dbus.TYPE;
|
||||
case AUDIODEV_DRIVER_DSOUND:
|
||||
return dev->u.dsound.TYPE;
|
||||
case AUDIODEV_DRIVER_JACK:
|
||||
|
654
audio/dbusaudio.c
Normal file
654
audio/dbusaudio.c
Normal file
@ -0,0 +1,654 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
#ifdef HOST_WORDS_BIGENDIAN
|
||||
#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")
|
@ -26,4 +26,10 @@ foreach m : [
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if dbus_display
|
||||
module_ss = ss.source_set()
|
||||
module_ss.add(when: gio, if_true: files('dbusaudio.c'))
|
||||
audio_modules += {'dbus': module_ss}
|
||||
endif
|
||||
|
||||
modules += {'audio': audio_modules}
|
||||
|
@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
|
||||
# ossaudio.c
|
||||
oss_version(int version) "OSS version = 0x%x"
|
||||
|
||||
# dbusaudio.c
|
||||
dbus_audio_register(const char *s, const char *dir) "sender = %s, dir = %s"
|
||||
dbus_audio_put_buffer_out(size_t len) "len = %zu"
|
||||
dbus_audio_read(size_t len) "len = %zu"
|
||||
|
||||
# audio.c
|
||||
audio_timer_start(int interval) "interval %d ms"
|
||||
audio_timer_stop(void) ""
|
||||
|
52
backends/dbus-vmstate1.xml
Normal file
52
backends/dbus-vmstate1.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||
<!--
|
||||
org.qemu.VMState1:
|
||||
|
||||
This interface must be implemented at the object path
|
||||
``/org/qemu/VMState1`` to support helper migration.
|
||||
-->
|
||||
<interface name="org.qemu.VMState1">
|
||||
|
||||
<!--
|
||||
Id:
|
||||
|
||||
A string that identifies the helper uniquely. (maximum 256 bytes
|
||||
including terminating NUL byte)
|
||||
|
||||
.. note::
|
||||
|
||||
The VMState helper ID namespace is its own namespace. In particular,
|
||||
it is not related to QEMU "id" used in -object/-device objects.
|
||||
-->
|
||||
<property name="Id" type="s" access="read"/>
|
||||
|
||||
<!--
|
||||
Load:
|
||||
@data: data to restore the state.
|
||||
|
||||
The method called on destination with the state to restore.
|
||||
|
||||
The helper may be initially started in a waiting state (with an
|
||||
``-incoming`` argument for example), and it may resume on success.
|
||||
|
||||
An error may be returned to the caller.
|
||||
-->
|
||||
<method name="Load">
|
||||
<arg type="ay" name="data" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Save:
|
||||
@data: state data to save for later resume.
|
||||
|
||||
The method called on the source to get the current state to be
|
||||
migrated. The helper should continue to run normally.
|
||||
|
||||
An error may be returned to the caller.
|
||||
-->
|
||||
<method name="Save">
|
||||
<arg type="ay" name="data" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
@ -25,9 +25,7 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "chardev/char.h"
|
||||
#include "io/channel-socket.h"
|
||||
#include "io/channel-tls.h"
|
||||
#include "io/channel-websock.h"
|
||||
#include "io/net-listener.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/option.h"
|
||||
@ -37,61 +35,7 @@
|
||||
#include "qemu/yank.h"
|
||||
|
||||
#include "chardev/char-io.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
/***********************************************************/
|
||||
/* TCP Net console */
|
||||
|
||||
#define TCP_MAX_FDS 16
|
||||
|
||||
typedef struct {
|
||||
char buf[21];
|
||||
size_t buflen;
|
||||
} TCPChardevTelnetInit;
|
||||
|
||||
typedef enum {
|
||||
TCP_CHARDEV_STATE_DISCONNECTED,
|
||||
TCP_CHARDEV_STATE_CONNECTING,
|
||||
TCP_CHARDEV_STATE_CONNECTED,
|
||||
} TCPChardevState;
|
||||
|
||||
struct SocketChardev {
|
||||
Chardev parent;
|
||||
QIOChannel *ioc; /* Client I/O channel */
|
||||
QIOChannelSocket *sioc; /* Client master channel */
|
||||
QIONetListener *listener;
|
||||
GSource *hup_source;
|
||||
QCryptoTLSCreds *tls_creds;
|
||||
char *tls_authz;
|
||||
TCPChardevState state;
|
||||
int max_size;
|
||||
int do_telnetopt;
|
||||
int do_nodelay;
|
||||
int *read_msgfds;
|
||||
size_t read_msgfds_num;
|
||||
int *write_msgfds;
|
||||
size_t write_msgfds_num;
|
||||
bool registered_yank;
|
||||
|
||||
SocketAddress *addr;
|
||||
bool is_listen;
|
||||
bool is_telnet;
|
||||
bool is_tn3270;
|
||||
GSource *telnet_source;
|
||||
TCPChardevTelnetInit *telnet_init;
|
||||
|
||||
bool is_websock;
|
||||
|
||||
GSource *reconnect_timer;
|
||||
int64_t reconnect_time;
|
||||
bool connect_err_reported;
|
||||
|
||||
QIOTask *connect_task;
|
||||
};
|
||||
typedef struct SocketChardev SocketChardev;
|
||||
|
||||
DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
|
||||
TYPE_CHARDEV_SOCKET)
|
||||
#include "chardev/char-socket.h"
|
||||
|
||||
static gboolean socket_reconnect_timeout(gpointer opaque);
|
||||
static void tcp_chr_telnet_init(Chardev *chr);
|
||||
@ -1248,6 +1192,10 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
|
||||
qio_net_listener_set_name(s->listener, name);
|
||||
g_free(name);
|
||||
|
||||
if (s->addr->type == SOCKET_ADDRESS_TYPE_FD && !*s->addr->u.fd.str) {
|
||||
goto skip_listen;
|
||||
}
|
||||
|
||||
if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
|
||||
object_unref(OBJECT(s->listener));
|
||||
s->listener = NULL;
|
||||
@ -1256,6 +1204,8 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
|
||||
|
||||
qapi_free_SocketAddress(s->addr);
|
||||
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
|
||||
|
||||
skip_listen:
|
||||
update_disconnected_filename(s);
|
||||
|
||||
if (is_waitconnect) {
|
||||
@ -1466,9 +1416,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
|
||||
SocketAddressLegacy *addr;
|
||||
ChardevSocket *sock;
|
||||
|
||||
if ((!!path + !!fd + !!host) != 1) {
|
||||
if ((!!path + !!fd + !!host) > 1) {
|
||||
error_setg(errp,
|
||||
"Exactly one of 'path', 'fd' or 'host' required");
|
||||
"None or one of 'path', 'fd' or 'host' option required.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1542,12 +1492,10 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
|
||||
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
|
||||
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
|
||||
};
|
||||
} else if (fd) {
|
||||
} else {
|
||||
addr->type = SOCKET_ADDRESS_TYPE_FD;
|
||||
addr->u.fd.data = g_new(String, 1);
|
||||
addr->u.fd.data->str = g_strdup(fd);
|
||||
} else {
|
||||
g_assert_not_reached();
|
||||
}
|
||||
sock->addr = addr;
|
||||
}
|
||||
|
1
configure
vendored
1
configure
vendored
@ -3694,6 +3694,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
|
||||
echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
|
||||
echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
|
||||
echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
|
||||
echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >> $config_host_mak
|
||||
echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
|
||||
echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
|
||||
echo "EXESUF=$EXESUF" >> $config_host_mak
|
||||
|
@ -73,6 +73,12 @@ needs_sphinx = '1.6'
|
||||
# ones.
|
||||
extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
|
||||
|
||||
if sphinx.version_info[:3] > (4, 0, 0):
|
||||
tags.add('sphinx4')
|
||||
extensions += ['dbusdoc']
|
||||
else:
|
||||
extensions += ['fakedbusdoc']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = [os.path.join(qemu_docdir, '_templates')]
|
||||
|
||||
@ -311,3 +317,5 @@ kerneldoc_bin = ['perl', os.path.join(qemu_docdir, '../scripts/kernel-doc')]
|
||||
kerneldoc_srctree = os.path.join(qemu_docdir, '..')
|
||||
hxtool_srctree = os.path.join(qemu_docdir, '..')
|
||||
qapidoc_srctree = os.path.join(qemu_docdir, '..')
|
||||
dbusdoc_srctree = os.path.join(qemu_docdir, '..')
|
||||
dbus_index_common_prefix = ["org.qemu."]
|
||||
|
31
docs/interop/dbus-display.rst
Normal file
31
docs/interop/dbus-display.rst
Normal file
@ -0,0 +1,31 @@
|
||||
D-Bus display
|
||||
=============
|
||||
|
||||
QEMU can export the VM display through D-Bus (when started with ``-display
|
||||
dbus``), to allow out-of-process UIs, remote protocol servers or other
|
||||
interactive display usages.
|
||||
|
||||
Various specialized D-Bus interfaces are available on different object paths
|
||||
under ``/org/qemu/Display1/``, depending on the VM configuration.
|
||||
|
||||
QEMU also implements the standard interfaces, such as
|
||||
`org.freedesktop.DBus.Introspectable
|
||||
<https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces>`_.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
|
||||
.. only:: sphinx4
|
||||
|
||||
.. dbus-doc:: ui/dbus-display1.xml
|
||||
|
||||
.. only:: not sphinx4
|
||||
|
||||
.. warning::
|
||||
Sphinx 4 is required to build D-Bus documentation.
|
||||
|
||||
This is the content of ``ui/dbus-display1.xml``:
|
||||
|
||||
.. literalinclude:: ../../ui/dbus-display1.xml
|
||||
:language: xml
|
@ -2,9 +2,6 @@
|
||||
D-Bus VMState
|
||||
=============
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The QEMU dbus-vmstate object's aim is to migrate helpers' data running
|
||||
on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
|
||||
some recommendations on D-Bus usage)
|
||||
@ -26,49 +23,16 @@ dbus-vmstate object can be configured with the expected list of
|
||||
helpers by setting its ``id-list`` property, with a comma-separated
|
||||
``Id`` list.
|
||||
|
||||
Interface
|
||||
=========
|
||||
.. only:: sphinx4
|
||||
|
||||
On object path ``/org/qemu/VMState1``, the following
|
||||
``org.qemu.VMState1`` interface should be implemented:
|
||||
.. dbus-doc:: backends/dbus-vmstate1.xml
|
||||
|
||||
.. code:: xml
|
||||
.. only:: not sphinx4
|
||||
|
||||
<interface name="org.qemu.VMState1">
|
||||
<property name="Id" type="s" access="read"/>
|
||||
<method name="Load">
|
||||
<arg type="ay" name="data" direction="in"/>
|
||||
</method>
|
||||
<method name="Save">
|
||||
<arg type="ay" name="data" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
.. warning::
|
||||
Sphinx 4 is required to build D-Bus documentation.
|
||||
|
||||
"Id" property
|
||||
-------------
|
||||
This is the content of ``backends/dbus-vmstate1.xml``:
|
||||
|
||||
A string that identifies the helper uniquely. (maximum 256 bytes
|
||||
including terminating NUL byte)
|
||||
|
||||
.. note::
|
||||
|
||||
The helper ID namespace is a separate namespace. In particular, it is not
|
||||
related to QEMU "id" used in -object/-device objects.
|
||||
|
||||
Load(in u8[] bytes) method
|
||||
--------------------------
|
||||
|
||||
The method called on destination with the state to restore.
|
||||
|
||||
The helper may be initially started in a waiting state (with
|
||||
an --incoming argument for example), and it may resume on success.
|
||||
|
||||
An error may be returned to the caller.
|
||||
|
||||
Save(out u8[] bytes) method
|
||||
---------------------------
|
||||
|
||||
The method called on the source to get the current state to be
|
||||
migrated. The helper should continue to run normally.
|
||||
|
||||
An error may be returned to the caller.
|
||||
.. literalinclude:: ../../backends/dbus-vmstate1.xml
|
||||
:language: xml
|
||||
|
@ -108,3 +108,5 @@ QEMU Interfaces
|
||||
===============
|
||||
|
||||
:doc:`dbus-vmstate`
|
||||
|
||||
:doc:`dbus-display`
|
||||
|
@ -12,6 +12,7 @@ are useful for making QEMU interoperate with other software.
|
||||
bitmaps
|
||||
dbus
|
||||
dbus-vmstate
|
||||
dbus-display
|
||||
live-block-operations
|
||||
pr-helper
|
||||
qemu-ga
|
||||
|
166
docs/sphinx/dbusdoc.py
Normal file
166
docs/sphinx/dbusdoc.py
Normal file
@ -0,0 +1,166 @@
|
||||
# D-Bus XML documentation extension
|
||||
#
|
||||
# Copyright (C) 2021, Red Hat Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
import sphinx
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Element, Node
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils.parsers.rst.states import RSTState
|
||||
from docutils.statemachine import StringList, ViewList
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docstrings import prepare_docstring
|
||||
from sphinx.util.docutils import SphinxDirective, switch_source_input
|
||||
from sphinx.util.nodes import nested_parse_with_titles
|
||||
|
||||
import dbusdomain
|
||||
from dbusparser import parse_dbus_xml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
|
||||
class DBusDoc:
|
||||
def __init__(self, sphinx_directive, dbusfile):
|
||||
self._cur_doc = None
|
||||
self._sphinx_directive = sphinx_directive
|
||||
self._dbusfile = dbusfile
|
||||
self._top_node = nodes.section()
|
||||
self.result = StringList()
|
||||
self.indent = ""
|
||||
|
||||
def add_line(self, line: str, *lineno: int) -> None:
|
||||
"""Append one line of generated reST to the output."""
|
||||
if line.strip(): # not a blank line
|
||||
self.result.append(self.indent + line, self._dbusfile, *lineno)
|
||||
else:
|
||||
self.result.append("", self._dbusfile, *lineno)
|
||||
|
||||
def add_method(self, method):
|
||||
self.add_line(f".. dbus:method:: {method.name}")
|
||||
self.add_line("")
|
||||
self.indent += " "
|
||||
for arg in method.in_args:
|
||||
self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
|
||||
for arg in method.out_args:
|
||||
self.add_line(f":ret {arg.signature} {arg.name}: {arg.doc_string}")
|
||||
self.add_line("")
|
||||
for line in prepare_docstring("\n" + method.doc_string):
|
||||
self.add_line(line)
|
||||
self.indent = self.indent[:-3]
|
||||
|
||||
def add_signal(self, signal):
|
||||
self.add_line(f".. dbus:signal:: {signal.name}")
|
||||
self.add_line("")
|
||||
self.indent += " "
|
||||
for arg in signal.args:
|
||||
self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
|
||||
self.add_line("")
|
||||
for line in prepare_docstring("\n" + signal.doc_string):
|
||||
self.add_line(line)
|
||||
self.indent = self.indent[:-3]
|
||||
|
||||
def add_property(self, prop):
|
||||
self.add_line(f".. dbus:property:: {prop.name}")
|
||||
self.indent += " "
|
||||
self.add_line(f":type: {prop.signature}")
|
||||
access = {"read": "readonly", "write": "writeonly", "readwrite": "readwrite"}[
|
||||
prop.access
|
||||
]
|
||||
self.add_line(f":{access}:")
|
||||
if prop.emits_changed_signal:
|
||||
self.add_line(f":emits-changed: yes")
|
||||
self.add_line("")
|
||||
for line in prepare_docstring("\n" + prop.doc_string):
|
||||
self.add_line(line)
|
||||
self.indent = self.indent[:-3]
|
||||
|
||||
def add_interface(self, iface):
|
||||
self.add_line(f".. dbus:interface:: {iface.name}")
|
||||
self.add_line("")
|
||||
self.indent += " "
|
||||
for line in prepare_docstring("\n" + iface.doc_string):
|
||||
self.add_line(line)
|
||||
for method in iface.methods:
|
||||
self.add_method(method)
|
||||
for sig in iface.signals:
|
||||
self.add_signal(sig)
|
||||
for prop in iface.properties:
|
||||
self.add_property(prop)
|
||||
self.indent = self.indent[:-3]
|
||||
|
||||
|
||||
def parse_generated_content(state: RSTState, content: StringList) -> List[Node]:
|
||||
"""Parse a generated content by Documenter."""
|
||||
with switch_source_input(state, content):
|
||||
node = nodes.paragraph()
|
||||
node.document = state.document
|
||||
state.nested_parse(content, 0, node)
|
||||
|
||||
return node.children
|
||||
|
||||
|
||||
class DBusDocDirective(SphinxDirective):
|
||||
"""Extract documentation from the specified D-Bus XML file"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
|
||||
def run(self):
|
||||
reporter = self.state.document.reporter
|
||||
|
||||
try:
|
||||
source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore
|
||||
except AttributeError:
|
||||
source, lineno = (None, None)
|
||||
|
||||
logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text)
|
||||
|
||||
env = self.state.document.settings.env
|
||||
dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
|
||||
with open(dbusfile, "rb") as f:
|
||||
xml_data = f.read()
|
||||
xml = parse_dbus_xml(xml_data)
|
||||
doc = DBusDoc(self, dbusfile)
|
||||
for iface in xml:
|
||||
doc.add_interface(iface)
|
||||
|
||||
result = parse_generated_content(self.state, doc.result)
|
||||
return result
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
"""Register dbus-doc directive with Sphinx"""
|
||||
app.add_config_value("dbusdoc_srctree", None, "env")
|
||||
app.add_directive("dbus-doc", DBusDocDirective)
|
||||
dbusdomain.setup(app)
|
||||
|
||||
return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)
|
406
docs/sphinx/dbusdomain.py
Normal file
406
docs/sphinx/dbusdomain.py
Normal file
@ -0,0 +1,406 @@
|
||||
# D-Bus sphinx domain extension
|
||||
#
|
||||
# Copyright (C) 2021, Red Hat Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Tuple,
|
||||
cast,
|
||||
)
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Element, Node
|
||||
from docutils.parsers.rst import directives
|
||||
from sphinx import addnodes
|
||||
from sphinx.addnodes import desc_signature, pending_xref
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.domains import Domain, Index, IndexEntry, ObjType
|
||||
from sphinx.locale import _
|
||||
from sphinx.roles import XRefRole
|
||||
from sphinx.util import nodes as node_utils
|
||||
from sphinx.util.docfields import Field, TypedField
|
||||
from sphinx.util.typing import OptionSpec
|
||||
|
||||
|
||||
class DBusDescription(ObjectDescription[str]):
|
||||
"""Base class for DBus objects"""
|
||||
|
||||
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
|
||||
option_spec.update(
|
||||
{
|
||||
"deprecated": directives.flag,
|
||||
}
|
||||
)
|
||||
|
||||
def get_index_text(self, modname: str, name: str) -> str:
|
||||
"""Return the text for the index entry of the object."""
|
||||
raise NotImplementedError("must be implemented in subclasses")
|
||||
|
||||
def add_target_and_index(
|
||||
self, name: str, sig: str, signode: desc_signature
|
||||
) -> None:
|
||||
ifacename = self.env.ref_context.get("dbus:interface")
|
||||
node_id = name
|
||||
if ifacename:
|
||||
node_id = f"{ifacename}.{node_id}"
|
||||
|
||||
signode["names"].append(name)
|
||||
signode["ids"].append(node_id)
|
||||
|
||||
if "noindexentry" not in self.options:
|
||||
indextext = self.get_index_text(ifacename, name)
|
||||
if indextext:
|
||||
self.indexnode["entries"].append(
|
||||
("single", indextext, node_id, "", None)
|
||||
)
|
||||
|
||||
domain = cast(DBusDomain, self.env.get_domain("dbus"))
|
||||
domain.note_object(name, self.objtype, node_id, location=signode)
|
||||
|
||||
|
||||
class DBusInterface(DBusDescription):
|
||||
"""
|
||||
Implementation of ``dbus:interface``.
|
||||
"""
|
||||
|
||||
def get_index_text(self, ifacename: str, name: str) -> str:
|
||||
return ifacename
|
||||
|
||||
def before_content(self) -> None:
|
||||
self.env.ref_context["dbus:interface"] = self.arguments[0]
|
||||
|
||||
def after_content(self) -> None:
|
||||
self.env.ref_context.pop("dbus:interface")
|
||||
|
||||
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
||||
signode += addnodes.desc_annotation("interface ", "interface ")
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
return sig
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
_, node = super().run()
|
||||
name = self.arguments[0]
|
||||
section = nodes.section(ids=[name + "-section"])
|
||||
section += nodes.title(name, "%s interface" % name)
|
||||
section += node
|
||||
return [self.indexnode, section]
|
||||
|
||||
|
||||
class DBusMember(DBusDescription):
|
||||
|
||||
signal = False
|
||||
|
||||
|
||||
class DBusMethod(DBusMember):
|
||||
"""
|
||||
Implementation of ``dbus:method``.
|
||||
"""
|
||||
|
||||
option_spec: OptionSpec = DBusMember.option_spec.copy()
|
||||
option_spec.update(
|
||||
{
|
||||
"noreply": directives.flag,
|
||||
}
|
||||
)
|
||||
|
||||
doc_field_types: List[Field] = [
|
||||
TypedField(
|
||||
"arg",
|
||||
label=_("Arguments"),
|
||||
names=("arg",),
|
||||
rolename="arg",
|
||||
typerolename=None,
|
||||
typenames=("argtype", "type"),
|
||||
),
|
||||
TypedField(
|
||||
"ret",
|
||||
label=_("Returns"),
|
||||
names=("ret",),
|
||||
rolename="ret",
|
||||
typerolename=None,
|
||||
typenames=("rettype", "type"),
|
||||
),
|
||||
]
|
||||
|
||||
def get_index_text(self, ifacename: str, name: str) -> str:
|
||||
return _("%s() (%s method)") % (name, ifacename)
|
||||
|
||||
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
||||
params = addnodes.desc_parameterlist()
|
||||
returns = addnodes.desc_parameterlist()
|
||||
|
||||
contentnode = addnodes.desc_content()
|
||||
self.state.nested_parse(self.content, self.content_offset, contentnode)
|
||||
for child in contentnode:
|
||||
if isinstance(child, nodes.field_list):
|
||||
for field in child:
|
||||
ty, sg, name = field[0].astext().split(None, 2)
|
||||
param = addnodes.desc_parameter()
|
||||
param += addnodes.desc_sig_keyword_type(sg, sg)
|
||||
param += addnodes.desc_sig_space()
|
||||
param += addnodes.desc_sig_name(name, name)
|
||||
if ty == "arg":
|
||||
params += param
|
||||
elif ty == "ret":
|
||||
returns += param
|
||||
|
||||
anno = "signal " if self.signal else "method "
|
||||
signode += addnodes.desc_annotation(anno, anno)
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
signode += params
|
||||
if not self.signal and "noreply" not in self.options:
|
||||
ret = addnodes.desc_returns()
|
||||
ret += returns
|
||||
signode += ret
|
||||
|
||||
return sig
|
||||
|
||||
|
||||
class DBusSignal(DBusMethod):
|
||||
"""
|
||||
Implementation of ``dbus:signal``.
|
||||
"""
|
||||
|
||||
doc_field_types: List[Field] = [
|
||||
TypedField(
|
||||
"arg",
|
||||
label=_("Arguments"),
|
||||
names=("arg",),
|
||||
rolename="arg",
|
||||
typerolename=None,
|
||||
typenames=("argtype", "type"),
|
||||
),
|
||||
]
|
||||
signal = True
|
||||
|
||||
def get_index_text(self, ifacename: str, name: str) -> str:
|
||||
return _("%s() (%s signal)") % (name, ifacename)
|
||||
|
||||
|
||||
class DBusProperty(DBusMember):
|
||||
"""
|
||||
Implementation of ``dbus:property``.
|
||||
"""
|
||||
|
||||
option_spec: OptionSpec = DBusMember.option_spec.copy()
|
||||
option_spec.update(
|
||||
{
|
||||
"type": directives.unchanged,
|
||||
"readonly": directives.flag,
|
||||
"writeonly": directives.flag,
|
||||
"readwrite": directives.flag,
|
||||
"emits-changed": directives.unchanged,
|
||||
}
|
||||
)
|
||||
|
||||
doc_field_types: List[Field] = []
|
||||
|
||||
def get_index_text(self, ifacename: str, name: str) -> str:
|
||||
return _("%s (%s property)") % (name, ifacename)
|
||||
|
||||
def transform_content(self, contentnode: addnodes.desc_content) -> None:
|
||||
fieldlist = nodes.field_list()
|
||||
access = None
|
||||
if "readonly" in self.options:
|
||||
access = _("read-only")
|
||||
if "writeonly" in self.options:
|
||||
access = _("write-only")
|
||||
if "readwrite" in self.options:
|
||||
access = _("read & write")
|
||||
if access:
|
||||
content = nodes.Text(access)
|
||||
fieldname = nodes.field_name("", _("Access"))
|
||||
fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
|
||||
field = nodes.field("", fieldname, fieldbody)
|
||||
fieldlist += field
|
||||
emits = self.options.get("emits-changed", None)
|
||||
if emits:
|
||||
content = nodes.Text(emits)
|
||||
fieldname = nodes.field_name("", _("Emits Changed"))
|
||||
fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
|
||||
field = nodes.field("", fieldname, fieldbody)
|
||||
fieldlist += field
|
||||
if len(fieldlist) > 0:
|
||||
contentnode.insert(0, fieldlist)
|
||||
|
||||
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
||||
contentnode = addnodes.desc_content()
|
||||
self.state.nested_parse(self.content, self.content_offset, contentnode)
|
||||
ty = self.options.get("type")
|
||||
|
||||
signode += addnodes.desc_annotation("property ", "property ")
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
signode += addnodes.desc_sig_punctuation("", ":")
|
||||
signode += addnodes.desc_sig_keyword_type(ty, ty)
|
||||
return sig
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
self.name = "dbus:member"
|
||||
return super().run()
|
||||
|
||||
|
||||
class DBusXRef(XRefRole):
|
||||
def process_link(self, env, refnode, has_explicit_title, title, target):
|
||||
refnode["dbus:interface"] = env.ref_context.get("dbus:interface")
|
||||
if not has_explicit_title:
|
||||
title = title.lstrip(".") # only has a meaning for the target
|
||||
target = target.lstrip("~") # only has a meaning for the title
|
||||
# if the first character is a tilde, don't display the module/class
|
||||
# parts of the contents
|
||||
if title[0:1] == "~":
|
||||
title = title[1:]
|
||||
dot = title.rfind(".")
|
||||
if dot != -1:
|
||||
title = title[dot + 1 :]
|
||||
# if the first character is a dot, search more specific namespaces first
|
||||
# else search builtins first
|
||||
if target[0:1] == ".":
|
||||
target = target[1:]
|
||||
refnode["refspecific"] = True
|
||||
return title, target
|
||||
|
||||
|
||||
class DBusIndex(Index):
|
||||
"""
|
||||
Index subclass to provide a D-Bus interfaces index.
|
||||
"""
|
||||
|
||||
name = "dbusindex"
|
||||
localname = _("D-Bus Interfaces Index")
|
||||
shortname = _("dbus")
|
||||
|
||||
def generate(
|
||||
self, docnames: Iterable[str] = None
|
||||
) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
|
||||
content: Dict[str, List[IndexEntry]] = {}
|
||||
# list of prefixes to ignore
|
||||
ignores: List[str] = self.domain.env.config["dbus_index_common_prefix"]
|
||||
ignores = sorted(ignores, key=len, reverse=True)
|
||||
|
||||
ifaces = sorted(
|
||||
[
|
||||
x
|
||||
for x in self.domain.data["objects"].items()
|
||||
if x[1].objtype == "interface"
|
||||
],
|
||||
key=lambda x: x[0].lower(),
|
||||
)
|
||||
for name, (docname, node_id, _) in ifaces:
|
||||
if docnames and docname not in docnames:
|
||||
continue
|
||||
|
||||
for ignore in ignores:
|
||||
if name.startswith(ignore):
|
||||
name = name[len(ignore) :]
|
||||
stripped = ignore
|
||||
break
|
||||
else:
|
||||
stripped = ""
|
||||
|
||||
entries = content.setdefault(name[0].lower(), [])
|
||||
entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
|
||||
|
||||
# sort by first letter
|
||||
sorted_content = sorted(content.items())
|
||||
|
||||
return sorted_content, False
|
||||
|
||||
|
||||
class ObjectEntry(NamedTuple):
|
||||
docname: str
|
||||
node_id: str
|
||||
objtype: str
|
||||
|
||||
|
||||
class DBusDomain(Domain):
|
||||
"""
|
||||
Implementation of the D-Bus domain.
|
||||
"""
|
||||
|
||||
name = "dbus"
|
||||
label = "D-Bus"
|
||||
object_types: Dict[str, ObjType] = {
|
||||
"interface": ObjType(_("interface"), "iface", "obj"),
|
||||
"method": ObjType(_("method"), "meth", "obj"),
|
||||
"signal": ObjType(_("signal"), "sig", "obj"),
|
||||
"property": ObjType(_("property"), "attr", "_prop", "obj"),
|
||||
}
|
||||
directives = {
|
||||
"interface": DBusInterface,
|
||||
"method": DBusMethod,
|
||||
"signal": DBusSignal,
|
||||
"property": DBusProperty,
|
||||
}
|
||||
roles = {
|
||||
"iface": DBusXRef(),
|
||||
"meth": DBusXRef(),
|
||||
"sig": DBusXRef(),
|
||||
"prop": DBusXRef(),
|
||||
}
|
||||
initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
|
||||
"objects": {}, # fullname -> ObjectEntry
|
||||
}
|
||||
indices = [
|
||||
DBusIndex,
|
||||
]
|
||||
|
||||
@property
|
||||
def objects(self) -> Dict[str, ObjectEntry]:
|
||||
return self.data.setdefault("objects", {}) # fullname -> ObjectEntry
|
||||
|
||||
def note_object(
|
||||
self, name: str, objtype: str, node_id: str, location: Any = None
|
||||
) -> None:
|
||||
self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
|
||||
|
||||
def clear_doc(self, docname: str) -> None:
|
||||
for fullname, obj in list(self.objects.items()):
|
||||
if obj.docname == docname:
|
||||
del self.objects[fullname]
|
||||
|
||||
def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:
|
||||
# skip parens
|
||||
if name[-2:] == "()":
|
||||
name = name[:-2]
|
||||
if typ in ("meth", "sig", "prop"):
|
||||
try:
|
||||
ifacename, name = name.rsplit(".", 1)
|
||||
except ValueError:
|
||||
pass
|
||||
return self.objects.get(name)
|
||||
|
||||
def resolve_xref(
|
||||
self,
|
||||
env: "BuildEnvironment",
|
||||
fromdocname: str,
|
||||
builder: "Builder",
|
||||
typ: str,
|
||||
target: str,
|
||||
node: pending_xref,
|
||||
contnode: Element,
|
||||
) -> Optional[Element]:
|
||||
"""Resolve the pending_xref *node* with the given *typ* and *target*."""
|
||||
objdef = self.find_obj(typ, target)
|
||||
if objdef:
|
||||
return node_utils.make_refnode(
|
||||
builder, fromdocname, objdef.docname, objdef.node_id, contnode
|
||||
)
|
||||
|
||||
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
|
||||
for refname, obj in self.objects.items():
|
||||
yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_domain(DBusDomain)
|
||||
app.add_config_value("dbus_index_common_prefix", [], "env")
|
373
docs/sphinx/dbusparser.py
Normal file
373
docs/sphinx/dbusparser.py
Normal file
@ -0,0 +1,373 @@
|
||||
# Based from "GDBus - GLib D-Bus Library":
|
||||
#
|
||||
# Copyright (C) 2008-2011 Red Hat, Inc.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General
|
||||
# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: David Zeuthen <davidz@redhat.com>
|
||||
|
||||
import xml.parsers.expat
|
||||
|
||||
|
||||
class Annotation:
|
||||
def __init__(self, key, value):
|
||||
self.key = key
|
||||
self.value = value
|
||||
self.annotations = []
|
||||
self.since = ""
|
||||
|
||||
|
||||
class Arg:
|
||||
def __init__(self, name, signature):
|
||||
self.name = name
|
||||
self.signature = signature
|
||||
self.annotations = []
|
||||
self.doc_string = ""
|
||||
self.since = ""
|
||||
|
||||
|
||||
class Method:
|
||||
def __init__(self, name, h_type_implies_unix_fd=True):
|
||||
self.name = name
|
||||
self.h_type_implies_unix_fd = h_type_implies_unix_fd
|
||||
self.in_args = []
|
||||
self.out_args = []
|
||||
self.annotations = []
|
||||
self.doc_string = ""
|
||||
self.since = ""
|
||||
self.deprecated = False
|
||||
self.unix_fd = False
|
||||
|
||||
|
||||
class Signal:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.args = []
|
||||
self.annotations = []
|
||||
self.doc_string = ""
|
||||
self.since = ""
|
||||
self.deprecated = False
|
||||
|
||||
|
||||
class Property:
|
||||
def __init__(self, name, signature, access):
|
||||
self.name = name
|
||||
self.signature = signature
|
||||
self.access = access
|
||||
self.annotations = []
|
||||
self.arg = Arg("value", self.signature)
|
||||
self.arg.annotations = self.annotations
|
||||
self.readable = False
|
||||
self.writable = False
|
||||
if self.access == "readwrite":
|
||||
self.readable = True
|
||||
self.writable = True
|
||||
elif self.access == "read":
|
||||
self.readable = True
|
||||
elif self.access == "write":
|
||||
self.writable = True
|
||||
else:
|
||||
raise ValueError('Invalid access type "{}"'.format(self.access))
|
||||
self.doc_string = ""
|
||||
self.since = ""
|
||||
self.deprecated = False
|
||||
self.emits_changed_signal = True
|
||||
|
||||
|
||||
class Interface:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.methods = []
|
||||
self.signals = []
|
||||
self.properties = []
|
||||
self.annotations = []
|
||||
self.doc_string = ""
|
||||
self.doc_string_brief = ""
|
||||
self.since = ""
|
||||
self.deprecated = False
|
||||
|
||||
|
||||
class DBusXMLParser:
|
||||
STATE_TOP = "top"
|
||||
STATE_NODE = "node"
|
||||
STATE_INTERFACE = "interface"
|
||||
STATE_METHOD = "method"
|
||||
STATE_SIGNAL = "signal"
|
||||
STATE_PROPERTY = "property"
|
||||
STATE_ARG = "arg"
|
||||
STATE_ANNOTATION = "annotation"
|
||||
STATE_IGNORED = "ignored"
|
||||
|
||||
def __init__(self, xml_data, h_type_implies_unix_fd=True):
|
||||
self._parser = xml.parsers.expat.ParserCreate()
|
||||
self._parser.CommentHandler = self.handle_comment
|
||||
self._parser.CharacterDataHandler = self.handle_char_data
|
||||
self._parser.StartElementHandler = self.handle_start_element
|
||||
self._parser.EndElementHandler = self.handle_end_element
|
||||
|
||||
self.parsed_interfaces = []
|
||||
self._cur_object = None
|
||||
|
||||
self.state = DBusXMLParser.STATE_TOP
|
||||
self.state_stack = []
|
||||
self._cur_object = None
|
||||
self._cur_object_stack = []
|
||||
|
||||
self.doc_comment_last_symbol = ""
|
||||
|
||||
self._h_type_implies_unix_fd = h_type_implies_unix_fd
|
||||
|
||||
self._parser.Parse(xml_data)
|
||||
|
||||
COMMENT_STATE_BEGIN = "begin"
|
||||
COMMENT_STATE_PARAMS = "params"
|
||||
COMMENT_STATE_BODY = "body"
|
||||
COMMENT_STATE_SKIP = "skip"
|
||||
|
||||
def handle_comment(self, data):
|
||||
comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
|
||||
lines = data.split("\n")
|
||||
symbol = ""
|
||||
body = ""
|
||||
in_para = False
|
||||
params = {}
|
||||
for line in lines:
|
||||
orig_line = line
|
||||
line = line.lstrip()
|
||||
if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
|
||||
if len(line) > 0:
|
||||
colon_index = line.find(": ")
|
||||
if colon_index == -1:
|
||||
if line.endswith(":"):
|
||||
symbol = line[0 : len(line) - 1]
|
||||
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
|
||||
else:
|
||||
comment_state = DBusXMLParser.COMMENT_STATE_SKIP
|
||||
else:
|
||||
symbol = line[0:colon_index]
|
||||
rest_of_line = line[colon_index + 2 :].strip()
|
||||
if len(rest_of_line) > 0:
|
||||
body += rest_of_line + "\n"
|
||||
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
|
||||
elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
|
||||
if line.startswith("@"):
|
||||
colon_index = line.find(": ")
|
||||
if colon_index == -1:
|
||||
comment_state = DBusXMLParser.COMMENT_STATE_BODY
|
||||
if not in_para:
|
||||
in_para = True
|
||||
body += orig_line + "\n"
|
||||
else:
|
||||
param = line[1:colon_index]
|
||||
docs = line[colon_index + 2 :]
|
||||
params[param] = docs
|
||||
else:
|
||||
comment_state = DBusXMLParser.COMMENT_STATE_BODY
|
||||
if len(line) > 0:
|
||||
if not in_para:
|
||||
in_para = True
|
||||
body += orig_line + "\n"
|
||||
elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
|
||||
if len(line) > 0:
|
||||
if not in_para:
|
||||
in_para = True
|
||||
body += orig_line + "\n"
|
||||
else:
|
||||
if in_para:
|
||||
body += "\n"
|
||||
in_para = False
|
||||
if in_para:
|
||||
body += "\n"
|
||||
|
||||
if symbol != "":
|
||||
self.doc_comment_last_symbol = symbol
|
||||
self.doc_comment_params = params
|
||||
self.doc_comment_body = body
|
||||
|
||||
def handle_char_data(self, data):
|
||||
# print 'char_data=%s'%data
|
||||
pass
|
||||
|
||||
def handle_start_element(self, name, attrs):
|
||||
old_state = self.state
|
||||
old_cur_object = self._cur_object
|
||||
if self.state == DBusXMLParser.STATE_IGNORED:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
elif self.state == DBusXMLParser.STATE_TOP:
|
||||
if name == DBusXMLParser.STATE_NODE:
|
||||
self.state = DBusXMLParser.STATE_NODE
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
elif self.state == DBusXMLParser.STATE_NODE:
|
||||
if name == DBusXMLParser.STATE_INTERFACE:
|
||||
self.state = DBusXMLParser.STATE_INTERFACE
|
||||
iface = Interface(attrs["name"])
|
||||
self._cur_object = iface
|
||||
self.parsed_interfaces.append(iface)
|
||||
elif name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
# assign docs, if any
|
||||
if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
|
||||
self._cur_object.doc_string = self.doc_comment_body
|
||||
if "short_description" in self.doc_comment_params:
|
||||
short_description = self.doc_comment_params["short_description"]
|
||||
self._cur_object.doc_string_brief = short_description
|
||||
if "since" in self.doc_comment_params:
|
||||
self._cur_object.since = self.doc_comment_params["since"].strip()
|
||||
|
||||
elif self.state == DBusXMLParser.STATE_INTERFACE:
|
||||
if name == DBusXMLParser.STATE_METHOD:
|
||||
self.state = DBusXMLParser.STATE_METHOD
|
||||
method = Method(
|
||||
attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
|
||||
)
|
||||
self._cur_object.methods.append(method)
|
||||
self._cur_object = method
|
||||
elif name == DBusXMLParser.STATE_SIGNAL:
|
||||
self.state = DBusXMLParser.STATE_SIGNAL
|
||||
signal = Signal(attrs["name"])
|
||||
self._cur_object.signals.append(signal)
|
||||
self._cur_object = signal
|
||||
elif name == DBusXMLParser.STATE_PROPERTY:
|
||||
self.state = DBusXMLParser.STATE_PROPERTY
|
||||
prop = Property(attrs["name"], attrs["type"], attrs["access"])
|
||||
self._cur_object.properties.append(prop)
|
||||
self._cur_object = prop
|
||||
elif name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
# assign docs, if any
|
||||
if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
|
||||
self._cur_object.doc_string = self.doc_comment_body
|
||||
if "since" in self.doc_comment_params:
|
||||
self._cur_object.since = self.doc_comment_params["since"].strip()
|
||||
|
||||
elif self.state == DBusXMLParser.STATE_METHOD:
|
||||
if name == DBusXMLParser.STATE_ARG:
|
||||
self.state = DBusXMLParser.STATE_ARG
|
||||
arg_name = None
|
||||
if "name" in attrs:
|
||||
arg_name = attrs["name"]
|
||||
arg = Arg(arg_name, attrs["type"])
|
||||
direction = attrs.get("direction", "in")
|
||||
if direction == "in":
|
||||
self._cur_object.in_args.append(arg)
|
||||
elif direction == "out":
|
||||
self._cur_object.out_args.append(arg)
|
||||
else:
|
||||
raise ValueError('Invalid direction "{}"'.format(direction))
|
||||
self._cur_object = arg
|
||||
elif name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
# assign docs, if any
|
||||
if self.doc_comment_last_symbol == old_cur_object.name:
|
||||
if "name" in attrs and attrs["name"] in self.doc_comment_params:
|
||||
doc_string = self.doc_comment_params[attrs["name"]]
|
||||
if doc_string is not None:
|
||||
self._cur_object.doc_string = doc_string
|
||||
if "since" in self.doc_comment_params:
|
||||
self._cur_object.since = self.doc_comment_params[
|
||||
"since"
|
||||
].strip()
|
||||
|
||||
elif self.state == DBusXMLParser.STATE_SIGNAL:
|
||||
if name == DBusXMLParser.STATE_ARG:
|
||||
self.state = DBusXMLParser.STATE_ARG
|
||||
arg_name = None
|
||||
if "name" in attrs:
|
||||
arg_name = attrs["name"]
|
||||
arg = Arg(arg_name, attrs["type"])
|
||||
self._cur_object.args.append(arg)
|
||||
self._cur_object = arg
|
||||
elif name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
# assign docs, if any
|
||||
if self.doc_comment_last_symbol == old_cur_object.name:
|
||||
if "name" in attrs and attrs["name"] in self.doc_comment_params:
|
||||
doc_string = self.doc_comment_params[attrs["name"]]
|
||||
if doc_string is not None:
|
||||
self._cur_object.doc_string = doc_string
|
||||
if "since" in self.doc_comment_params:
|
||||
self._cur_object.since = self.doc_comment_params[
|
||||
"since"
|
||||
].strip()
|
||||
|
||||
elif self.state == DBusXMLParser.STATE_PROPERTY:
|
||||
if name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
elif self.state == DBusXMLParser.STATE_ARG:
|
||||
if name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
elif self.state == DBusXMLParser.STATE_ANNOTATION:
|
||||
if name == DBusXMLParser.STATE_ANNOTATION:
|
||||
self.state = DBusXMLParser.STATE_ANNOTATION
|
||||
anno = Annotation(attrs["name"], attrs["value"])
|
||||
self._cur_object.annotations.append(anno)
|
||||
self._cur_object = anno
|
||||
else:
|
||||
self.state = DBusXMLParser.STATE_IGNORED
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
'Unhandled state "{}" while entering element with name "{}"'.format(
|
||||
self.state, name
|
||||
)
|
||||
)
|
||||
|
||||
self.state_stack.append(old_state)
|
||||
self._cur_object_stack.append(old_cur_object)
|
||||
|
||||
def handle_end_element(self, name):
|
||||
self.state = self.state_stack.pop()
|
||||
self._cur_object = self._cur_object_stack.pop()
|
||||
|
||||
|
||||
def parse_dbus_xml(xml_data):
|
||||
parser = DBusXMLParser(xml_data, True)
|
||||
return parser.parsed_interfaces
|
25
docs/sphinx/fakedbusdoc.py
Normal file
25
docs/sphinx/fakedbusdoc.py
Normal file
@ -0,0 +1,25 @@
|
||||
# D-Bus XML documentation extension, compatibility gunk for <sphinx4
|
||||
#
|
||||
# Copyright (C) 2021, Red Hat Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
class FakeDBusDocDirective(SphinxDirective):
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
|
||||
def run(self):
|
||||
return []
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
"""Register a fake dbus-doc directive with Sphinx"""
|
||||
app.add_directive("dbus-doc", FakeDBusDocDirective)
|
@ -2171,12 +2171,17 @@ static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
|
||||
}
|
||||
|
||||
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
|
||||
Error *err = NULL;
|
||||
char device_address[256] = "";
|
||||
if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
|
||||
if (qemu_console_fill_device_address(qxl->vga.con,
|
||||
device_address, sizeof(device_address),
|
||||
&err)) {
|
||||
spice_qxl_set_device_info(&qxl->ssd.qxl,
|
||||
device_address,
|
||||
0,
|
||||
qxl->max_outputs);
|
||||
} else {
|
||||
error_report_err(err);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -254,8 +254,8 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
|
||||
vhost_user_gpu_unblock(g);
|
||||
break;
|
||||
}
|
||||
dpy_gl_update(con, m->x, m->y, m->width, m->height);
|
||||
g->backend_blocked = true;
|
||||
dpy_gl_update(con, m->x, m->y, m->width, m->height);
|
||||
break;
|
||||
}
|
||||
case VHOST_USER_GPU_UPDATE: {
|
||||
|
@ -117,6 +117,10 @@ virtio_gpu_gl_block(void *opaque, bool block)
|
||||
g->renderer_blocked--;
|
||||
}
|
||||
assert(g->renderer_blocked >= 0);
|
||||
|
||||
if (!block && g->renderer_blocked == 0) {
|
||||
virtio_gpu_gl_flushed(g);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
@ -143,7 +147,6 @@ static const GraphicHwOps virtio_gpu_ops = {
|
||||
.text_update = virtio_gpu_text_update,
|
||||
.ui_info = virtio_gpu_ui_info,
|
||||
.gl_block = virtio_gpu_gl_block,
|
||||
.gl_flushed = virtio_gpu_gl_flushed,
|
||||
};
|
||||
|
||||
bool
|
||||
|
@ -175,7 +175,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
|
||||
virgl_renderer_force_ctx_0();
|
||||
dpy_gl_scanout_texture(
|
||||
g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
|
||||
info.flags & 1 /* FIXME: Y_0_TOP */,
|
||||
info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
|
||||
info.width, info.height,
|
||||
ss.r.x, ss.r.y, ss.r.width, ss.r.height);
|
||||
} else {
|
||||
@ -609,6 +609,7 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
|
||||
|
||||
ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
|
||||
if (ret != 0) {
|
||||
error_report("virgl could not be initialized: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -68,16 +68,6 @@ static void virtio_vga_base_gl_block(void *opaque, bool block)
|
||||
}
|
||||
}
|
||||
|
||||
static void virtio_vga_base_gl_flushed(void *opaque)
|
||||
{
|
||||
VirtIOVGABase *vvga = opaque;
|
||||
VirtIOGPUBase *g = vvga->vgpu;
|
||||
|
||||
if (g->hw_ops->gl_flushed) {
|
||||
g->hw_ops->gl_flushed(g);
|
||||
}
|
||||
}
|
||||
|
||||
static int virtio_vga_base_get_flags(void *opaque)
|
||||
{
|
||||
VirtIOVGABase *vvga = opaque;
|
||||
@ -93,7 +83,6 @@ static const GraphicHwOps virtio_vga_base_ops = {
|
||||
.text_update = virtio_vga_base_text_update,
|
||||
.ui_info = virtio_vga_base_ui_info,
|
||||
.gl_block = virtio_vga_base_gl_block,
|
||||
.gl_flushed = virtio_vga_base_gl_flushed,
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_virtio_vga_base = {
|
||||
|
86
include/chardev/char-socket.h
Normal file
86
include/chardev/char-socket.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* QEMU System Emulator
|
||||
*
|
||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef CHAR_SOCKET_H_
|
||||
#define CHAR_SOCKET_H_
|
||||
|
||||
#include "io/channel-socket.h"
|
||||
#include "io/channel-tls.h"
|
||||
#include "io/net-listener.h"
|
||||
#include "chardev/char.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TCP_MAX_FDS 16
|
||||
|
||||
typedef struct {
|
||||
char buf[21];
|
||||
size_t buflen;
|
||||
} TCPChardevTelnetInit;
|
||||
|
||||
typedef enum {
|
||||
TCP_CHARDEV_STATE_DISCONNECTED,
|
||||
TCP_CHARDEV_STATE_CONNECTING,
|
||||
TCP_CHARDEV_STATE_CONNECTED,
|
||||
} TCPChardevState;
|
||||
|
||||
typedef ChardevClass SocketChardevClass;
|
||||
|
||||
struct SocketChardev {
|
||||
Chardev parent;
|
||||
QIOChannel *ioc; /* Client I/O channel */
|
||||
QIOChannelSocket *sioc; /* Client master channel */
|
||||
QIONetListener *listener;
|
||||
GSource *hup_source;
|
||||
QCryptoTLSCreds *tls_creds;
|
||||
char *tls_authz;
|
||||
TCPChardevState state;
|
||||
int max_size;
|
||||
int do_telnetopt;
|
||||
int do_nodelay;
|
||||
int *read_msgfds;
|
||||
size_t read_msgfds_num;
|
||||
int *write_msgfds;
|
||||
size_t write_msgfds_num;
|
||||
bool registered_yank;
|
||||
|
||||
SocketAddress *addr;
|
||||
bool is_listen;
|
||||
bool is_telnet;
|
||||
bool is_tn3270;
|
||||
GSource *telnet_source;
|
||||
TCPChardevTelnetInit *telnet_init;
|
||||
|
||||
bool is_websock;
|
||||
|
||||
GSource *reconnect_timer;
|
||||
int64_t reconnect_time;
|
||||
bool connect_err_reported;
|
||||
|
||||
QIOTask *connect_task;
|
||||
};
|
||||
typedef struct SocketChardev SocketChardev;
|
||||
|
||||
DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
|
||||
TYPE_CHARDEV_SOCKET)
|
||||
|
||||
#endif /* CHAR_SOCKET_H_ */
|
@ -209,4 +209,9 @@ int qemu_pstrcmp0(const char **str1, const char **str2);
|
||||
*/
|
||||
char *get_relocated_path(const char *dir);
|
||||
|
||||
static inline const char *yes_no(bool b)
|
||||
{
|
||||
return b ? "yes" : "no";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -12,6 +12,30 @@
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "chardev/char.h"
|
||||
#include "qemu/notify.h"
|
||||
#include "qemu/typedefs.h"
|
||||
|
||||
/* glib/gio 2.68 */
|
||||
#define DBUS_METHOD_INVOCATION_HANDLED TRUE
|
||||
#define DBUS_METHOD_INVOCATION_UNHANDLED FALSE
|
||||
|
||||
/* in msec */
|
||||
#define DBUS_DEFAULT_TIMEOUT 1000
|
||||
|
||||
#define DBUS_DISPLAY1_ROOT "/org/qemu/Display1"
|
||||
|
||||
#define DBUS_DISPLAY_ERROR (dbus_display_error_quark())
|
||||
GQuark dbus_display_error_quark(void);
|
||||
|
||||
typedef enum {
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
DBUS_DISPLAY_ERROR_INVALID,
|
||||
DBUS_DISPLAY_ERROR_UNSUPPORTED,
|
||||
DBUS_DISPLAY_N_ERRORS,
|
||||
} DBusDisplayError;
|
||||
|
||||
GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
|
||||
const char *name,
|
||||
Error **errp);
|
||||
|
@ -150,4 +150,6 @@ QDict *keyval_parse(const char *params, const char *implied_key,
|
||||
bool *help, Error **errp);
|
||||
void keyval_merge(QDict *old, const QDict *new, Error **errp);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(QemuOpts, qemu_opts_del)
|
||||
|
||||
#endif
|
||||
|
@ -20,8 +20,10 @@
|
||||
*/
|
||||
|
||||
typedef enum QemuClipboardType QemuClipboardType;
|
||||
typedef enum QemuClipboardNotifyType QemuClipboardNotifyType;
|
||||
typedef enum QemuClipboardSelection QemuClipboardSelection;
|
||||
typedef struct QemuClipboardPeer QemuClipboardPeer;
|
||||
typedef struct QemuClipboardNotify QemuClipboardNotify;
|
||||
typedef struct QemuClipboardInfo QemuClipboardInfo;
|
||||
|
||||
/**
|
||||
@ -55,18 +57,46 @@ enum QemuClipboardSelection {
|
||||
* struct QemuClipboardPeer
|
||||
*
|
||||
* @name: peer name.
|
||||
* @update: notifier for clipboard updates.
|
||||
* @notifier: notifier for clipboard updates.
|
||||
* @request: callback for clipboard data requests.
|
||||
*
|
||||
* Clipboard peer description.
|
||||
*/
|
||||
struct QemuClipboardPeer {
|
||||
const char *name;
|
||||
Notifier update;
|
||||
Notifier notifier;
|
||||
void (*request)(QemuClipboardInfo *info,
|
||||
QemuClipboardType type);
|
||||
};
|
||||
|
||||
/**
|
||||
* enum QemuClipboardNotifyType
|
||||
*
|
||||
* @QEMU_CLIPBOARD_UPDATE_INFO: clipboard info update
|
||||
* @QEMU_CLIPBOARD_RESET_SERIAL: reset clipboard serial
|
||||
*
|
||||
* Clipboard notify type.
|
||||
*/
|
||||
enum QemuClipboardNotifyType {
|
||||
QEMU_CLIPBOARD_UPDATE_INFO,
|
||||
QEMU_CLIPBOARD_RESET_SERIAL,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct QemuClipboardNotify
|
||||
*
|
||||
* @type: the type of event.
|
||||
* @info: a QemuClipboardInfo event.
|
||||
*
|
||||
* Clipboard notify data.
|
||||
*/
|
||||
struct QemuClipboardNotify {
|
||||
QemuClipboardNotifyType type;
|
||||
union {
|
||||
QemuClipboardInfo *info;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* struct QemuClipboardInfo
|
||||
*
|
||||
@ -74,6 +104,8 @@ struct QemuClipboardPeer {
|
||||
* @owner: clipboard owner.
|
||||
* @selection: clipboard selection.
|
||||
* @types: clipboard data array (one entry per type).
|
||||
* @has_serial: whether @serial is available.
|
||||
* @serial: the grab serial counter.
|
||||
*
|
||||
* Clipboard content data and metadata.
|
||||
*/
|
||||
@ -81,6 +113,8 @@ struct QemuClipboardInfo {
|
||||
uint32_t refcount;
|
||||
QemuClipboardPeer *owner;
|
||||
QemuClipboardSelection selection;
|
||||
bool has_serial;
|
||||
uint32_t serial;
|
||||
struct {
|
||||
bool available;
|
||||
bool requested;
|
||||
@ -140,6 +174,16 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
|
||||
*/
|
||||
QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection);
|
||||
|
||||
/**
|
||||
* qemu_clipboard_check_serial
|
||||
*
|
||||
* @info: clipboard info.
|
||||
* @client: whether to check from the client context and priority.
|
||||
*
|
||||
* Return TRUE if the @info has a higher serial than the current clipboard.
|
||||
*/
|
||||
bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client);
|
||||
|
||||
/**
|
||||
* qemu_clipboard_info_new
|
||||
*
|
||||
@ -188,6 +232,13 @@ void qemu_clipboard_info_unref(QemuClipboardInfo *info);
|
||||
*/
|
||||
void qemu_clipboard_update(QemuClipboardInfo *info);
|
||||
|
||||
/**
|
||||
* qemu_clipboard_reset_serial
|
||||
*
|
||||
* Reset the clipboard serial.
|
||||
*/
|
||||
void qemu_clipboard_reset_serial(void);
|
||||
|
||||
/**
|
||||
* qemu_clipboard_request
|
||||
*
|
||||
|
@ -108,6 +108,17 @@ struct QemuConsoleClass {
|
||||
#define QEMU_ALLOCATED_FLAG 0x01
|
||||
#define QEMU_PLACEHOLDER_FLAG 0x02
|
||||
|
||||
typedef struct ScanoutTexture {
|
||||
uint32_t backing_id;
|
||||
bool backing_y_0_top;
|
||||
uint32_t backing_width;
|
||||
uint32_t backing_height;
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} ScanoutTexture;
|
||||
|
||||
typedef struct DisplaySurface {
|
||||
pixman_format_code_t format;
|
||||
pixman_image_t *image;
|
||||
@ -178,7 +189,24 @@ typedef struct QemuDmaBuf {
|
||||
bool draw_submitted;
|
||||
} QemuDmaBuf;
|
||||
|
||||
enum display_scanout {
|
||||
SCANOUT_NONE,
|
||||
SCANOUT_SURFACE,
|
||||
SCANOUT_TEXTURE,
|
||||
SCANOUT_DMABUF,
|
||||
};
|
||||
|
||||
typedef struct DisplayScanout {
|
||||
enum display_scanout kind;
|
||||
union {
|
||||
/* DisplaySurface *surface; is kept in QemuConsole */
|
||||
ScanoutTexture texture;
|
||||
QemuDmaBuf *dmabuf;
|
||||
};
|
||||
} DisplayScanout;
|
||||
|
||||
typedef struct DisplayState DisplayState;
|
||||
typedef struct DisplayGLCtx DisplayGLCtx;
|
||||
|
||||
typedef struct DisplayChangeListenerOps {
|
||||
const char *dpy_name;
|
||||
@ -213,16 +241,6 @@ typedef struct DisplayChangeListenerOps {
|
||||
void (*dpy_cursor_define)(DisplayChangeListener *dcl,
|
||||
QEMUCursor *cursor);
|
||||
|
||||
/* required if GL */
|
||||
QEMUGLContext (*dpy_gl_ctx_create)(DisplayChangeListener *dcl,
|
||||
QEMUGLParams *params);
|
||||
/* required if GL */
|
||||
void (*dpy_gl_ctx_destroy)(DisplayChangeListener *dcl,
|
||||
QEMUGLContext ctx);
|
||||
/* required if GL */
|
||||
int (*dpy_gl_ctx_make_current)(DisplayChangeListener *dcl,
|
||||
QEMUGLContext ctx);
|
||||
|
||||
/* required if GL */
|
||||
void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl);
|
||||
/* required if GL */
|
||||
@ -263,6 +281,26 @@ struct DisplayChangeListener {
|
||||
QLIST_ENTRY(DisplayChangeListener) next;
|
||||
};
|
||||
|
||||
typedef struct DisplayGLCtxOps {
|
||||
/*
|
||||
* We only check if the GLCtx is compatible with a DCL via ops. A natural
|
||||
* evolution of this would be a callback to check some runtime requirements
|
||||
* and allow various DCL kinds.
|
||||
*/
|
||||
const DisplayChangeListenerOps *compatible_dcl;
|
||||
|
||||
QEMUGLContext (*dpy_gl_ctx_create)(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params);
|
||||
void (*dpy_gl_ctx_destroy)(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
int (*dpy_gl_ctx_make_current)(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
} DisplayGLCtxOps;
|
||||
|
||||
struct DisplayGLCtx {
|
||||
const DisplayGLCtxOps *ops;
|
||||
};
|
||||
|
||||
DisplayState *init_displaystate(void);
|
||||
DisplaySurface *qemu_create_displaysurface_from(int width, int height,
|
||||
pixman_format_code_t format,
|
||||
@ -292,7 +330,7 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl);
|
||||
|
||||
bool dpy_ui_info_supported(QemuConsole *con);
|
||||
const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con);
|
||||
int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info);
|
||||
int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay);
|
||||
|
||||
void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h);
|
||||
void dpy_gfx_update_full(QemuConsole *con);
|
||||
@ -391,7 +429,6 @@ typedef struct GraphicHwOps {
|
||||
void (*update_interval)(void *opaque, uint64_t interval);
|
||||
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
|
||||
void (*gl_block)(void *opaque, bool block);
|
||||
void (*gl_flushed)(void *opaque);
|
||||
} GraphicHwOps;
|
||||
|
||||
QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
|
||||
@ -407,10 +444,11 @@ void graphic_hw_update_done(QemuConsole *con);
|
||||
void graphic_hw_invalidate(QemuConsole *con);
|
||||
void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
|
||||
void graphic_hw_gl_block(QemuConsole *con, bool block);
|
||||
void graphic_hw_gl_flushed(QemuConsole *con);
|
||||
|
||||
void qemu_console_early_init(void);
|
||||
|
||||
void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *ctx);
|
||||
|
||||
QemuConsole *qemu_console_lookup_by_index(unsigned int index);
|
||||
QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
|
||||
QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
|
||||
@ -484,4 +522,10 @@ int index_from_key(const char *key, size_t key_length);
|
||||
int udmabuf_fd(void);
|
||||
#endif
|
||||
|
||||
/* util.c */
|
||||
bool qemu_console_fill_device_address(QemuConsole *con,
|
||||
char *device_address,
|
||||
size_t size,
|
||||
Error **errp);
|
||||
|
||||
#endif
|
||||
|
17
include/ui/dbus-display.h
Normal file
17
include/ui/dbus-display.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef DBUS_DISPLAY_H_
|
||||
#define DBUS_DISPLAY_H_
|
||||
|
||||
#include "qapi/error.h"
|
||||
#include "ui/dbus-module.h"
|
||||
|
||||
static inline bool qemu_using_dbus_display(Error **errp)
|
||||
{
|
||||
if (!using_dbus_display) {
|
||||
error_set(errp, ERROR_CLASS_DEVICE_NOT_ACTIVE,
|
||||
"D-Bus display is not in use");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* DBUS_DISPLAY_H_ */
|
11
include/ui/dbus-module.h
Normal file
11
include/ui/dbus-module.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef DBUS_MODULE_H_
|
||||
#define DBUS_MODULE_H_
|
||||
|
||||
struct QemuDBusDisplayOps {
|
||||
bool (*add_client)(int csock, Error **errp);
|
||||
};
|
||||
|
||||
extern int using_dbus_display;
|
||||
extern struct QemuDBusDisplayOps qemu_dbus_display;
|
||||
|
||||
#endif /* DBUS_MODULE_H_*/
|
@ -4,10 +4,10 @@
|
||||
#include "ui/console.h"
|
||||
#include "ui/egl-helpers.h"
|
||||
|
||||
QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params);
|
||||
void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
|
||||
int qemu_egl_make_context_current(DisplayChangeListener *dcl,
|
||||
void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
|
||||
int qemu_egl_make_context_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
|
||||
#endif /* EGL_CONTEXT_H */
|
||||
|
@ -35,6 +35,7 @@ typedef struct GtkDisplayState GtkDisplayState;
|
||||
|
||||
typedef struct VirtualGfxConsole {
|
||||
GtkWidget *drawing_area;
|
||||
DisplayGLCtx dgc;
|
||||
DisplayChangeListener dcl;
|
||||
QKbdState *kbd;
|
||||
DisplaySurface *ds;
|
||||
@ -165,7 +166,7 @@ void gd_egl_update(DisplayChangeListener *dcl,
|
||||
void gd_egl_refresh(DisplayChangeListener *dcl);
|
||||
void gd_egl_switch(DisplayChangeListener *dcl,
|
||||
DisplaySurface *surface);
|
||||
QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params);
|
||||
void gd_egl_scanout_disable(DisplayChangeListener *dcl);
|
||||
void gd_egl_scanout_texture(DisplayChangeListener *dcl,
|
||||
@ -187,7 +188,7 @@ void gd_egl_flush(DisplayChangeListener *dcl,
|
||||
void gd_egl_scanout_flush(DisplayChangeListener *dcl,
|
||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
||||
void gtk_egl_init(DisplayGLMode mode);
|
||||
int gd_egl_make_current(DisplayChangeListener *dcl,
|
||||
int gd_egl_make_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
|
||||
/* ui/gtk-gl-area.c */
|
||||
@ -198,9 +199,9 @@ void gd_gl_area_update(DisplayChangeListener *dcl,
|
||||
void gd_gl_area_refresh(DisplayChangeListener *dcl);
|
||||
void gd_gl_area_switch(DisplayChangeListener *dcl,
|
||||
DisplaySurface *surface);
|
||||
QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params);
|
||||
void gd_gl_area_destroy_context(DisplayChangeListener *dcl,
|
||||
void gd_gl_area_destroy_context(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
|
||||
QemuDmaBuf *dmabuf);
|
||||
@ -215,7 +216,7 @@ void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
|
||||
void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
|
||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
||||
void gtk_gl_area_init(void);
|
||||
int gd_gl_area_make_current(DisplayChangeListener *dcl,
|
||||
int gd_gl_area_make_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
|
||||
/* gtk-clipboard.c */
|
||||
|
@ -16,6 +16,7 @@
|
||||
#endif
|
||||
|
||||
struct sdl2_console {
|
||||
DisplayGLCtx dgc;
|
||||
DisplayChangeListener dcl;
|
||||
DisplaySurface *surface;
|
||||
DisplayOptions *opts;
|
||||
@ -65,10 +66,10 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
|
||||
void sdl2_gl_refresh(DisplayChangeListener *dcl);
|
||||
void sdl2_gl_redraw(struct sdl2_console *scon);
|
||||
|
||||
QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params);
|
||||
void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
|
||||
int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
|
||||
void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
|
||||
int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx);
|
||||
|
||||
void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
|
||||
|
@ -86,6 +86,7 @@ typedef struct SimpleSpiceCursor SimpleSpiceCursor;
|
||||
|
||||
struct SimpleSpiceDisplay {
|
||||
DisplaySurface *ds;
|
||||
DisplayGLCtx dgc;
|
||||
DisplayChangeListener dcl;
|
||||
void *buf;
|
||||
int bufsize;
|
||||
@ -183,8 +184,4 @@ void qemu_spice_display_start(void);
|
||||
void qemu_spice_display_stop(void);
|
||||
int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
|
||||
|
||||
bool qemu_spice_fill_device_address(QemuConsole *con,
|
||||
char *device_address,
|
||||
size_t size);
|
||||
|
||||
#endif
|
||||
|
22
meson.build
22
meson.build
@ -404,14 +404,16 @@ endif
|
||||
add_project_arguments(config_host['GLIB_CFLAGS'].split(),
|
||||
native: false, language: ['c', 'cpp', 'objc'])
|
||||
glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
|
||||
link_args: config_host['GLIB_LIBS'].split())
|
||||
link_args: config_host['GLIB_LIBS'].split(),
|
||||
version: config_host['GLIB_VERSION'])
|
||||
# override glib dep with the configure results (for subprojects)
|
||||
meson.override_dependency('glib-2.0', glib)
|
||||
|
||||
gio = not_found
|
||||
if 'CONFIG_GIO' in config_host
|
||||
gio = declare_dependency(compile_args: config_host['GIO_CFLAGS'].split(),
|
||||
link_args: config_host['GIO_LIBS'].split())
|
||||
link_args: config_host['GIO_LIBS'].split(),
|
||||
version: config_host['GLIB_VERSION'])
|
||||
endif
|
||||
lttng = not_found
|
||||
if 'ust' in get_option('trace_backends')
|
||||
@ -1395,6 +1397,15 @@ endif
|
||||
have_host_block_device = (targetos != 'darwin' or
|
||||
cc.has_header('IOKit/storage/IOMedia.h'))
|
||||
|
||||
dbus_display = false
|
||||
if not get_option('dbus_display').disabled()
|
||||
# FIXME enable_modules shouldn't be necessary, but: https://github.com/mesonbuild/meson/issues/8333
|
||||
dbus_display = gio.version().version_compare('>=2.64') and config_host.has_key('GDBUS_CODEGEN') and enable_modules
|
||||
if get_option('dbus_display').enabled() and not dbus_display
|
||||
error('Requirements missing to enable -display dbus (glib>=2.64 && --enable-modules)')
|
||||
endif
|
||||
endif
|
||||
|
||||
have_virtfs = (targetos == 'linux' and
|
||||
have_system and
|
||||
libattr.found() and
|
||||
@ -1497,8 +1508,14 @@ config_host_data.set('CONFIG_ZSTD', zstd.found())
|
||||
config_host_data.set('CONFIG_FUSE', fuse.found())
|
||||
config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
|
||||
config_host_data.set('CONFIG_SPICE_PROTOCOL', spice_protocol.found())
|
||||
if spice_protocol.found()
|
||||
config_host_data.set('CONFIG_SPICE_PROTOCOL_MAJOR', spice_protocol.version().split('.')[0])
|
||||
config_host_data.set('CONFIG_SPICE_PROTOCOL_MINOR', spice_protocol.version().split('.')[1])
|
||||
config_host_data.set('CONFIG_SPICE_PROTOCOL_MICRO', spice_protocol.version().split('.')[2])
|
||||
endif
|
||||
config_host_data.set('CONFIG_SPICE', spice.found())
|
||||
config_host_data.set('CONFIG_X11', x11.found())
|
||||
config_host_data.set('CONFIG_DBUS_DISPLAY', dbus_display)
|
||||
config_host_data.set('CONFIG_CFI', get_option('cfi'))
|
||||
config_host_data.set('CONFIG_SELINUX', selinux.found())
|
||||
config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
|
||||
@ -3222,6 +3239,7 @@ summary_info += {'Trace backends': ','.join(get_option('trace_backends'))}
|
||||
if 'simple' in get_option('trace_backends')
|
||||
summary_info += {'Trace output file': get_option('trace_file') + '-<pid>'}
|
||||
endif
|
||||
summary_info += {'D-Bus display': dbus_display}
|
||||
summary_info += {'QOM debugging': config_host.has_key('CONFIG_QOM_CAST_DEBUG')}
|
||||
summary_info += {'vhost-kernel support': config_host.has_key('CONFIG_VHOST_KERNEL')}
|
||||
summary_info += {'vhost-net support': config_host.has_key('CONFIG_VHOST_NET')}
|
||||
|
@ -66,6 +66,8 @@ option('cfi_debug', type: 'boolean', value: 'false',
|
||||
description: 'Verbose errors in case of CFI violation')
|
||||
option('multiprocess', type: 'feature', value: 'auto',
|
||||
description: 'Out of process device emulation support')
|
||||
option('dbus_display', type: 'feature', value: 'auto',
|
||||
description: '-display dbus support')
|
||||
|
||||
option('attr', type : 'feature', value : 'auto',
|
||||
description: 'attr/xattr support')
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "chardev/char.h"
|
||||
#include "ui/qemu-spice.h"
|
||||
#include "ui/console.h"
|
||||
#include "ui/dbus-display.h"
|
||||
#include "sysemu/kvm.h"
|
||||
#include "sysemu/runstate.h"
|
||||
#include "sysemu/runstate-action.h"
|
||||
@ -285,6 +286,18 @@ void qmp_add_client(const char *protocol, const char *fdname,
|
||||
skipauth = has_skipauth ? skipauth : false;
|
||||
vnc_display_add_client(NULL, fd, skipauth);
|
||||
return;
|
||||
#endif
|
||||
#ifdef CONFIG_DBUS_DISPLAY
|
||||
} else if (strcmp(protocol, "@dbus-display") == 0) {
|
||||
if (!qemu_using_dbus_display(errp)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
if (!qemu_dbus_display.add_client(fd, errp)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
} else if ((s = qemu_chr_find(protocol)) != NULL) {
|
||||
if (qemu_chr_add_client(s, fd) < 0) {
|
||||
|
@ -386,7 +386,7 @@
|
||||
# Since: 4.0
|
||||
##
|
||||
{ 'enum': 'AudiodevDriver',
|
||||
'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',
|
||||
'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',
|
||||
'sdl', 'spice', 'wav' ] }
|
||||
|
||||
##
|
||||
@ -412,6 +412,7 @@
|
||||
'none': 'AudiodevGenericOptions',
|
||||
'alsa': 'AudiodevAlsaOptions',
|
||||
'coreaudio': 'AudiodevCoreaudioOptions',
|
||||
'dbus': 'AudiodevGenericOptions',
|
||||
'dsound': 'AudiodevDsoundOptions',
|
||||
'jack': 'AudiodevJackOptions',
|
||||
'oss': 'AudiodevOssOptions',
|
||||
|
@ -358,6 +358,20 @@
|
||||
'base': 'ChardevCommon',
|
||||
'if': 'CONFIG_SPICE' }
|
||||
|
||||
##
|
||||
# @ChardevDBus:
|
||||
#
|
||||
# Configuration info for DBus chardevs.
|
||||
#
|
||||
# @name: name of the channel (following docs/spice-port-fqdn.txt)
|
||||
#
|
||||
# Since: 7.0
|
||||
##
|
||||
{ 'struct': 'ChardevDBus',
|
||||
'data': { 'name': 'str' },
|
||||
'base': 'ChardevCommon',
|
||||
'if': 'CONFIG_DBUS_DISPLAY' }
|
||||
|
||||
##
|
||||
# @ChardevVC:
|
||||
#
|
||||
@ -422,6 +436,7 @@
|
||||
# @spicevmc: Since 1.5
|
||||
# @spiceport: Since 1.5
|
||||
# @qemu-vdagent: Since 6.1
|
||||
# @dbus: Since 7.0
|
||||
# @vc: v1.5
|
||||
# @ringbuf: Since 1.6
|
||||
# @memory: Since 1.5
|
||||
@ -447,6 +462,7 @@
|
||||
{ 'name': 'spicevmc', 'if': 'CONFIG_SPICE' },
|
||||
{ 'name': 'spiceport', 'if': 'CONFIG_SPICE' },
|
||||
{ 'name': 'qemu-vdagent', 'if': 'CONFIG_SPICE_PROTOCOL' },
|
||||
{ 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' },
|
||||
'vc',
|
||||
'ringbuf',
|
||||
# next one is just for compatibility
|
||||
@ -535,6 +551,15 @@
|
||||
'data': { 'data': 'ChardevQemuVDAgent' },
|
||||
'if': 'CONFIG_SPICE_PROTOCOL' }
|
||||
|
||||
##
|
||||
# @ChardevDBusWrapper:
|
||||
#
|
||||
# Since: 7.0
|
||||
##
|
||||
{ 'struct': 'ChardevDBusWrapper',
|
||||
'data': { 'data': 'ChardevDBus' },
|
||||
'if': 'CONFIG_DBUS_DISPLAY' }
|
||||
|
||||
##
|
||||
# @ChardevVCWrapper:
|
||||
#
|
||||
@ -582,6 +607,8 @@
|
||||
'if': 'CONFIG_SPICE' },
|
||||
'qemu-vdagent': { 'type': 'ChardevQemuVDAgentWrapper',
|
||||
'if': 'CONFIG_SPICE_PROTOCOL' },
|
||||
'dbus': { 'type': 'ChardevDBusWrapper',
|
||||
'if': 'CONFIG_DBUS_DISPLAY' },
|
||||
'vc': 'ChardevVCWrapper',
|
||||
'ringbuf': 'ChardevRingbufWrapper',
|
||||
# next one is just for compatibility
|
||||
|
@ -14,8 +14,8 @@
|
||||
# Allow client connections for VNC, Spice and socket based
|
||||
# character devices to be passed in to QEMU via SCM_RIGHTS.
|
||||
#
|
||||
# @protocol: protocol name. Valid names are "vnc", "spice" or the
|
||||
# name of a character device (eg. from -chardev id=XXXX)
|
||||
# @protocol: protocol name. Valid names are "vnc", "spice", "@dbus-display" or
|
||||
# the name of a character device (eg. from -chardev id=XXXX)
|
||||
#
|
||||
# @fdname: file descriptor name previously passed via 'getfd' command
|
||||
#
|
||||
|
34
qapi/ui.json
34
qapi/ui.json
@ -1121,6 +1121,30 @@
|
||||
{ 'struct' : 'DisplayEGLHeadless',
|
||||
'data' : { '*rendernode' : 'str' } }
|
||||
|
||||
##
|
||||
# @DisplayDBus:
|
||||
#
|
||||
# DBus display options.
|
||||
#
|
||||
# @addr: The D-Bus bus address (default to the session bus).
|
||||
#
|
||||
# @rendernode: Which DRM render node should be used. Default is the first
|
||||
# available node on the host.
|
||||
#
|
||||
# @p2p: Whether to use peer-to-peer connections (accepted through
|
||||
# ``add_client``).
|
||||
#
|
||||
# @audiodev: Use the specified DBus audiodev to export audio.
|
||||
#
|
||||
# Since: 7.0
|
||||
#
|
||||
##
|
||||
{ 'struct' : 'DisplayDBus',
|
||||
'data' : { '*rendernode' : 'str',
|
||||
'*addr': 'str',
|
||||
'*p2p': 'bool',
|
||||
'*audiodev': 'str' } }
|
||||
|
||||
##
|
||||
# @DisplayGLMode:
|
||||
#
|
||||
@ -1186,6 +1210,8 @@
|
||||
# application to connect to it. The server will redirect
|
||||
# the serial console and QEMU monitors. (Since 4.0)
|
||||
#
|
||||
# @dbus: Start a D-Bus service for the display. (Since 7.0)
|
||||
#
|
||||
# Since: 2.12
|
||||
#
|
||||
##
|
||||
@ -1199,7 +1225,10 @@
|
||||
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
|
||||
{ 'name': 'curses', 'if': 'CONFIG_CURSES' },
|
||||
{ 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
|
||||
{ 'name': 'spice-app', 'if': 'CONFIG_SPICE'} ] }
|
||||
{ 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
|
||||
{ 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
|
||||
]
|
||||
}
|
||||
|
||||
##
|
||||
# @DisplayOptions:
|
||||
@ -1227,7 +1256,8 @@
|
||||
'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
|
||||
'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
|
||||
'egl-headless': { 'type': 'DisplayEGLHeadless',
|
||||
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }
|
||||
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
|
||||
'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -659,6 +659,9 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
|
||||
#endif
|
||||
#ifdef CONFIG_SPICE
|
||||
"-audiodev spice,id=id[,prop[=value][,...]]\n"
|
||||
#endif
|
||||
#ifdef CONFIG_DBUS_DISPLAY
|
||||
"-audiodev dbus,id=id[,prop[=value][,...]]\n"
|
||||
#endif
|
||||
"-audiodev wav,id=id[,prop[=value][,...]]\n"
|
||||
" path= path of wav file to record\n",
|
||||
@ -1862,6 +1865,10 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
|
||||
#endif
|
||||
#if defined(CONFIG_OPENGL)
|
||||
"-display egl-headless[,rendernode=<file>]\n"
|
||||
#endif
|
||||
#if defined(CONFIG_DBUS_DISPLAY)
|
||||
"-display dbus[,addr=<dbusaddr>]\n"
|
||||
" [,gl=on|core|es|off][,rendernode=<file>]\n"
|
||||
#endif
|
||||
"-display none\n"
|
||||
" select display backend type\n"
|
||||
@ -1889,6 +1896,19 @@ SRST
|
||||
application. The Spice server will redirect the serial consoles
|
||||
and QEMU monitors. (Since 4.0)
|
||||
|
||||
``dbus``
|
||||
Export the display over D-Bus interfaces. (Since 7.0)
|
||||
|
||||
The connection is registered with the "org.qemu" name (and queued when
|
||||
already owned).
|
||||
|
||||
``addr=<dbusaddr>`` : D-Bus bus address to connect to.
|
||||
|
||||
``p2p=yes|no`` : Use peer-to-peer connection, accepted via QMP ``add_client``.
|
||||
|
||||
``gl=on|off|core|es`` : Use OpenGL for rendering (the D-Bus interface
|
||||
will share framebuffers with DMABUF file descriptors).
|
||||
|
||||
``sdl``
|
||||
Display video output via SDL (usually in a separate graphics
|
||||
window; see the SDL documentation for other possibilities).
|
||||
|
@ -33,6 +33,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' coreaudio CoreAudio sound support'
|
||||
printf "%s\n" ' curl CURL block device driver'
|
||||
printf "%s\n" ' curses curses UI'
|
||||
printf "%s\n" ' dbus-display -display dbus support'
|
||||
printf "%s\n" ' docs Documentations build support'
|
||||
printf "%s\n" ' dsound DirectSound sound support'
|
||||
printf "%s\n" ' fuse FUSE block device export'
|
||||
@ -131,6 +132,8 @@ _meson_option_parse() {
|
||||
--disable-curl) printf "%s" -Dcurl=disabled ;;
|
||||
--enable-curses) printf "%s" -Dcurses=enabled ;;
|
||||
--disable-curses) printf "%s" -Dcurses=disabled ;;
|
||||
--enable-dbus-display) printf "%s" -Ddbus_display=enabled ;;
|
||||
--disable-dbus-display) printf "%s" -Ddbus_display=disabled ;;
|
||||
--enable-docs) printf "%s" -Ddocs=enabled ;;
|
||||
--disable-docs) printf "%s" -Ddocs=disabled ;;
|
||||
--enable-dsound) printf "%s" -Ddsound=enabled ;;
|
||||
|
@ -51,6 +51,9 @@ def main(args):
|
||||
with open('compile_commands.json') as f:
|
||||
compile_commands = json.load(f)
|
||||
for src in args:
|
||||
if not src.endswith('.c'):
|
||||
print("MODINFO_DEBUG skip %s" % src)
|
||||
continue
|
||||
print("MODINFO_DEBUG src %s" % src)
|
||||
command = find_command(src, target, compile_commands)
|
||||
cmdline = process_command(src, command)
|
||||
|
257
tests/qtest/dbus-display-test.c
Normal file
257
tests/qtest/dbus-display-test.c
Normal file
@ -0,0 +1,257 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/dbus.h"
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include "libqos/libqtest.h"
|
||||
#include "qemu-common.h"
|
||||
#include "dbus-display1.h"
|
||||
|
||||
static GDBusConnection*
|
||||
test_dbus_p2p_from_fd(int fd)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GSocket) socket = NULL;
|
||||
g_autoptr(GSocketConnection) socketc = NULL;
|
||||
GDBusConnection *conn;
|
||||
|
||||
socket = g_socket_new_from_fd(fd, &err);
|
||||
g_assert_no_error(err);
|
||||
|
||||
socketc = g_socket_connection_factory_create_connection(socket);
|
||||
g_assert(socketc != NULL);
|
||||
|
||||
conn = g_dbus_connection_new_sync(
|
||||
G_IO_STREAM(socketc), NULL,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
||||
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
|
||||
NULL, NULL, &err);
|
||||
g_assert_no_error(err);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void
|
||||
test_setup(QTestState **qts, GDBusConnection **conn)
|
||||
{
|
||||
int pair[2];
|
||||
|
||||
*qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
|
||||
|
||||
g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
|
||||
|
||||
qtest_qmp_add_client(*qts, "@dbus-display", pair[1]);
|
||||
|
||||
*conn = test_dbus_p2p_from_fd(pair[0]);
|
||||
g_dbus_connection_start_message_processing(*conn);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dbus_display_vm(void)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GDBusConnection) conn = NULL;
|
||||
g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
|
||||
QTestState *qts = NULL;
|
||||
|
||||
test_setup(&qts, &conn);
|
||||
|
||||
vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
|
||||
qemu_dbus_display1_vm_proxy_new_sync(
|
||||
conn,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL,
|
||||
DBUS_DISPLAY1_ROOT "/VM",
|
||||
NULL,
|
||||
&err));
|
||||
g_assert_no_error(err);
|
||||
|
||||
g_assert_cmpstr(
|
||||
qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)),
|
||||
==,
|
||||
"dbus-test");
|
||||
qtest_quit(qts);
|
||||
}
|
||||
|
||||
typedef struct TestDBusConsoleRegister {
|
||||
GMainLoop *loop;
|
||||
GThread *thread;
|
||||
GDBusConnection *listener_conn;
|
||||
GDBusObjectManagerServer *server;
|
||||
} TestDBusConsoleRegister;
|
||||
|
||||
static gboolean listener_handle_scanout(
|
||||
QemuDBusDisplay1Listener *object,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint arg_width,
|
||||
guint arg_height,
|
||||
guint arg_stride,
|
||||
guint arg_pixman_format,
|
||||
GVariant *arg_data,
|
||||
TestDBusConsoleRegister *test)
|
||||
{
|
||||
g_main_loop_quit(test->loop);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
test_dbus_console_setup_listener(TestDBusConsoleRegister *test)
|
||||
{
|
||||
g_autoptr(GDBusObjectSkeleton) listener = NULL;
|
||||
g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL;
|
||||
|
||||
test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
|
||||
listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener");
|
||||
iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON(
|
||||
qemu_dbus_display1_listener_skeleton_new());
|
||||
g_object_connect(iface,
|
||||
"signal::handle-scanout", listener_handle_scanout, test,
|
||||
NULL);
|
||||
g_dbus_object_skeleton_add_interface(listener,
|
||||
G_DBUS_INTERFACE_SKELETON(iface));
|
||||
g_dbus_object_manager_server_export(test->server, listener);
|
||||
g_dbus_object_manager_server_set_connection(test->server,
|
||||
test->listener_conn);
|
||||
|
||||
g_dbus_connection_start_message_processing(test->listener_conn);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dbus_console_registered(GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
TestDBusConsoleRegister *test = user_data;
|
||||
g_autoptr(GError) err = NULL;
|
||||
|
||||
qemu_dbus_display1_console_call_register_listener_finish(
|
||||
QEMU_DBUS_DISPLAY1_CONSOLE(source_object),
|
||||
NULL, res, &err);
|
||||
g_assert_no_error(err);
|
||||
|
||||
test->listener_conn = g_thread_join(test->thread);
|
||||
test_dbus_console_setup_listener(test);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
test_dbus_p2p_server_setup_thread(gpointer data)
|
||||
{
|
||||
return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data));
|
||||
}
|
||||
|
||||
static void
|
||||
test_dbus_display_console(void)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GDBusConnection) conn = NULL;
|
||||
g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL;
|
||||
g_autoptr(GUnixFDList) fd_list = NULL;
|
||||
g_autoptr(GMainLoop) loop = NULL;
|
||||
QTestState *qts = NULL;
|
||||
int pair[2], idx;
|
||||
TestDBusConsoleRegister test;
|
||||
|
||||
test_setup(&qts, &conn);
|
||||
|
||||
g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
|
||||
fd_list = g_unix_fd_list_new();
|
||||
idx = g_unix_fd_list_append(fd_list, pair[1], NULL);
|
||||
|
||||
console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY(
|
||||
qemu_dbus_display1_console_proxy_new_sync(
|
||||
conn,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL,
|
||||
"/org/qemu/Display1/Console_0",
|
||||
NULL,
|
||||
&err));
|
||||
g_assert_no_error(err);
|
||||
|
||||
test.loop = loop = g_main_loop_new(NULL, FALSE);
|
||||
test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
|
||||
GINT_TO_POINTER(pair[0]));
|
||||
|
||||
qemu_dbus_display1_console_call_register_listener(
|
||||
QEMU_DBUS_DISPLAY1_CONSOLE(console),
|
||||
g_variant_new_handle(idx),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
fd_list,
|
||||
NULL,
|
||||
test_dbus_console_registered,
|
||||
&test);
|
||||
|
||||
g_main_loop_run(loop);
|
||||
|
||||
g_clear_object(&test.server);
|
||||
g_clear_object(&test.listener_conn);
|
||||
qtest_quit(qts);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dbus_display_keyboard(void)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GDBusConnection) conn = NULL;
|
||||
g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL;
|
||||
QTestState *qts = NULL;
|
||||
|
||||
test_setup(&qts, &conn);
|
||||
|
||||
keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY(
|
||||
qemu_dbus_display1_keyboard_proxy_new_sync(
|
||||
conn,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL,
|
||||
"/org/qemu/Display1/Console_0",
|
||||
NULL,
|
||||
&err));
|
||||
g_assert_no_error(err);
|
||||
|
||||
|
||||
g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0);
|
||||
g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0);
|
||||
|
||||
qemu_dbus_display1_keyboard_call_press_sync(
|
||||
QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
|
||||
0x1C, /* qnum enter */
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
&err);
|
||||
g_assert_no_error(err);
|
||||
|
||||
/* may be should wait for interrupt? */
|
||||
g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
|
||||
g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
|
||||
|
||||
qemu_dbus_display1_keyboard_call_release_sync(
|
||||
QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
|
||||
0x1C, /* qnum enter */
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
&err);
|
||||
g_assert_no_error(err);
|
||||
|
||||
g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
|
||||
g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */
|
||||
g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
|
||||
|
||||
g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers(
|
||||
QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0);
|
||||
|
||||
qtest_quit(qts);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
qtest_add_func("/dbus-display/vm", test_dbus_display_vm);
|
||||
qtest_add_func("/dbus-display/console", test_dbus_display_console);
|
||||
qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
|
||||
|
||||
return g_test_run();
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||
<interface name="org.qemu.VMState1">
|
||||
<property name="Id" type="s" access="read"/>
|
||||
<method name="Load">
|
||||
<arg type="ay" name="data" direction="in"/>
|
||||
</method>
|
||||
<method name="Save">
|
||||
<arg type="ay" name="data" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
@ -744,6 +744,16 @@ void qtest_qmp_device_add_qdict(QTestState *qts, const char *drv,
|
||||
void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
|
||||
const char *fmt, ...) GCC_FMT_ATTR(4, 5);
|
||||
|
||||
/**
|
||||
* qtest_qmp_add_client:
|
||||
* @qts: QTestState instance to operate on
|
||||
* @protocol: the protocol to add to
|
||||
* @fd: the client file-descriptor
|
||||
*
|
||||
* Call QMP ``getfd`` followed by ``add_client`` with the given @fd.
|
||||
*/
|
||||
void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd);
|
||||
|
||||
/**
|
||||
* qtest_qmp_device_del:
|
||||
* @qts: QTestState instance to operate on
|
||||
|
@ -1453,6 +1453,25 @@ void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
|
||||
qobject_unref(args);
|
||||
}
|
||||
|
||||
void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd)
|
||||
{
|
||||
QDict *resp;
|
||||
|
||||
resp = qtest_qmp_fds(qts, &fd, 1, "{'execute': 'getfd',"
|
||||
"'arguments': {'fdname': 'fdname'}}");
|
||||
g_assert(resp);
|
||||
g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
|
||||
g_assert(!qdict_haskey(resp, "error"));
|
||||
qobject_unref(resp);
|
||||
|
||||
resp = qtest_qmp(
|
||||
qts, "{'execute': 'add_client',"
|
||||
"'arguments': {'protocol': %s, 'fdname': 'fdname'}}", protocol);
|
||||
g_assert(resp);
|
||||
g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
|
||||
g_assert(!qdict_haskey(resp, "error"));
|
||||
qobject_unref(resp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic hot-unplugging test via the device_del QMP command.
|
||||
|
@ -92,13 +92,17 @@ qtests_i386 = \
|
||||
'test-x86-cpuid-compat',
|
||||
'numa-test']
|
||||
|
||||
if dbus_display
|
||||
qtests_i386 += ['dbus-display-test']
|
||||
endif
|
||||
|
||||
dbus_daemon = find_program('dbus-daemon', required: false)
|
||||
if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
|
||||
# Temporarily disabled due to Patchew failures:
|
||||
#qtests_i386 += ['dbus-vmstate-test']
|
||||
dbus_vmstate1 = custom_target('dbus-vmstate description',
|
||||
output: ['dbus-vmstate1.h', 'dbus-vmstate1.c'],
|
||||
input: files('dbus-vmstate1.xml'),
|
||||
input: meson.source_root() / 'backends/dbus-vmstate1.xml',
|
||||
command: [config_host['GDBUS_CODEGEN'],
|
||||
'@INPUT@',
|
||||
'--interface-prefix', 'org.qemu',
|
||||
@ -265,6 +269,10 @@ qtests = {
|
||||
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
|
||||
}
|
||||
|
||||
if dbus_display
|
||||
qtests += {'dbus-display-test': [dbus_display1, gio]}
|
||||
endif
|
||||
|
||||
qtest_executables = {}
|
||||
foreach dir : target_dirs
|
||||
if not dir.endswith('-softmmu')
|
||||
|
@ -8,7 +8,7 @@ static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||
|
||||
void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
|
||||
{
|
||||
notifier_list_add(&clipboard_notifiers, &peer->update);
|
||||
notifier_list_add(&clipboard_notifiers, &peer->notifier);
|
||||
}
|
||||
|
||||
void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
|
||||
@ -18,8 +18,7 @@ void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
|
||||
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
||||
qemu_clipboard_peer_release(peer, i);
|
||||
}
|
||||
|
||||
notifier_remove(&peer->update);
|
||||
notifier_remove(&peer->notifier);
|
||||
}
|
||||
|
||||
bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
|
||||
@ -42,12 +41,32 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
|
||||
}
|
||||
}
|
||||
|
||||
bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client)
|
||||
{
|
||||
if (!info->has_serial ||
|
||||
!cbinfo[info->selection] ||
|
||||
!cbinfo[info->selection]->has_serial) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (client) {
|
||||
return cbinfo[info->selection]->serial >= info->serial;
|
||||
} else {
|
||||
return cbinfo[info->selection]->serial > info->serial;
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_clipboard_update(QemuClipboardInfo *info)
|
||||
{
|
||||
QemuClipboardNotify notify = {
|
||||
.type = QEMU_CLIPBOARD_UPDATE_INFO,
|
||||
.info = info,
|
||||
};
|
||||
g_autoptr(QemuClipboardInfo) old = NULL;
|
||||
|
||||
assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
|
||||
|
||||
notifier_list_notify(&clipboard_notifiers, info);
|
||||
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||
|
||||
old = cbinfo[info->selection];
|
||||
cbinfo[info->selection] = qemu_clipboard_info_ref(info);
|
||||
@ -110,6 +129,13 @@ void qemu_clipboard_request(QemuClipboardInfo *info,
|
||||
info->owner->request(info, type);
|
||||
}
|
||||
|
||||
void qemu_clipboard_reset_serial(void)
|
||||
{
|
||||
QemuClipboardNotify notify = { .type = QEMU_CLIPBOARD_RESET_SERIAL };
|
||||
|
||||
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||
}
|
||||
|
||||
void qemu_clipboard_set_data(QemuClipboardPeer *peer,
|
||||
QemuClipboardInfo *info,
|
||||
QemuClipboardType type,
|
||||
|
22
ui/cocoa.m
22
ui/cocoa.m
@ -552,7 +552,7 @@ QemuCocoaView *cocoaView;
|
||||
info.width = frameSize.width;
|
||||
info.height = frameSize.height;
|
||||
|
||||
dpy_set_ui_info(dcl.con, &info);
|
||||
dpy_set_ui_info(dcl.con, &info, TRUE);
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
@ -1808,14 +1808,12 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
|
||||
|
||||
static QemuClipboardPeer cbpeer = {
|
||||
.name = "cocoa",
|
||||
.update = { .notify = cocoa_clipboard_notify },
|
||||
.notifier = { .notify = cocoa_clipboard_notify },
|
||||
.request = cocoa_clipboard_request
|
||||
};
|
||||
|
||||
static void cocoa_clipboard_notify(Notifier *notifier, void *data)
|
||||
static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
|
||||
{
|
||||
QemuClipboardInfo *info = data;
|
||||
|
||||
if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
|
||||
return;
|
||||
}
|
||||
@ -1831,6 +1829,20 @@ static void cocoa_clipboard_notify(Notifier *notifier, void *data)
|
||||
qemu_event_set(&cbevent);
|
||||
}
|
||||
|
||||
static void cocoa_clipboard_notify(Notifier *notifier, void *data)
|
||||
{
|
||||
QemuClipboardNotify *notify = data;
|
||||
|
||||
switch (notify->type) {
|
||||
case QEMU_CLIPBOARD_UPDATE_INFO:
|
||||
cocoa_clipboard_update_info(notify->info);
|
||||
return;
|
||||
case QEMU_CLIPBOARD_RESET_SERIAL:
|
||||
/* ignore */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void cocoa_clipboard_request(QemuClipboardInfo *info,
|
||||
QemuClipboardType type)
|
||||
{
|
||||
|
305
ui/console.c
305
ui/console.c
@ -77,9 +77,11 @@ struct QemuConsole {
|
||||
console_type_t console_type;
|
||||
DisplayState *ds;
|
||||
DisplaySurface *surface;
|
||||
DisplayScanout scanout;
|
||||
int dcls;
|
||||
DisplayChangeListener *gl;
|
||||
bool gl_block;
|
||||
DisplayGLCtx *gl;
|
||||
int gl_block;
|
||||
QEMUTimer *gl_unblock_timer;
|
||||
int window_id;
|
||||
|
||||
/* Graphic console state. */
|
||||
@ -145,6 +147,7 @@ static void dpy_refresh(DisplayState *s);
|
||||
static DisplayState *get_alloc_displaystate(void);
|
||||
static void text_console_update_cursor_timer(void);
|
||||
static void text_console_update_cursor(void *opaque);
|
||||
static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
|
||||
|
||||
static void gui_update(void *opaque)
|
||||
{
|
||||
@ -233,22 +236,36 @@ void graphic_hw_update(QemuConsole *con)
|
||||
}
|
||||
}
|
||||
|
||||
void graphic_hw_gl_block(QemuConsole *con, bool block)
|
||||
static void graphic_hw_gl_unblock_timer(void *opaque)
|
||||
{
|
||||
assert(con != NULL);
|
||||
|
||||
con->gl_block = block;
|
||||
if (con->hw_ops->gl_block) {
|
||||
con->hw_ops->gl_block(con->hw, block);
|
||||
}
|
||||
warn_report("console: no gl-unblock within one second");
|
||||
}
|
||||
|
||||
void graphic_hw_gl_flushed(QemuConsole *con)
|
||||
void graphic_hw_gl_block(QemuConsole *con, bool block)
|
||||
{
|
||||
uint64_t timeout;
|
||||
assert(con != NULL);
|
||||
|
||||
if (con->hw_ops->gl_flushed) {
|
||||
con->hw_ops->gl_flushed(con->hw);
|
||||
if (block) {
|
||||
con->gl_block++;
|
||||
} else {
|
||||
con->gl_block--;
|
||||
}
|
||||
assert(con->gl_block >= 0);
|
||||
if (!con->hw_ops->gl_block) {
|
||||
return;
|
||||
}
|
||||
if ((block && con->gl_block != 1) || (!block && con->gl_block != 0)) {
|
||||
return;
|
||||
}
|
||||
con->hw_ops->gl_block(con->hw, block);
|
||||
|
||||
if (block) {
|
||||
timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
||||
timeout += 1000; /* one sec */
|
||||
timer_mod(con->gl_unblock_timer, timeout);
|
||||
} else {
|
||||
timer_del(con->gl_unblock_timer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,6 +483,8 @@ static void text_console_resize(QemuConsole *s)
|
||||
TextCell *cells, *c, *c1;
|
||||
int w1, x, y, last_width;
|
||||
|
||||
assert(s->scanout.kind == SCANOUT_SURFACE);
|
||||
|
||||
last_width = s->width;
|
||||
s->width = surface_width(s->surface) / FONT_WIDTH;
|
||||
s->height = surface_height(s->surface) / FONT_HEIGHT;
|
||||
@ -1037,6 +1056,48 @@ static void console_putchar(QemuConsole *s, int ch)
|
||||
}
|
||||
}
|
||||
|
||||
static void displaychangelistener_display_console(DisplayChangeListener *dcl,
|
||||
QemuConsole *con)
|
||||
{
|
||||
static const char nodev[] =
|
||||
"This VM has no graphic display device.";
|
||||
static DisplaySurface *dummy;
|
||||
|
||||
if (!con) {
|
||||
if (!dcl->ops->dpy_gfx_switch) {
|
||||
return;
|
||||
}
|
||||
if (!dummy) {
|
||||
dummy = qemu_create_placeholder_surface(640, 480, nodev);
|
||||
}
|
||||
dcl->ops->dpy_gfx_switch(dcl, dummy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (con->scanout.kind == SCANOUT_DMABUF &&
|
||||
displaychangelistener_has_dmabuf(dcl)) {
|
||||
dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
|
||||
} else if (con->scanout.kind == SCANOUT_TEXTURE &&
|
||||
dcl->ops->dpy_gl_scanout_texture) {
|
||||
dcl->ops->dpy_gl_scanout_texture(dcl,
|
||||
con->scanout.texture.backing_id,
|
||||
con->scanout.texture.backing_y_0_top,
|
||||
con->scanout.texture.backing_width,
|
||||
con->scanout.texture.backing_height,
|
||||
con->scanout.texture.x,
|
||||
con->scanout.texture.y,
|
||||
con->scanout.texture.width,
|
||||
con->scanout.texture.height);
|
||||
} else if (con->scanout.kind == SCANOUT_SURFACE &&
|
||||
dcl->ops->dpy_gfx_switch) {
|
||||
dcl->ops->dpy_gfx_switch(dcl, con->surface);
|
||||
}
|
||||
|
||||
dcl->ops->dpy_gfx_update(dcl, 0, 0,
|
||||
qemu_console_get_width(con, 0),
|
||||
qemu_console_get_height(con, 0));
|
||||
}
|
||||
|
||||
void console_select(unsigned int index)
|
||||
{
|
||||
DisplayChangeListener *dcl;
|
||||
@ -1053,13 +1114,7 @@ void console_select(unsigned int index)
|
||||
if (dcl->con != NULL) {
|
||||
continue;
|
||||
}
|
||||
if (dcl->ops->dpy_gfx_switch) {
|
||||
dcl->ops->dpy_gfx_switch(dcl, s->surface);
|
||||
}
|
||||
}
|
||||
if (s->surface) {
|
||||
dpy_gfx_update(s, 0, 0, surface_width(s->surface),
|
||||
surface_height(s->surface));
|
||||
displaychangelistener_display_console(dcl, s);
|
||||
}
|
||||
}
|
||||
if (ds->have_text) {
|
||||
@ -1443,24 +1498,36 @@ static bool dpy_compatible_with(QemuConsole *con,
|
||||
return true;
|
||||
}
|
||||
|
||||
void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl)
|
||||
{
|
||||
/* display has opengl support */
|
||||
assert(con);
|
||||
if (con->gl) {
|
||||
error_report("The console already has an OpenGL context.");
|
||||
exit(1);
|
||||
}
|
||||
con->gl = gl;
|
||||
}
|
||||
|
||||
static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
|
||||
{
|
||||
if (!con->gl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return con->gl->ops->compatible_dcl == dcl->ops;
|
||||
}
|
||||
|
||||
void register_displaychangelistener(DisplayChangeListener *dcl)
|
||||
{
|
||||
static const char nodev[] =
|
||||
"This VM has no graphic display device.";
|
||||
static DisplaySurface *dummy;
|
||||
QemuConsole *con;
|
||||
|
||||
assert(!dcl->ds);
|
||||
|
||||
if (dcl->ops->dpy_gl_ctx_create) {
|
||||
/* display has opengl support */
|
||||
assert(dcl->con);
|
||||
if (dcl->con->gl) {
|
||||
fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
|
||||
dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
|
||||
exit(1);
|
||||
}
|
||||
dcl->con->gl = dcl;
|
||||
if (dcl->con && !dpy_gl_compatible_with(dcl->con, dcl)) {
|
||||
error_report("Display %s is incompatible with the GL context",
|
||||
dcl->ops->dpy_name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (dcl->con) {
|
||||
@ -1477,16 +1544,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
|
||||
} else {
|
||||
con = active_console;
|
||||
}
|
||||
if (dcl->ops->dpy_gfx_switch) {
|
||||
if (con) {
|
||||
dcl->ops->dpy_gfx_switch(dcl, con->surface);
|
||||
} else {
|
||||
if (!dummy) {
|
||||
dummy = qemu_create_placeholder_surface(640, 480, nodev);
|
||||
}
|
||||
dcl->ops->dpy_gfx_switch(dcl, dummy);
|
||||
}
|
||||
}
|
||||
displaychangelistener_display_console(dcl, con);
|
||||
text_console_update_cursor(NULL);
|
||||
}
|
||||
|
||||
@ -1538,7 +1596,7 @@ const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
|
||||
return &con->ui_info;
|
||||
}
|
||||
|
||||
int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
|
||||
int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay)
|
||||
{
|
||||
if (con == NULL) {
|
||||
con = active_console;
|
||||
@ -1558,7 +1616,8 @@ int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
|
||||
* go notify the guest.
|
||||
*/
|
||||
con->ui_info = *info;
|
||||
timer_mod(con->ui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
|
||||
timer_mod(con->ui_timer,
|
||||
qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + (delay ? 1000 : 0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1566,13 +1625,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
|
||||
{
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
int width = w;
|
||||
int height = h;
|
||||
int width = qemu_console_get_width(con, x + w);
|
||||
int height = qemu_console_get_height(con, y + h);
|
||||
|
||||
if (con->surface) {
|
||||
width = surface_width(con->surface);
|
||||
height = surface_height(con->surface);
|
||||
}
|
||||
x = MAX(x, 0);
|
||||
y = MAX(y, 0);
|
||||
x = MIN(x, width);
|
||||
@ -1595,12 +1650,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
|
||||
|
||||
void dpy_gfx_update_full(QemuConsole *con)
|
||||
{
|
||||
if (!con->surface) {
|
||||
return;
|
||||
}
|
||||
dpy_gfx_update(con, 0, 0,
|
||||
surface_width(con->surface),
|
||||
surface_height(con->surface));
|
||||
int w = qemu_console_get_width(con, 0);
|
||||
int h = qemu_console_get_height(con, 0);
|
||||
|
||||
dpy_gfx_update(con, 0, 0, w, h);
|
||||
}
|
||||
|
||||
void dpy_gfx_replace_surface(QemuConsole *con,
|
||||
@ -1627,6 +1680,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
|
||||
|
||||
assert(old_surface != surface);
|
||||
|
||||
con->scanout.kind = SCANOUT_SURFACE;
|
||||
con->surface = surface;
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
if (con != (dcl->con ? dcl->con : active_console)) {
|
||||
@ -1799,8 +1853,15 @@ int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx)
|
||||
|
||||
void dpy_gl_scanout_disable(QemuConsole *con)
|
||||
{
|
||||
assert(con->gl);
|
||||
con->gl->ops->dpy_gl_scanout_disable(con->gl);
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
if (con->scanout.kind != SCANOUT_SURFACE) {
|
||||
con->scanout.kind = SCANOUT_NONE;
|
||||
}
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
dcl->ops->dpy_gl_scanout_disable(dcl);
|
||||
}
|
||||
}
|
||||
|
||||
void dpy_gl_scanout_texture(QemuConsole *con,
|
||||
@ -1811,56 +1872,88 @@ void dpy_gl_scanout_texture(QemuConsole *con,
|
||||
uint32_t x, uint32_t y,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
assert(con->gl);
|
||||
con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id,
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
con->scanout.kind = SCANOUT_TEXTURE;
|
||||
con->scanout.texture = (ScanoutTexture) {
|
||||
backing_id, backing_y_0_top, backing_width, backing_height,
|
||||
x, y, width, height
|
||||
};
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
|
||||
backing_y_0_top,
|
||||
backing_width, backing_height,
|
||||
x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void dpy_gl_scanout_dmabuf(QemuConsole *con,
|
||||
QemuDmaBuf *dmabuf)
|
||||
{
|
||||
assert(con->gl);
|
||||
con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf);
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
con->scanout.kind = SCANOUT_DMABUF;
|
||||
con->scanout.dmabuf = dmabuf;
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
|
||||
}
|
||||
}
|
||||
|
||||
void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
|
||||
bool have_hot, uint32_t hot_x, uint32_t hot_y)
|
||||
{
|
||||
assert(con->gl);
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
if (con->gl->ops->dpy_gl_cursor_dmabuf) {
|
||||
con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf,
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
if (dcl->ops->dpy_gl_cursor_dmabuf) {
|
||||
dcl->ops->dpy_gl_cursor_dmabuf(dcl, dmabuf,
|
||||
have_hot, hot_x, hot_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dpy_gl_cursor_position(QemuConsole *con,
|
||||
uint32_t pos_x, uint32_t pos_y)
|
||||
{
|
||||
assert(con->gl);
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
if (con->gl->ops->dpy_gl_cursor_position) {
|
||||
con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y);
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
if (dcl->ops->dpy_gl_cursor_position) {
|
||||
dcl->ops->dpy_gl_cursor_position(dcl, pos_x, pos_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dpy_gl_release_dmabuf(QemuConsole *con,
|
||||
QemuDmaBuf *dmabuf)
|
||||
{
|
||||
assert(con->gl);
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
if (con->gl->ops->dpy_gl_release_dmabuf) {
|
||||
con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf);
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
if (dcl->ops->dpy_gl_release_dmabuf) {
|
||||
dcl->ops->dpy_gl_release_dmabuf(dcl, dmabuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dpy_gl_update(QemuConsole *con,
|
||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
|
||||
{
|
||||
DisplayState *s = con->ds;
|
||||
DisplayChangeListener *dcl;
|
||||
|
||||
assert(con->gl);
|
||||
con->gl->ops->dpy_gl_update(con->gl, x, y, w, h);
|
||||
|
||||
graphic_hw_gl_block(con, true);
|
||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||
dcl->ops->dpy_gl_update(dcl, x, y, w, h);
|
||||
}
|
||||
graphic_hw_gl_block(con, false);
|
||||
}
|
||||
|
||||
/***********************************************************/
|
||||
@ -1929,10 +2022,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
|
||||
s = qemu_console_lookup_unused();
|
||||
if (s) {
|
||||
trace_console_gfx_reuse(s->index);
|
||||
if (s->surface) {
|
||||
width = surface_width(s->surface);
|
||||
height = surface_height(s->surface);
|
||||
}
|
||||
width = qemu_console_get_width(s, 0);
|
||||
height = qemu_console_get_height(s, 0);
|
||||
} else {
|
||||
trace_console_gfx_new();
|
||||
s = new_console(ds, GRAPHIC_CONSOLE, head);
|
||||
@ -1947,6 +2038,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
|
||||
|
||||
surface = qemu_create_placeholder_surface(width, height, noinit);
|
||||
dpy_gfx_replace_surface(s, surface);
|
||||
s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
||||
graphic_hw_gl_unblock_timer, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -1959,13 +2052,8 @@ void graphic_console_close(QemuConsole *con)
|
||||
static const char unplugged[] =
|
||||
"Guest display has been unplugged";
|
||||
DisplaySurface *surface;
|
||||
int width = 640;
|
||||
int height = 480;
|
||||
|
||||
if (con->surface) {
|
||||
width = surface_width(con->surface);
|
||||
height = surface_height(con->surface);
|
||||
}
|
||||
int width = qemu_console_get_width(con, 640);
|
||||
int height = qemu_console_get_height(con, 480);
|
||||
|
||||
trace_console_gfx_close(con->index);
|
||||
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
|
||||
@ -2117,7 +2205,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
|
||||
if (con == NULL) {
|
||||
con = active_console;
|
||||
}
|
||||
return con ? surface_width(con->surface) : fallback;
|
||||
if (con == NULL) {
|
||||
return fallback;
|
||||
}
|
||||
switch (con->scanout.kind) {
|
||||
case SCANOUT_DMABUF:
|
||||
return con->scanout.dmabuf->width;
|
||||
case SCANOUT_TEXTURE:
|
||||
return con->scanout.texture.width;
|
||||
case SCANOUT_SURFACE:
|
||||
return surface_width(con->surface);
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
int qemu_console_get_height(QemuConsole *con, int fallback)
|
||||
@ -2125,7 +2225,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
|
||||
if (con == NULL) {
|
||||
con = active_console;
|
||||
}
|
||||
return con ? surface_height(con->surface) : fallback;
|
||||
if (con == NULL) {
|
||||
return fallback;
|
||||
}
|
||||
switch (con->scanout.kind) {
|
||||
case SCANOUT_DMABUF:
|
||||
return con->scanout.dmabuf->height;
|
||||
case SCANOUT_TEXTURE:
|
||||
return con->scanout.texture.height;
|
||||
case SCANOUT_SURFACE:
|
||||
return surface_height(con->surface);
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
static void vc_chr_accept_input(Chardev *chr)
|
||||
@ -2191,12 +2303,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
|
||||
s->total_height = DEFAULT_BACKSCROLL;
|
||||
s->x = 0;
|
||||
s->y = 0;
|
||||
if (!s->surface) {
|
||||
if (active_console && active_console->surface) {
|
||||
g_width = surface_width(active_console->surface);
|
||||
g_height = surface_height(active_console->surface);
|
||||
if (s->scanout.kind != SCANOUT_SURFACE) {
|
||||
if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
|
||||
g_width = qemu_console_get_width(active_console, g_width);
|
||||
g_height = qemu_console_get_height(active_console, g_height);
|
||||
}
|
||||
s->surface = qemu_create_displaysurface(g_width, g_height);
|
||||
s->scanout.kind = SCANOUT_SURFACE;
|
||||
}
|
||||
|
||||
s->hw_ops = &text_console_ops;
|
||||
@ -2255,6 +2368,7 @@ static void vc_chr_open(Chardev *chr,
|
||||
s = new_console(NULL, TEXT_CONSOLE, 0);
|
||||
} else {
|
||||
s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
|
||||
s->scanout.kind = SCANOUT_SURFACE;
|
||||
s->surface = qemu_create_displaysurface(width, height);
|
||||
}
|
||||
|
||||
@ -2278,13 +2392,13 @@ static void vc_chr_open(Chardev *chr,
|
||||
|
||||
void qemu_console_resize(QemuConsole *s, int width, int height)
|
||||
{
|
||||
DisplaySurface *surface;
|
||||
DisplaySurface *surface = qemu_console_surface(s);
|
||||
|
||||
assert(s->console_type == GRAPHIC_CONSOLE);
|
||||
|
||||
if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
|
||||
pixman_image_get_width(s->surface->image) == width &&
|
||||
pixman_image_get_height(s->surface->image) == height) {
|
||||
if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
|
||||
pixman_image_get_width(surface->image) == width &&
|
||||
pixman_image_get_height(surface->image) == height) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2294,7 +2408,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
|
||||
|
||||
DisplaySurface *qemu_console_surface(QemuConsole *console)
|
||||
{
|
||||
return console->surface;
|
||||
switch (console->scanout.kind) {
|
||||
case SCANOUT_SURFACE:
|
||||
return console->surface;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PixelFormat qemu_default_pixelformat(int bpp)
|
||||
|
296
ui/dbus-chardev.c
Normal file
296
ui/dbus-chardev.c
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* QEMU DBus display
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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 "trace.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/config-file.h"
|
||||
#include "qemu/option.h"
|
||||
|
||||
#include <gio/gunixfdlist.h>
|
||||
|
||||
#include "dbus.h"
|
||||
|
||||
static char *
|
||||
dbus_display_chardev_path(DBusChardev *chr)
|
||||
{
|
||||
return g_strdup_printf(DBUS_DISPLAY1_ROOT "/Chardev_%s",
|
||||
CHARDEV(chr)->label);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_chardev_export(DBusDisplay *dpy, DBusChardev *chr)
|
||||
{
|
||||
g_autoptr(GDBusObjectSkeleton) sk = NULL;
|
||||
g_autofree char *path = dbus_display_chardev_path(chr);
|
||||
|
||||
if (chr->exported) {
|
||||
return;
|
||||
}
|
||||
|
||||
sk = g_dbus_object_skeleton_new(path);
|
||||
g_dbus_object_skeleton_add_interface(
|
||||
sk, G_DBUS_INTERFACE_SKELETON(chr->iface));
|
||||
g_dbus_object_manager_server_export(dpy->server, sk);
|
||||
chr->exported = true;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_chardev_unexport(DBusDisplay *dpy, DBusChardev *chr)
|
||||
{
|
||||
g_autofree char *path = dbus_display_chardev_path(chr);
|
||||
|
||||
if (!chr->exported) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_dbus_object_manager_server_unexport(dpy->server, path);
|
||||
chr->exported = false;
|
||||
}
|
||||
|
||||
static int
|
||||
dbus_display_chardev_foreach(Object *obj, void *data)
|
||||
{
|
||||
DBusDisplay *dpy = DBUS_DISPLAY(data);
|
||||
|
||||
if (!CHARDEV_IS_DBUS(obj)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
dbus_display_chardev_export(dpy, DBUS_CHARDEV(obj));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_on_notify(Notifier *notifier, void *data)
|
||||
{
|
||||
DBusDisplay *dpy = container_of(notifier, DBusDisplay, notifier);
|
||||
DBusDisplayEvent *event = data;
|
||||
|
||||
switch (event->type) {
|
||||
case DBUS_DISPLAY_CHARDEV_OPEN:
|
||||
dbus_display_chardev_export(dpy, event->chardev);
|
||||
break;
|
||||
case DBUS_DISPLAY_CHARDEV_CLOSE:
|
||||
dbus_display_chardev_unexport(dpy, event->chardev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dbus_chardev_init(DBusDisplay *dpy)
|
||||
{
|
||||
dpy->notifier.notify = dbus_display_on_notify;
|
||||
dbus_display_notifier_add(&dpy->notifier);
|
||||
|
||||
object_child_foreach(container_get(object_get_root(), "/chardevs"),
|
||||
dbus_display_chardev_foreach, dpy);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_chr_register(
|
||||
DBusChardev *dc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
GUnixFDList *fd_list,
|
||||
GVariant *arg_stream,
|
||||
QemuDBusDisplay1Chardev *object)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
int fd;
|
||||
|
||||
fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_stream), &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;
|
||||
}
|
||||
|
||||
if (qemu_chr_add_client(CHARDEV(dc), fd) < 0) {
|
||||
g_dbus_method_invocation_return_error(invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Couldn't register FD!");
|
||||
close(fd);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
g_object_set(dc->iface,
|
||||
"owner", g_dbus_method_invocation_get_sender(invocation),
|
||||
NULL);
|
||||
|
||||
qemu_dbus_display1_chardev_complete_register(object, invocation, NULL);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_chr_send_break(
|
||||
DBusChardev *dc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
QemuDBusDisplay1Chardev *object)
|
||||
{
|
||||
qemu_chr_be_event(CHARDEV(dc), CHR_EVENT_BREAK);
|
||||
|
||||
qemu_dbus_display1_chardev_complete_send_break(object, invocation);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_chr_open(Chardev *chr, ChardevBackend *backend,
|
||||
bool *be_opened, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
|
||||
DBusChardev *dc = DBUS_CHARDEV(chr);
|
||||
DBusDisplayEvent event = {
|
||||
.type = DBUS_DISPLAY_CHARDEV_OPEN,
|
||||
.chardev = dc,
|
||||
};
|
||||
g_autoptr(ChardevBackend) be = NULL;
|
||||
g_autoptr(QemuOpts) opts = NULL;
|
||||
|
||||
dc->iface = qemu_dbus_display1_chardev_skeleton_new();
|
||||
g_object_set(dc->iface, "name", backend->u.dbus.data->name, NULL);
|
||||
g_object_connect(dc->iface,
|
||||
"swapped-signal::handle-register",
|
||||
dbus_chr_register, dc,
|
||||
"swapped-signal::handle-send-break",
|
||||
dbus_chr_send_break, dc,
|
||||
NULL);
|
||||
|
||||
dbus_display_notify(&event);
|
||||
|
||||
be = g_new0(ChardevBackend, 1);
|
||||
opts = qemu_opts_create(qemu_find_opts("chardev"), NULL, 0, &error_abort);
|
||||
qemu_opt_set(opts, "server", "on", &error_abort);
|
||||
qemu_opt_set(opts, "wait", "off", &error_abort);
|
||||
CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->parse(
|
||||
opts, be, errp);
|
||||
if (*errp) {
|
||||
return;
|
||||
}
|
||||
CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->open(
|
||||
chr, be, be_opened, errp);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_chr_set_fe_open(Chardev *chr, int fe_open)
|
||||
{
|
||||
DBusChardev *dc = DBUS_CHARDEV(chr);
|
||||
|
||||
g_object_set(dc->iface, "feopened", fe_open, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_chr_set_echo(Chardev *chr, bool echo)
|
||||
{
|
||||
DBusChardev *dc = DBUS_CHARDEV(chr);
|
||||
|
||||
g_object_set(dc->iface, "echo", echo, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_chr_be_event(Chardev *chr, QEMUChrEvent event)
|
||||
{
|
||||
DBusChardev *dc = DBUS_CHARDEV(chr);
|
||||
DBusChardevClass *klass = DBUS_CHARDEV_GET_CLASS(chr);
|
||||
|
||||
switch (event) {
|
||||
case CHR_EVENT_CLOSED:
|
||||
if (dc->iface) {
|
||||
/* on finalize, iface is set to NULL */
|
||||
g_object_set(dc->iface, "owner", "", NULL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
klass->parent_chr_be_event(chr, event);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_chr_parse(QemuOpts *opts, ChardevBackend *backend,
|
||||
Error **errp)
|
||||
{
|
||||
const char *name = qemu_opt_get(opts, "name");
|
||||
ChardevDBus *dbus;
|
||||
|
||||
if (name == NULL) {
|
||||
error_setg(errp, "chardev: dbus: no name given");
|
||||
return;
|
||||
}
|
||||
|
||||
backend->type = CHARDEV_BACKEND_KIND_DBUS;
|
||||
dbus = backend->u.dbus.data = g_new0(ChardevDBus, 1);
|
||||
qemu_chr_parse_common(opts, qapi_ChardevDBus_base(dbus));
|
||||
dbus->name = g_strdup(name);
|
||||
}
|
||||
|
||||
static void
|
||||
char_dbus_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DBusChardevClass *klass = DBUS_CHARDEV_CLASS(oc);
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
|
||||
cc->parse = dbus_chr_parse;
|
||||
cc->open = dbus_chr_open;
|
||||
cc->chr_set_fe_open = dbus_chr_set_fe_open;
|
||||
cc->chr_set_echo = dbus_chr_set_echo;
|
||||
klass->parent_chr_be_event = cc->chr_be_event;
|
||||
cc->chr_be_event = dbus_chr_be_event;
|
||||
}
|
||||
|
||||
static void
|
||||
char_dbus_finalize(Object *obj)
|
||||
{
|
||||
DBusChardev *dc = DBUS_CHARDEV(obj);
|
||||
DBusDisplayEvent event = {
|
||||
.type = DBUS_DISPLAY_CHARDEV_CLOSE,
|
||||
.chardev = dc,
|
||||
};
|
||||
|
||||
dbus_display_notify(&event);
|
||||
g_clear_object(&dc->iface);
|
||||
}
|
||||
|
||||
static const TypeInfo char_dbus_type_info = {
|
||||
.name = TYPE_CHARDEV_DBUS,
|
||||
.parent = TYPE_CHARDEV_SOCKET,
|
||||
.class_size = sizeof(DBusChardevClass),
|
||||
.instance_size = sizeof(DBusChardev),
|
||||
.instance_finalize = char_dbus_finalize,
|
||||
.class_init = char_dbus_class_init,
|
||||
};
|
||||
module_obj(TYPE_CHARDEV_DBUS);
|
||||
|
||||
static void
|
||||
register_types(void)
|
||||
{
|
||||
type_register_static(&char_dbus_type_info);
|
||||
}
|
||||
|
||||
type_init(register_types);
|
457
ui/dbus-clipboard.c
Normal file
457
ui/dbus-clipboard.c
Normal file
@ -0,0 +1,457 @@
|
||||
/*
|
||||
* QEMU DBus display
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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/dbus.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "dbus.h"
|
||||
|
||||
#define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
|
||||
|
||||
static void
|
||||
dbus_clipboard_complete_request(
|
||||
DBusDisplay *dpy,
|
||||
GDBusMethodInvocation *invocation,
|
||||
QemuClipboardInfo *info,
|
||||
QemuClipboardType type)
|
||||
{
|
||||
GVariant *v_data = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("ay"),
|
||||
info->types[type].data,
|
||||
info->types[type].size,
|
||||
TRUE,
|
||||
(GDestroyNotify)qemu_clipboard_info_unref,
|
||||
qemu_clipboard_info_ref(info));
|
||||
|
||||
qemu_dbus_display1_clipboard_complete_request(
|
||||
dpy->clipboard, invocation,
|
||||
MIME_TEXT_PLAIN_UTF8, v_data);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
|
||||
{
|
||||
bool self_update = info->owner == &dpy->clipboard_peer;
|
||||
const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
|
||||
DBusClipboardRequest *req;
|
||||
int i = 0;
|
||||
|
||||
if (info->owner == NULL) {
|
||||
if (dpy->clipboard_proxy) {
|
||||
qemu_dbus_display1_clipboard_call_release(
|
||||
dpy->clipboard_proxy,
|
||||
info->selection,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self_update || !info->has_serial) {
|
||||
return;
|
||||
}
|
||||
|
||||
req = &dpy->clipboard_request[info->selection];
|
||||
if (req->invocation && info->types[req->type].data) {
|
||||
dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
|
||||
g_clear_object(&req->invocation);
|
||||
g_source_remove(req->timeout_id);
|
||||
req->timeout_id = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
|
||||
mime[i++] = MIME_TEXT_PLAIN_UTF8;
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
if (dpy->clipboard_proxy) {
|
||||
qemu_dbus_display1_clipboard_call_grab(
|
||||
dpy->clipboard_proxy,
|
||||
info->selection,
|
||||
info->serial,
|
||||
mime,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_clipboard_reset_serial(DBusDisplay *dpy)
|
||||
{
|
||||
if (dpy->clipboard_proxy) {
|
||||
qemu_dbus_display1_clipboard_call_register(
|
||||
dpy->clipboard_proxy,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_clipboard_notify(Notifier *notifier, void *data)
|
||||
{
|
||||
DBusDisplay *dpy =
|
||||
container_of(notifier, DBusDisplay, clipboard_peer.notifier);
|
||||
QemuClipboardNotify *notify = data;
|
||||
|
||||
switch (notify->type) {
|
||||
case QEMU_CLIPBOARD_UPDATE_INFO:
|
||||
dbus_clipboard_update_info(dpy, notify->info);
|
||||
return;
|
||||
case QEMU_CLIPBOARD_RESET_SERIAL:
|
||||
dbus_clipboard_reset_serial(dpy);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_clipboard_qemu_request(QemuClipboardInfo *info,
|
||||
QemuClipboardType type)
|
||||
{
|
||||
DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer);
|
||||
g_autofree char *mime = NULL;
|
||||
g_autoptr(GVariant) v_data = NULL;
|
||||
g_autoptr(GError) err = NULL;
|
||||
const char *data = NULL;
|
||||
const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
|
||||
size_t n;
|
||||
|
||||
if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
|
||||
/* unsupported atm */
|
||||
return;
|
||||
}
|
||||
|
||||
if (dpy->clipboard_proxy) {
|
||||
if (!qemu_dbus_display1_clipboard_call_request_sync(
|
||||
dpy->clipboard_proxy,
|
||||
info->selection,
|
||||
mimes,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
|
||||
error_report("Failed to request clipboard: %s", err->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
|
||||
error_report("Unsupported returned MIME: %s", mime);
|
||||
return;
|
||||
}
|
||||
|
||||
data = g_variant_get_fixed_array(v_data, &n, 1);
|
||||
qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
|
||||
n, data, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
|
||||
{
|
||||
if (!req->invocation) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_dbus_method_invocation_return_error(
|
||||
req->invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Cancelled clipboard request");
|
||||
|
||||
g_clear_object(&req->invocation);
|
||||
g_source_remove(req->timeout_id);
|
||||
req->timeout_id = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
|
||||
{
|
||||
const char *name = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
|
||||
dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
|
||||
}
|
||||
|
||||
if (!dpy->clipboard_proxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
|
||||
trace_dbus_clipboard_unregister(name);
|
||||
g_clear_object(&dpy->clipboard_proxy);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_on_clipboard_proxy_name_owner_changed(
|
||||
DBusDisplay *dpy,
|
||||
GObject *object,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
dbus_clipboard_unregister_proxy(dpy);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_register(
|
||||
DBusDisplay *dpy,
|
||||
GDBusMethodInvocation *invocation)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
const char *name = NULL;
|
||||
|
||||
if (dpy->clipboard_proxy) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Clipboard peer already registered!");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
dpy->clipboard_proxy =
|
||||
qemu_dbus_display1_clipboard_proxy_new_sync(
|
||||
g_dbus_method_invocation_get_connection(invocation),
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
||||
g_dbus_method_invocation_get_sender(invocation),
|
||||
"/org/qemu/Display1/Clipboard",
|
||||
NULL,
|
||||
&err);
|
||||
if (!dpy->clipboard_proxy) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Failed to setup proxy: %s", err->message);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
|
||||
trace_dbus_clipboard_register(name);
|
||||
|
||||
g_object_connect(dpy->clipboard_proxy,
|
||||
"swapped-signal::notify::g-name-owner",
|
||||
dbus_on_clipboard_proxy_name_owner_changed, dpy,
|
||||
NULL);
|
||||
qemu_clipboard_reset_serial();
|
||||
|
||||
qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
|
||||
{
|
||||
if (!dpy->clipboard_proxy ||
|
||||
g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
|
||||
g_dbus_method_invocation_get_sender(invocation))) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Unregistered caller");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_unregister(
|
||||
DBusDisplay *dpy,
|
||||
GDBusMethodInvocation *invocation)
|
||||
{
|
||||
if (!dbus_clipboard_check_caller(dpy, invocation)) {
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
dbus_clipboard_unregister_proxy(dpy);
|
||||
|
||||
qemu_dbus_display1_clipboard_complete_unregister(
|
||||
dpy->clipboard, invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_grab(
|
||||
DBusDisplay *dpy,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gint arg_selection,
|
||||
guint arg_serial,
|
||||
const gchar *const *arg_mimes)
|
||||
{
|
||||
QemuClipboardSelection s = arg_selection;
|
||||
g_autoptr(QemuClipboardInfo) info = NULL;
|
||||
|
||||
if (!dbus_clipboard_check_caller(dpy, invocation)) {
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Invalid clipboard selection: %d", arg_selection);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
|
||||
if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
|
||||
info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
|
||||
}
|
||||
info->serial = arg_serial;
|
||||
info->has_serial = true;
|
||||
if (qemu_clipboard_check_serial(info, true)) {
|
||||
qemu_clipboard_update(info);
|
||||
} else {
|
||||
trace_dbus_clipboard_grab_failed();
|
||||
}
|
||||
|
||||
qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_release(
|
||||
DBusDisplay *dpy,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gint arg_selection)
|
||||
{
|
||||
if (!dbus_clipboard_check_caller(dpy, invocation)) {
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
|
||||
|
||||
qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_request_timeout(gpointer user_data)
|
||||
{
|
||||
dbus_clipboard_request_cancelled(user_data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_clipboard_request(
|
||||
DBusDisplay *dpy,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gint arg_selection,
|
||||
const gchar *const *arg_mimes)
|
||||
{
|
||||
QemuClipboardSelection s = arg_selection;
|
||||
QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
|
||||
QemuClipboardInfo *info = NULL;
|
||||
|
||||
if (!dbus_clipboard_check_caller(dpy, invocation)) {
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Invalid clipboard selection: %d", arg_selection);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
if (dpy->clipboard_request[s].invocation) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Pending request");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
info = qemu_clipboard_info(s);
|
||||
if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Empty clipboard");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
|
||||
!info->types[type].available) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_FAILED,
|
||||
"Unhandled MIME types requested");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
if (info->types[type].data) {
|
||||
dbus_clipboard_complete_request(dpy, invocation, info, type);
|
||||
} else {
|
||||
qemu_clipboard_request(info, type);
|
||||
|
||||
dpy->clipboard_request[s].invocation = g_object_ref(invocation);
|
||||
dpy->clipboard_request[s].type = type;
|
||||
dpy->clipboard_request[s].timeout_id =
|
||||
g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
|
||||
&dpy->clipboard_request[s]);
|
||||
}
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
void
|
||||
dbus_clipboard_init(DBusDisplay *dpy)
|
||||
{
|
||||
g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
|
||||
|
||||
assert(!dpy->clipboard);
|
||||
|
||||
clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
|
||||
dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
|
||||
g_object_connect(dpy->clipboard,
|
||||
"swapped-signal::handle-register",
|
||||
dbus_clipboard_register, dpy,
|
||||
"swapped-signal::handle-unregister",
|
||||
dbus_clipboard_unregister, dpy,
|
||||
"swapped-signal::handle-grab",
|
||||
dbus_clipboard_grab, dpy,
|
||||
"swapped-signal::handle-release",
|
||||
dbus_clipboard_release, dpy,
|
||||
"swapped-signal::handle-request",
|
||||
dbus_clipboard_request, dpy,
|
||||
NULL);
|
||||
|
||||
g_dbus_object_skeleton_add_interface(
|
||||
G_DBUS_OBJECT_SKELETON(clipboard),
|
||||
G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
|
||||
g_dbus_object_manager_server_export(dpy->server, clipboard);
|
||||
dpy->clipboard_peer.name = "dbus";
|
||||
dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
|
||||
dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
|
||||
qemu_clipboard_peer_register(&dpy->clipboard_peer);
|
||||
}
|
497
ui/dbus-console.c
Normal file
497
ui/dbus-console.c
Normal file
@ -0,0 +1,497 @@
|
||||
/*
|
||||
* QEMU DBus display console
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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 "qapi/error.h"
|
||||
#include "ui/input.h"
|
||||
#include "ui/kbd-state.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include <gio/gunixfdlist.h>
|
||||
|
||||
#include "dbus.h"
|
||||
|
||||
struct _DBusDisplayConsole {
|
||||
GDBusObjectSkeleton parent_instance;
|
||||
DisplayChangeListener dcl;
|
||||
|
||||
DBusDisplay *display;
|
||||
QemuConsole *con;
|
||||
GHashTable *listeners;
|
||||
QemuDBusDisplay1Console *iface;
|
||||
|
||||
QemuDBusDisplay1Keyboard *iface_kbd;
|
||||
QKbdState *kbd;
|
||||
|
||||
QemuDBusDisplay1Mouse *iface_mouse;
|
||||
gboolean last_set;
|
||||
guint last_x;
|
||||
guint last_y;
|
||||
Notifier mouse_mode_notifier;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(DBusDisplayConsole,
|
||||
dbus_display_console,
|
||||
G_TYPE_DBUS_OBJECT_SKELETON)
|
||||
|
||||
static void
|
||||
dbus_display_console_set_size(DBusDisplayConsole *ddc,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
g_object_set(ddc->iface,
|
||||
"width", width,
|
||||
"height", height,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_gfx_switch(DisplayChangeListener *dcl,
|
||||
struct DisplaySurface *new_surface)
|
||||
{
|
||||
DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
|
||||
|
||||
dbus_display_console_set_size(ddc,
|
||||
surface_width(new_surface),
|
||||
surface_height(new_surface));
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_gfx_update(DisplayChangeListener *dcl,
|
||||
int x, int y, int w, int h)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_gl_scanout_disable(DisplayChangeListener *dcl)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_gl_scanout_texture(DisplayChangeListener *dcl,
|
||||
uint32_t tex_id,
|
||||
bool backing_y_0_top,
|
||||
uint32_t backing_width,
|
||||
uint32_t backing_height,
|
||||
uint32_t x, uint32_t y,
|
||||
uint32_t w, uint32_t h)
|
||||
{
|
||||
DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
|
||||
|
||||
dbus_display_console_set_size(ddc, w, h);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
|
||||
QemuDmaBuf *dmabuf)
|
||||
{
|
||||
DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
|
||||
|
||||
dbus_display_console_set_size(ddc,
|
||||
dmabuf->width,
|
||||
dmabuf->height);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_gl_scanout_update(DisplayChangeListener *dcl,
|
||||
uint32_t x, uint32_t y,
|
||||
uint32_t w, uint32_t h)
|
||||
{
|
||||
}
|
||||
|
||||
static const DisplayChangeListenerOps dbus_console_dcl_ops = {
|
||||
.dpy_name = "dbus-console",
|
||||
.dpy_gfx_switch = dbus_gfx_switch,
|
||||
.dpy_gfx_update = dbus_gfx_update,
|
||||
.dpy_gl_scanout_disable = dbus_gl_scanout_disable,
|
||||
.dpy_gl_scanout_texture = dbus_gl_scanout_texture,
|
||||
.dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf,
|
||||
.dpy_gl_update = dbus_gl_scanout_update,
|
||||
};
|
||||
|
||||
static void
|
||||
dbus_display_console_init(DBusDisplayConsole *object)
|
||||
{
|
||||
DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
|
||||
|
||||
ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
NULL, g_object_unref);
|
||||
ddc->dcl.ops = &dbus_console_dcl_ops;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_console_dispose(GObject *object)
|
||||
{
|
||||
DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
|
||||
|
||||
unregister_displaychangelistener(&ddc->dcl);
|
||||
g_clear_object(&ddc->iface_kbd);
|
||||
g_clear_object(&ddc->iface);
|
||||
g_clear_pointer(&ddc->listeners, g_hash_table_unref);
|
||||
g_clear_pointer(&ddc->kbd, qkbd_state_free);
|
||||
|
||||
G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||
|
||||
gobject_class->dispose = dbus_display_console_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
listener_vanished_cb(DBusDisplayListener *listener)
|
||||
{
|
||||
DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
|
||||
const char *name = dbus_display_listener_get_bus_name(listener);
|
||||
|
||||
trace_dbus_listener_vanished(name);
|
||||
|
||||
g_hash_table_remove(ddc->listeners, name);
|
||||
qkbd_state_lift_all_keys(ddc->kbd);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_console_set_ui_info(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint16 arg_width_mm,
|
||||
guint16 arg_height_mm,
|
||||
gint arg_xoff,
|
||||
gint arg_yoff,
|
||||
guint arg_width,
|
||||
guint arg_height)
|
||||
{
|
||||
QemuUIInfo info = {
|
||||
.width_mm = arg_width_mm,
|
||||
.height_mm = arg_height_mm,
|
||||
.xoff = arg_xoff,
|
||||
.yoff = arg_yoff,
|
||||
.width = arg_width,
|
||||
.height = arg_height,
|
||||
};
|
||||
|
||||
if (!dpy_ui_info_supported(ddc->con)) {
|
||||
g_dbus_method_invocation_return_error(invocation,
|
||||
DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_UNSUPPORTED,
|
||||
"SetUIInfo is not supported");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
dpy_set_ui_info(ddc->con, &info, false);
|
||||
qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_console_register_listener(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
GUnixFDList *fd_list,
|
||||
GVariant *arg_listener)
|
||||
{
|
||||
const char *sender = g_dbus_method_invocation_get_sender(invocation);
|
||||
GDBusConnection *listener_conn;
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GSocket) socket = NULL;
|
||||
g_autoptr(GSocketConnection) socket_conn = NULL;
|
||||
g_autofree char *guid = g_dbus_generate_guid();
|
||||
DBusDisplayListener *listener;
|
||||
int fd;
|
||||
|
||||
if (sender && g_hash_table_contains(ddc->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);
|
||||
close(fd);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
socket_conn = g_socket_connection_factory_create_connection(socket);
|
||||
|
||||
qemu_dbus_display1_console_complete_register_listener(
|
||||
ddc->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 = dbus_display_listener_new(sender, listener_conn, ddc);
|
||||
if (!listener) {
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
g_hash_table_insert(ddc->listeners,
|
||||
(gpointer)dbus_display_listener_get_bus_name(listener),
|
||||
listener);
|
||||
g_object_connect(listener_conn,
|
||||
"swapped-signal::closed", listener_vanished_cb, listener,
|
||||
NULL);
|
||||
|
||||
trace_dbus_registered_listener(sender);
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_kbd_press(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint arg_keycode)
|
||||
{
|
||||
QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
|
||||
|
||||
trace_dbus_kbd_press(arg_keycode);
|
||||
|
||||
qkbd_state_key_event(ddc->kbd, qcode, true);
|
||||
|
||||
qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_kbd_release(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint arg_keycode)
|
||||
{
|
||||
QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
|
||||
|
||||
trace_dbus_kbd_release(arg_keycode);
|
||||
|
||||
qkbd_state_key_event(ddc->kbd, qcode, false);
|
||||
|
||||
qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_kbd_qemu_leds_updated(void *data, int ledstate)
|
||||
{
|
||||
DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
|
||||
|
||||
qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
int dx, int dy)
|
||||
{
|
||||
trace_dbus_mouse_rel_motion(dx, dy);
|
||||
|
||||
if (qemu_input_is_absolute()) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation, DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_INVALID,
|
||||
"Mouse is not relative");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
qemu_input_queue_rel(ddc->con, INPUT_AXIS_X, dx);
|
||||
qemu_input_queue_rel(ddc->con, INPUT_AXIS_Y, dy);
|
||||
qemu_input_event_sync();
|
||||
|
||||
qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
|
||||
invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_mouse_set_pos(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint x, guint y)
|
||||
{
|
||||
int width, height;
|
||||
|
||||
trace_dbus_mouse_set_pos(x, y);
|
||||
|
||||
if (!qemu_input_is_absolute()) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation, DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_INVALID,
|
||||
"Mouse is not absolute");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
width = qemu_console_get_width(ddc->con, 0);
|
||||
height = qemu_console_get_height(ddc->con, 0);
|
||||
if (x >= width || y >= height) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation, DBUS_DISPLAY_ERROR,
|
||||
DBUS_DISPLAY_ERROR_INVALID,
|
||||
"Invalid mouse position");
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
qemu_input_queue_abs(ddc->con, INPUT_AXIS_X, x, 0, width);
|
||||
qemu_input_queue_abs(ddc->con, INPUT_AXIS_Y, y, 0, height);
|
||||
qemu_input_event_sync();
|
||||
|
||||
qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
|
||||
invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_mouse_press(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint button)
|
||||
{
|
||||
trace_dbus_mouse_press(button);
|
||||
|
||||
qemu_input_queue_btn(ddc->con, button, true);
|
||||
qemu_input_event_sync();
|
||||
|
||||
qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dbus_mouse_release(DBusDisplayConsole *ddc,
|
||||
GDBusMethodInvocation *invocation,
|
||||
guint button)
|
||||
{
|
||||
trace_dbus_mouse_release(button);
|
||||
|
||||
qemu_input_queue_btn(ddc->con, button, false);
|
||||
qemu_input_event_sync();
|
||||
|
||||
qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation);
|
||||
|
||||
return DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_mouse_mode_change(Notifier *notify, void *data)
|
||||
{
|
||||
DBusDisplayConsole *ddc =
|
||||
container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
|
||||
|
||||
g_object_set(ddc->iface_mouse,
|
||||
"is-absolute", qemu_input_is_absolute(),
|
||||
NULL);
|
||||
}
|
||||
|
||||
int dbus_display_console_get_index(DBusDisplayConsole *ddc)
|
||||
{
|
||||
return qemu_console_get_index(ddc->con);
|
||||
}
|
||||
|
||||
DBusDisplayConsole *
|
||||
dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
|
||||
{
|
||||
g_autofree char *path = NULL;
|
||||
g_autofree char *label = NULL;
|
||||
char device_addr[256] = "";
|
||||
DBusDisplayConsole *ddc;
|
||||
int idx;
|
||||
|
||||
assert(display);
|
||||
assert(con);
|
||||
|
||||
label = qemu_console_get_label(con);
|
||||
idx = qemu_console_get_index(con);
|
||||
path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
|
||||
ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
|
||||
"g-object-path", path,
|
||||
NULL);
|
||||
ddc->display = display;
|
||||
ddc->con = con;
|
||||
/* handle errors, and skip non graphics? */
|
||||
qemu_console_fill_device_address(
|
||||
con, device_addr, sizeof(device_addr), NULL);
|
||||
|
||||
ddc->iface = qemu_dbus_display1_console_skeleton_new();
|
||||
g_object_set(ddc->iface,
|
||||
"label", label,
|
||||
"type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
|
||||
"head", qemu_console_get_head(con),
|
||||
"width", qemu_console_get_width(con, 0),
|
||||
"height", qemu_console_get_height(con, 0),
|
||||
"device-address", device_addr,
|
||||
NULL);
|
||||
g_object_connect(ddc->iface,
|
||||
"swapped-signal::handle-register-listener",
|
||||
dbus_console_register_listener, ddc,
|
||||
"swapped-signal::handle-set-uiinfo",
|
||||
dbus_console_set_ui_info, ddc,
|
||||
NULL);
|
||||
g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
|
||||
G_DBUS_INTERFACE_SKELETON(ddc->iface));
|
||||
|
||||
ddc->kbd = qkbd_state_init(con);
|
||||
ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
|
||||
qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
|
||||
g_object_connect(ddc->iface_kbd,
|
||||
"swapped-signal::handle-press", dbus_kbd_press, ddc,
|
||||
"swapped-signal::handle-release", dbus_kbd_release, ddc,
|
||||
NULL);
|
||||
g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
|
||||
G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
|
||||
|
||||
ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
|
||||
g_object_connect(ddc->iface_mouse,
|
||||
"swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
|
||||
"swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
|
||||
"swapped-signal::handle-press", dbus_mouse_press, ddc,
|
||||
"swapped-signal::handle-release", dbus_mouse_release, ddc,
|
||||
NULL);
|
||||
g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
|
||||
G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
|
||||
|
||||
register_displaychangelistener(&ddc->dcl);
|
||||
ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
|
||||
qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
|
||||
|
||||
return ddc;
|
||||
}
|
761
ui/dbus-display1.xml
Normal file
761
ui/dbus-display1.xml
Normal file
@ -0,0 +1,761 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<node>
|
||||
<!--
|
||||
org.qemu.Display1.VM:
|
||||
|
||||
This interface is implemented on ``/org/qemu/Display1/VM``.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.VM">
|
||||
<!--
|
||||
Name:
|
||||
|
||||
The name of the VM.
|
||||
-->
|
||||
<property name="Name" type="s" access="read"/>
|
||||
|
||||
<!--
|
||||
UUID:
|
||||
|
||||
The UUID of the VM.
|
||||
-->
|
||||
<property name="UUID" type="s" access="read"/>
|
||||
|
||||
<!--
|
||||
ConsoleIDs:
|
||||
|
||||
The list of consoles available on ``/org/qemu/Display1/Console_$id``.
|
||||
-->
|
||||
<property name="ConsoleIDs" type="au" access="read"/>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Console:
|
||||
|
||||
This interface is implemented on ``/org/qemu/Display1/Console_$id``. You
|
||||
may discover available consoles through introspection or with the
|
||||
:dbus:prop:`org.qemu.Display1.VM.ConsoleIDs` property.
|
||||
|
||||
A console is attached to a video device head. It may be "Graphic" or
|
||||
"Text" (see :dbus:prop:`Type` and other properties).
|
||||
|
||||
Interactions with a console may be done with
|
||||
:dbus:iface:`org.qemu.Display1.Keyboard` and
|
||||
:dbus:iface:`org.qemu.Display1.Mouse` interfaces when available.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Console">
|
||||
<!--
|
||||
RegisterListener:
|
||||
@listener: a Unix socket FD, for peer-to-peer D-Bus communication.
|
||||
|
||||
Register a console listener, which will receive display updates, until
|
||||
it is disconnected.
|
||||
|
||||
Multiple listeners may be registered simultaneously.
|
||||
|
||||
The listener is expected to implement the
|
||||
:dbus:iface:`org.qemu.Display1.Listener` interface.
|
||||
-->
|
||||
<method name="RegisterListener">
|
||||
<arg type="h" name="listener" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SetUIInfo:
|
||||
@width_mm: the physical display width in millimeters.
|
||||
@height_mm: the physical display height in millimeters.
|
||||
@xoff: horizontal offset, in pixels.
|
||||
@yoff: vertical offset, in pixels.
|
||||
@width: console width, in pixels.
|
||||
@height: console height, in pixels.
|
||||
|
||||
Modify the dimensions and display settings.
|
||||
-->
|
||||
<method name="SetUIInfo">
|
||||
<arg name="width_mm" type="q" direction="in"/>
|
||||
<arg name="height_mm" type="q" direction="in"/>
|
||||
<arg name="xoff" type="i" direction="in"/>
|
||||
<arg name="yoff" type="i" direction="in"/>
|
||||
<arg name="width" type="u" direction="in"/>
|
||||
<arg name="height" type="u" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Label:
|
||||
|
||||
A user-friendly name for the console (for ex: "VGA").
|
||||
-->
|
||||
<property name="Label" type="s" access="read"/>
|
||||
|
||||
<!--
|
||||
Head:
|
||||
|
||||
Graphical device head number.
|
||||
-->
|
||||
<property name="Head" type="u" access="read"/>
|
||||
|
||||
<!--
|
||||
Type:
|
||||
|
||||
Console type ("Graphic" or "Text").
|
||||
-->
|
||||
<property name="Type" type="s" access="read"/>
|
||||
|
||||
<!--
|
||||
Width:
|
||||
|
||||
Console width, in pixels.
|
||||
-->
|
||||
<property name="Width" type="u" access="read"/>
|
||||
|
||||
<!--
|
||||
Height:
|
||||
|
||||
Console height, in pixels.
|
||||
-->
|
||||
<property name="Height" type="u" access="read"/>
|
||||
|
||||
<!--
|
||||
DeviceAddress:
|
||||
|
||||
The device address (ex: "pci/0000/02.0").
|
||||
-->
|
||||
<property name="DeviceAddress" type="s" access="read"/>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Keyboard:
|
||||
|
||||
This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
|
||||
:dbus:iface:`~org.qemu.Display1.Console`).
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Keyboard">
|
||||
<!--
|
||||
Press:
|
||||
@keycode: QEMU key number (xtkbd + special re-encoding of high bit)
|
||||
|
||||
Send a key press event.
|
||||
-->
|
||||
<method name="Press">
|
||||
<arg type="u" name="keycode" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Release:
|
||||
@keycode: QEMU key number (xtkbd + special re-encoding of high bit)
|
||||
|
||||
Send a key release event.
|
||||
-->
|
||||
<method name="Release">
|
||||
<arg type="u" name="keycode" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Modifiers:
|
||||
|
||||
The active keyboard modifiers::
|
||||
|
||||
Scroll = 1 << 0
|
||||
Num = 1 << 1
|
||||
Caps = 1 << 2
|
||||
-->
|
||||
<property name="Modifiers" type="u" access="read"/>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Mouse:
|
||||
|
||||
This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
|
||||
:dbus:iface:`~org.qemu.Display1.Console` documentation).
|
||||
|
||||
.. _dbus-button-values:
|
||||
|
||||
**Button values**::
|
||||
|
||||
Left = 0
|
||||
Middle = 1
|
||||
Right = 2
|
||||
Wheel-up = 3
|
||||
Wheel-down = 4
|
||||
Side = 5
|
||||
Extra = 6
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Mouse">
|
||||
<!--
|
||||
Press:
|
||||
@button: :ref:`button value<dbus-button-values>`.
|
||||
|
||||
Send a mouse button press event.
|
||||
-->
|
||||
<method name="Press">
|
||||
<arg type="u" name="button" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Release:
|
||||
@button: :ref:`button value<dbus-button-values>`.
|
||||
|
||||
Send a mouse button release event.
|
||||
-->
|
||||
<method name="Release">
|
||||
<arg type="u" name="button" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SetAbsPosition:
|
||||
@x: X position, in pixels.
|
||||
@y: Y position, in pixels.
|
||||
|
||||
Set the mouse pointer position.
|
||||
|
||||
Returns an error if not :dbus:prop:`IsAbsolute`.
|
||||
-->
|
||||
<method name="SetAbsPosition">
|
||||
<arg type="u" name="x" direction="in"/>
|
||||
<arg type="u" name="y" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
RelMotion:
|
||||
@dx: X-delta, in pixels.
|
||||
@dy: Y-delta, in pixels.
|
||||
|
||||
Move the mouse pointer position, relative to the current position.
|
||||
|
||||
Returns an error if :dbus:prop:`IsAbsolute`.
|
||||
-->
|
||||
<method name="RelMotion">
|
||||
<arg type="i" name="dx" direction="in"/>
|
||||
<arg type="i" name="dy" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
IsAbsolute:
|
||||
|
||||
Whether the mouse is using absolute movements.
|
||||
-->
|
||||
<property name="IsAbsolute" type="b" access="read"/>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Listener:
|
||||
|
||||
This client-side interface must be available on
|
||||
``/org/qemu/Display1/Listener`` when registering the peer-to-peer
|
||||
connection with :dbus:meth:`~org.qemu.Display1.Console.Register`.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Listener">
|
||||
<!--
|
||||
Scanout:
|
||||
@width: display width, in pixels.
|
||||
@height: display height, in pixels.
|
||||
@stride: data stride, in bytes.
|
||||
@pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
|
||||
@data: image data.
|
||||
|
||||
Resize and update the display content.
|
||||
|
||||
The data to transfer for the display update may be large. The preferred
|
||||
scanout method is :dbus:meth:`ScanoutDMABUF`, used whenever possible.
|
||||
-->
|
||||
<method name="Scanout">
|
||||
<arg type="u" name="width" direction="in"/>
|
||||
<arg type="u" name="height" direction="in"/>
|
||||
<arg type="u" name="stride" direction="in"/>
|
||||
<arg type="u" name="pixman_format" direction="in"/>
|
||||
<arg type="ay" name="data" direction="in">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Update:
|
||||
@x: X update position, in pixels.
|
||||
@y: Y update position, in pixels.
|
||||
@width: update width, in pixels.
|
||||
@height: update height, in pixels.
|
||||
@stride: data stride, in bytes.
|
||||
@pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
|
||||
@data: display image data.
|
||||
|
||||
Update the display content.
|
||||
|
||||
This method is only called after a :dbus:meth:`Scanout` call.
|
||||
-->
|
||||
<method name="Update">
|
||||
<arg type="i" name="x" direction="in"/>
|
||||
<arg type="i" name="y" direction="in"/>
|
||||
<arg type="i" name="width" direction="in"/>
|
||||
<arg type="i" name="height" direction="in"/>
|
||||
<arg type="u" name="stride" direction="in"/>
|
||||
<arg type="u" name="pixman_format" direction="in"/>
|
||||
<arg type="ay" name="data" direction="in">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
ScanoutDMABUF:
|
||||
@dmabuf: the DMABUF file descriptor.
|
||||
@width: display width, in pixels.
|
||||
@height: display height, in pixels.
|
||||
@stride: stride, in bytes.
|
||||
@fourcc: DMABUF fourcc.
|
||||
@modifier: DMABUF modifier.
|
||||
@y0_top: whether Y position 0 is the top or not.
|
||||
|
||||
Resize and update the display content with a DMABUF.
|
||||
-->
|
||||
<method name="ScanoutDMABUF">
|
||||
<arg type="h" name="dmabuf" direction="in"/>
|
||||
<arg type="u" name="width" direction="in"/>
|
||||
<arg type="u" name="height" direction="in"/>
|
||||
<arg type="u" name="stride" direction="in"/>
|
||||
<arg type="u" name="fourcc" direction="in"/>
|
||||
<!-- xywh? -->
|
||||
<arg type="t" name="modifier" direction="in"/>
|
||||
<arg type="b" name="y0_top" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
UpdateDMABUF:
|
||||
@x: the X update position, in pixels.
|
||||
@y: the Y update position, in pixels.
|
||||
@width: the update width, in pixels.
|
||||
@height: the update height, in pixels.
|
||||
|
||||
Update the display content with the current DMABUF and the given region.
|
||||
-->
|
||||
<method name="UpdateDMABUF">
|
||||
<arg type="i" name="x" direction="in"/>
|
||||
<arg type="i" name="y" direction="in"/>
|
||||
<arg type="i" name="width" direction="in"/>
|
||||
<arg type="i" name="height" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Disable:
|
||||
|
||||
Disable the display (turn it off).
|
||||
-->
|
||||
<method name="Disable">
|
||||
</method>
|
||||
|
||||
<!--
|
||||
MouseSet:
|
||||
@x: X mouse position, in pixels.
|
||||
@y: Y mouse position, in pixels.
|
||||
@on: whether the mouse is visible or not.
|
||||
|
||||
Set the mouse position and visibility.
|
||||
-->
|
||||
<method name="MouseSet">
|
||||
<arg type="i" name="x" direction="in"/>
|
||||
<arg type="i" name="y" direction="in"/>
|
||||
<arg type="i" name="on" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
CursorDefine:
|
||||
@width: cursor width, in pixels.
|
||||
@height: cursor height, in pixels.
|
||||
@hot_x: hot-spot X position, in pixels.
|
||||
@hot_y: hot-spot Y position, in pixels.
|
||||
@data: the cursor data.
|
||||
|
||||
Set the mouse cursor shape and hot-spot. The "data" must be ARGB, 32-bit
|
||||
per pixel.
|
||||
-->
|
||||
<method name="CursorDefine">
|
||||
<arg type="i" name="width" direction="in"/>
|
||||
<arg type="i" name="height" direction="in"/>
|
||||
<arg type="i" name="hot_x" direction="in"/>
|
||||
<arg type="i" name="hot_y" direction="in"/>
|
||||
<arg type="ay" name="data" direction="in">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Clipboard:
|
||||
|
||||
This interface must be implemented by both the client and the server on
|
||||
``/org/qemu/Display1/Clipboard`` to support clipboard sharing between
|
||||
the client and the guest.
|
||||
|
||||
Once :dbus:meth:`Register`'ed, method calls may be sent and received in both
|
||||
directions. Unregistered callers will get error replies.
|
||||
|
||||
.. _dbus-clipboard-selection:
|
||||
|
||||
**Selection values**::
|
||||
|
||||
Clipboard = 0
|
||||
Primary = 1
|
||||
Secondary = 2
|
||||
|
||||
.. _dbus-clipboard-serial:
|
||||
|
||||
**Serial counter**
|
||||
|
||||
To solve potential clipboard races, clipboard grabs have an associated
|
||||
serial counter. It is set to 0 on registration, and incremented by 1 for
|
||||
each grab. The peer with the highest serial is the clipboard grab owner.
|
||||
|
||||
When a grab with a lower serial is received, it should be discarded.
|
||||
|
||||
When a grab is attempted with the same serial number as the current grab,
|
||||
the one coming from the client should have higher priority, and the client
|
||||
should gain clipboard grab ownership.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Clipboard">
|
||||
<!--
|
||||
Register:
|
||||
|
||||
Register a clipboard session and reinitialize the serial counter.
|
||||
|
||||
The client must register itself, and is granted an exclusive
|
||||
access for handling the clipboard.
|
||||
|
||||
The server can reinitialize the session as well (to reset the counter).
|
||||
-->
|
||||
<method name="Register"/>
|
||||
|
||||
<!--
|
||||
Unregister:
|
||||
|
||||
Unregister the clipboard session.
|
||||
-->
|
||||
<method name="Unregister"/>
|
||||
<!--
|
||||
Grab:
|
||||
@selection: a :ref:`selection value<dbus-clipboard-selection>`.
|
||||
@serial: the current grab :ref:`serial<dbus-clipboard-serial>`.
|
||||
@mimes: the list of available content MIME types.
|
||||
|
||||
Grab the clipboard, claiming current clipboard content.
|
||||
-->
|
||||
<method name="Grab">
|
||||
<arg type="u" name="selection"/>
|
||||
<arg type="u" name="serial"/>
|
||||
<arg type="as" name="mimes"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Release:
|
||||
@selection: a :ref:`selection value<dbus-clipboard-selection>`.
|
||||
|
||||
Release the clipboard (does nothing if not the current owner).
|
||||
-->
|
||||
<method name="Release">
|
||||
<arg type="u" name="selection"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Request:
|
||||
@selection: a :ref:`selection value<dbus-clipboard-selection>`
|
||||
@mimes: requested MIME types (by order of preference).
|
||||
@reply_mime: the returned data MIME type.
|
||||
@data: the clipboard data.
|
||||
|
||||
Request the clipboard content.
|
||||
|
||||
Return an error if the clipboard is empty, or the requested MIME types
|
||||
are unavailable.
|
||||
-->
|
||||
<method name="Request">
|
||||
<arg type="u" name="selection"/>
|
||||
<arg type="as" name="mimes"/>
|
||||
<arg type="s" name="reply_mime" direction="out"/>
|
||||
<arg type="ay" name="data" direction="out">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Audio:
|
||||
|
||||
Audio backend may be available on ``/org/qemu/Display1/Audio``.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Audio">
|
||||
<!--
|
||||
RegisterOutListener:
|
||||
@listener: a Unix socket FD, for peer-to-peer D-Bus communication.
|
||||
|
||||
Register an audio backend playback handler.
|
||||
|
||||
Multiple listeners may be registered simultaneously.
|
||||
|
||||
The listener is expected to implement the
|
||||
:dbus:iface:`org.qemu.Display1.AudioOutListener` interface.
|
||||
-->
|
||||
<method name="RegisterOutListener">
|
||||
<arg type="h" name="listener" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
RegisterInListener:
|
||||
@listener: a Unix socket FD, for peer-to-peer D-Bus communication.
|
||||
|
||||
Register an audio backend record handler.
|
||||
|
||||
Multiple listeners may be registered simultaneously.
|
||||
|
||||
The listener is expected to implement the
|
||||
:dbus:iface:`org.qemu.Display1.AudioInListener` interface.
|
||||
-->
|
||||
<method name="RegisterInListener">
|
||||
<arg type="h" name="listener" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.AudioOutListener:
|
||||
|
||||
This client-side interface must be available on
|
||||
``/org/qemu/Display1/AudioOutListener`` when registering the peer-to-peer
|
||||
connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterOutListener`.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.AudioOutListener">
|
||||
<!--
|
||||
Init:
|
||||
@id: the stream ID.
|
||||
@bits: PCM bits per sample.
|
||||
@is_signed: whether the PCM data is signed.
|
||||
@is_float: PCM floating point format.
|
||||
@freq: the PCM frequency in Hz.
|
||||
@nchannels: the number of channels.
|
||||
@bytes_per_frame: the bytes per frame.
|
||||
@bytes_per_second: the bytes per second.
|
||||
@be: whether using big-endian format.
|
||||
|
||||
Initializes a PCM playback stream.
|
||||
-->
|
||||
<method name="Init">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="bits" type="y" direction="in"/>
|
||||
<arg name="is_signed" type="b" direction="in"/>
|
||||
<arg name="is_float" type="b" direction="in"/>
|
||||
<arg name="freq" type="u" direction="in"/>
|
||||
<arg name="nchannels" type="y" direction="in"/>
|
||||
<arg name="bytes_per_frame" type="u" direction="in"/>
|
||||
<arg name="bytes_per_second" type="u" direction="in"/>
|
||||
<arg name="be" type="b" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Fini:
|
||||
@id: the stream ID.
|
||||
|
||||
Finish & close a playback stream.
|
||||
-->
|
||||
<method name="Fini">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SetEnabled:
|
||||
@id: the stream ID.
|
||||
|
||||
Resume or suspend the playback stream.
|
||||
-->
|
||||
<method name="SetEnabled">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="enabled" type="b" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SetVolume:
|
||||
@id: the stream ID.
|
||||
@mute: whether the stream is muted.
|
||||
@volume: the volume per-channel.
|
||||
|
||||
Set the stream volume and mute state (volume without unit, 0-255).
|
||||
-->
|
||||
<method name="SetVolume">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="mute" type="b" direction="in"/>
|
||||
<arg name="volume" type="ay" direction="in">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Write:
|
||||
@id: the stream ID.
|
||||
@data: the PCM data.
|
||||
|
||||
PCM stream to play.
|
||||
-->
|
||||
<method name="Write">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg type="ay" name="data" direction="in">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.AudioInListener:
|
||||
|
||||
This client-side interface must be available on
|
||||
``/org/qemu/Display1/AudioInListener`` when registering the peer-to-peer
|
||||
connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterInListener`.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.AudioInListener">
|
||||
<!--
|
||||
Init:
|
||||
@id: the stream ID.
|
||||
@bits: PCM bits per sample.
|
||||
@is_signed: whether the PCM data is signed.
|
||||
@is_float: PCM floating point format.
|
||||
@freq: the PCM frequency in Hz.
|
||||
@nchannels: the number of channels.
|
||||
@bytes_per_frame: the bytes per frame.
|
||||
@bytes_per_second: the bytes per second.
|
||||
@be: whether using big-endian format.
|
||||
|
||||
Initializes a PCM record stream.
|
||||
-->
|
||||
<method name="Init">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="bits" type="y" direction="in"/>
|
||||
<arg name="is_signed" type="b" direction="in"/>
|
||||
<arg name="is_float" type="b" direction="in"/>
|
||||
<arg name="freq" type="u" direction="in"/>
|
||||
<arg name="nchannels" type="y" direction="in"/>
|
||||
<arg name="bytes_per_frame" type="u" direction="in"/>
|
||||
<arg name="bytes_per_second" type="u" direction="in"/>
|
||||
<arg name="be" type="b" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Fini:
|
||||
@id: the stream ID.
|
||||
|
||||
Finish & close a record stream.
|
||||
-->
|
||||
<method name="Fini">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SetEnabled:
|
||||
@id: the stream ID.
|
||||
|
||||
Resume or suspend the record stream.
|
||||
-->
|
||||
<method name="SetEnabled">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="enabled" type="b" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SetVolume:
|
||||
@id: the stream ID.
|
||||
@mute: whether the stream is muted.
|
||||
@volume: the volume per-channel.
|
||||
|
||||
Set the stream volume and mute state (volume without unit, 0-255).
|
||||
-->
|
||||
<method name="SetVolume">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="mute" type="b" direction="in"/>
|
||||
<arg name="volume" type="ay" direction="in">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
Read:
|
||||
@id: the stream ID.
|
||||
@size: the amount to read, in bytes.
|
||||
@data: the recorded data (which may be less than requested).
|
||||
|
||||
Read "size" bytes from the record stream.
|
||||
-->
|
||||
<method name="Read">
|
||||
<arg name="id" type="t" direction="in"/>
|
||||
<arg name="size" type="t" direction="in"/>
|
||||
<arg type="ay" name="data" direction="out">
|
||||
<annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
|
||||
</arg>
|
||||
</method>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Chardev:
|
||||
|
||||
Character devices may be available on ``/org/qemu/Display1/Chardev_$id``.
|
||||
|
||||
They may be used for different kind of streams, which are identified via
|
||||
their FQDN :dbus:prop:`Name`.
|
||||
|
||||
.. _dbus-chardev-fqdn:
|
||||
|
||||
Here are some known reserved kind names (the ``org.qemu`` prefix is
|
||||
reserved by QEMU):
|
||||
|
||||
org.qemu.console.serial.0
|
||||
A serial console stream.
|
||||
|
||||
org.qemu.monitor.hmp.0
|
||||
A QEMU HMP human monitor.
|
||||
|
||||
org.qemu.monitor.qmp.0
|
||||
A QEMU QMP monitor.
|
||||
|
||||
org.qemu.usbredir
|
||||
A usbredir stream.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Chardev">
|
||||
<!--
|
||||
Register:
|
||||
@stream: a Unix FD to redirect the stream to.
|
||||
|
||||
Register a file-descriptor for the stream handling.
|
||||
|
||||
The current handler, if any, will be replaced.
|
||||
-->
|
||||
<method name="Register">
|
||||
<arg type="h" name="stream" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
SendBreak:
|
||||
|
||||
Send a break event to the character device.
|
||||
-->
|
||||
<method name="SendBreak"/>
|
||||
|
||||
<!--
|
||||
Name:
|
||||
|
||||
The FQDN name to identify the kind of stream. See :ref:`reserved
|
||||
names<dbus-chardev-fqdn>`.
|
||||
-->
|
||||
<property name="Name" type="s" access="read"/>
|
||||
|
||||
<!--
|
||||
FEOpened:
|
||||
|
||||
Whether the front-end side is opened.
|
||||
-->
|
||||
<property name="FEOpened" type="b" access="read"/>
|
||||
|
||||
<!--
|
||||
Echo:
|
||||
|
||||
Whether the input should be echo'ed (for serial streams).
|
||||
-->
|
||||
<property name="Echo" type="b" access="read"/>
|
||||
|
||||
<!--
|
||||
Owner:
|
||||
|
||||
The D-Bus unique name of the registered handler.
|
||||
-->
|
||||
<property name="Owner" type="s" access="read"/>
|
||||
</interface>
|
||||
</node>
|
48
ui/dbus-error.c
Normal file
48
ui/dbus-error.c
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* QEMU DBus display errors
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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 "dbus.h"
|
||||
|
||||
static const GDBusErrorEntry dbus_display_error_entries[] = {
|
||||
{ DBUS_DISPLAY_ERROR_FAILED, "org.qemu.Display1.Error.Failed" },
|
||||
{ DBUS_DISPLAY_ERROR_INVALID, "org.qemu.Display1.Error.Invalid" },
|
||||
{ DBUS_DISPLAY_ERROR_UNSUPPORTED, "org.qemu.Display1.Error.Unsupported" },
|
||||
};
|
||||
|
||||
G_STATIC_ASSERT(G_N_ELEMENTS(dbus_display_error_entries) ==
|
||||
DBUS_DISPLAY_N_ERRORS);
|
||||
|
||||
GQuark
|
||||
dbus_display_error_quark(void)
|
||||
{
|
||||
static gsize quark;
|
||||
|
||||
g_dbus_error_register_error_domain(
|
||||
"dbus-display-error-quark",
|
||||
&quark,
|
||||
dbus_display_error_entries,
|
||||
G_N_ELEMENTS(dbus_display_error_entries));
|
||||
|
||||
return (GQuark)quark;
|
||||
}
|
486
ui/dbus-listener.c
Normal file
486
ui/dbus-listener.c
Normal file
@ -0,0 +1,486 @@
|
||||
/*
|
||||
* QEMU DBus display console
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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 "sysemu/sysemu.h"
|
||||
#include "dbus.h"
|
||||
#include <gio/gunixfdlist.h>
|
||||
|
||||
#include "ui/shader.h"
|
||||
#include "ui/egl-helpers.h"
|
||||
#include "ui/egl-context.h"
|
||||
#include "trace.h"
|
||||
|
||||
struct _DBusDisplayListener {
|
||||
GObject parent;
|
||||
|
||||
char *bus_name;
|
||||
DBusDisplayConsole *console;
|
||||
GDBusConnection *conn;
|
||||
|
||||
QemuDBusDisplay1Listener *proxy;
|
||||
|
||||
DisplayChangeListener dcl;
|
||||
DisplaySurface *ds;
|
||||
QemuGLShader *gls;
|
||||
int gl_updates;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
|
||||
|
||||
static void dbus_update_gl_cb(GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
DBusDisplayListener *ddl = user_data;
|
||||
|
||||
if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy,
|
||||
res, &err)) {
|
||||
error_report("Failed to call update: %s", err->message);
|
||||
}
|
||||
|
||||
graphic_hw_gl_block(ddl->dcl.con, false);
|
||||
g_object_unref(ddl);
|
||||
}
|
||||
|
||||
static void dbus_call_update_gl(DBusDisplayListener *ddl,
|
||||
int x, int y, int w, int h)
|
||||
{
|
||||
graphic_hw_gl_block(ddl->dcl.con, true);
|
||||
glFlush();
|
||||
qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
|
||||
x, y, w, h,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
DBUS_DEFAULT_TIMEOUT, NULL,
|
||||
dbus_update_gl_cb,
|
||||
g_object_ref(ddl));
|
||||
}
|
||||
|
||||
static void dbus_scanout_disable(DisplayChangeListener *dcl)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
ddl->ds = NULL;
|
||||
qemu_dbus_display1_listener_call_disable(
|
||||
ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
|
||||
QemuDmaBuf *dmabuf)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GUnixFDList) fd_list = NULL;
|
||||
|
||||
fd_list = g_unix_fd_list_new();
|
||||
if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) {
|
||||
error_report("Failed to setup dmabuf fdlist: %s", err->message);
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_dbus_display1_listener_call_scanout_dmabuf(
|
||||
ddl->proxy,
|
||||
g_variant_new_handle(0),
|
||||
dmabuf->width,
|
||||
dmabuf->height,
|
||||
dmabuf->stride,
|
||||
dmabuf->fourcc,
|
||||
dmabuf->modifier,
|
||||
dmabuf->y0_top,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
fd_list,
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void dbus_scanout_texture(DisplayChangeListener *dcl,
|
||||
uint32_t tex_id,
|
||||
bool backing_y_0_top,
|
||||
uint32_t backing_width,
|
||||
uint32_t backing_height,
|
||||
uint32_t x, uint32_t y,
|
||||
uint32_t w, uint32_t h)
|
||||
{
|
||||
QemuDmaBuf dmabuf = {
|
||||
.width = backing_width,
|
||||
.height = backing_height,
|
||||
.y0_top = backing_y_0_top,
|
||||
};
|
||||
|
||||
assert(tex_id);
|
||||
dmabuf.fd = egl_get_fd_for_texture(
|
||||
tex_id, (EGLint *)&dmabuf.stride,
|
||||
(EGLint *)&dmabuf.fourcc,
|
||||
&dmabuf.modifier);
|
||||
if (dmabuf.fd < 0) {
|
||||
error_report("%s: failed to get fd for texture", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
dbus_scanout_dmabuf(dcl, &dmabuf);
|
||||
close(dmabuf.fd);
|
||||
}
|
||||
|
||||
static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
|
||||
QemuDmaBuf *dmabuf, bool have_hot,
|
||||
uint32_t hot_x, uint32_t hot_y)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
DisplaySurface *ds;
|
||||
GVariant *v_data = NULL;
|
||||
egl_fb cursor_fb;
|
||||
|
||||
if (!dmabuf) {
|
||||
qemu_dbus_display1_listener_call_mouse_set(
|
||||
ddl->proxy, 0, 0, false,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
egl_dmabuf_import_texture(dmabuf);
|
||||
if (!dmabuf->texture) {
|
||||
return;
|
||||
}
|
||||
egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height,
|
||||
dmabuf->texture, false);
|
||||
ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height);
|
||||
egl_fb_read(ds, &cursor_fb);
|
||||
|
||||
v_data = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("ay"),
|
||||
surface_data(ds),
|
||||
surface_width(ds) * surface_height(ds) * 4,
|
||||
TRUE,
|
||||
(GDestroyNotify)qemu_free_displaysurface,
|
||||
ds);
|
||||
qemu_dbus_display1_listener_call_cursor_define(
|
||||
ddl->proxy,
|
||||
surface_width(ds),
|
||||
surface_height(ds),
|
||||
hot_x,
|
||||
hot_y,
|
||||
v_data,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void dbus_cursor_position(DisplayChangeListener *dcl,
|
||||
uint32_t pos_x, uint32_t pos_y)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
qemu_dbus_display1_listener_call_mouse_set(
|
||||
ddl->proxy, pos_x, pos_y, true,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void dbus_release_dmabuf(DisplayChangeListener *dcl,
|
||||
QemuDmaBuf *dmabuf)
|
||||
{
|
||||
dbus_scanout_disable(dcl);
|
||||
}
|
||||
|
||||
static void dbus_scanout_update(DisplayChangeListener *dcl,
|
||||
uint32_t x, uint32_t y,
|
||||
uint32_t w, uint32_t h)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
dbus_call_update_gl(ddl, x, y, w, h);
|
||||
}
|
||||
|
||||
static void dbus_gl_refresh(DisplayChangeListener *dcl)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
graphic_hw_update(dcl->con);
|
||||
|
||||
if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ddl->gl_updates) {
|
||||
dbus_call_update_gl(ddl, 0, 0,
|
||||
surface_width(ddl->ds), surface_height(ddl->ds));
|
||||
ddl->gl_updates = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void dbus_refresh(DisplayChangeListener *dcl)
|
||||
{
|
||||
graphic_hw_update(dcl->con);
|
||||
}
|
||||
|
||||
static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
|
||||
int x, int y, int w, int h)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
if (ddl->ds) {
|
||||
surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h);
|
||||
}
|
||||
|
||||
ddl->gl_updates++;
|
||||
}
|
||||
|
||||
static void dbus_gfx_update(DisplayChangeListener *dcl,
|
||||
int x, int y, int w, int h)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
pixman_image_t *img;
|
||||
GVariant *v_data;
|
||||
size_t stride;
|
||||
|
||||
assert(ddl->ds);
|
||||
stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
|
||||
|
||||
trace_dbus_update(x, y, w, h);
|
||||
|
||||
/* make a copy, since gvariant only handles linear data */
|
||||
img = pixman_image_create_bits(surface_format(ddl->ds),
|
||||
w, h, NULL, stride);
|
||||
pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
|
||||
x, y, 0, 0, 0, 0, w, h);
|
||||
|
||||
v_data = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("ay"),
|
||||
pixman_image_get_data(img),
|
||||
pixman_image_get_stride(img) * h,
|
||||
TRUE,
|
||||
(GDestroyNotify)pixman_image_unref,
|
||||
img);
|
||||
qemu_dbus_display1_listener_call_update(ddl->proxy,
|
||||
x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img),
|
||||
v_data,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
|
||||
struct DisplaySurface *new_surface)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
if (ddl->ds) {
|
||||
surface_gl_destroy_texture(ddl->gls, ddl->ds);
|
||||
}
|
||||
ddl->ds = new_surface;
|
||||
if (ddl->ds) {
|
||||
int width = surface_width(ddl->ds);
|
||||
int height = surface_height(ddl->ds);
|
||||
|
||||
surface_gl_create_texture(ddl->gls, ddl->ds);
|
||||
/* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
|
||||
dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
|
||||
width, height, 0, 0, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void dbus_gfx_switch(DisplayChangeListener *dcl,
|
||||
struct DisplaySurface *new_surface)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
GVariant *v_data = NULL;
|
||||
|
||||
ddl->ds = new_surface;
|
||||
if (!ddl->ds) {
|
||||
/* why not call disable instead? */
|
||||
return;
|
||||
}
|
||||
|
||||
v_data = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("ay"),
|
||||
surface_data(ddl->ds),
|
||||
surface_stride(ddl->ds) * surface_height(ddl->ds),
|
||||
TRUE,
|
||||
(GDestroyNotify)pixman_image_unref,
|
||||
pixman_image_ref(ddl->ds->image));
|
||||
qemu_dbus_display1_listener_call_scanout(ddl->proxy,
|
||||
surface_width(ddl->ds),
|
||||
surface_height(ddl->ds),
|
||||
surface_stride(ddl->ds),
|
||||
surface_format(ddl->ds),
|
||||
v_data,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void dbus_mouse_set(DisplayChangeListener *dcl,
|
||||
int x, int y, int on)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
qemu_dbus_display1_listener_call_mouse_set(
|
||||
ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void dbus_cursor_define(DisplayChangeListener *dcl,
|
||||
QEMUCursor *c)
|
||||
{
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
GVariant *v_data = NULL;
|
||||
|
||||
cursor_get(c);
|
||||
v_data = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("ay"),
|
||||
c->data,
|
||||
c->width * c->height * 4,
|
||||
TRUE,
|
||||
(GDestroyNotify)cursor_put,
|
||||
c);
|
||||
|
||||
qemu_dbus_display1_listener_call_cursor_define(
|
||||
ddl->proxy,
|
||||
c->width,
|
||||
c->height,
|
||||
c->hot_x,
|
||||
c->hot_y,
|
||||
v_data,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
const DisplayChangeListenerOps dbus_gl_dcl_ops = {
|
||||
.dpy_name = "dbus-gl",
|
||||
.dpy_gfx_update = dbus_gl_gfx_update,
|
||||
.dpy_gfx_switch = dbus_gl_gfx_switch,
|
||||
.dpy_gfx_check_format = console_gl_check_format,
|
||||
.dpy_refresh = dbus_gl_refresh,
|
||||
.dpy_mouse_set = dbus_mouse_set,
|
||||
.dpy_cursor_define = dbus_cursor_define,
|
||||
|
||||
.dpy_gl_scanout_disable = dbus_scanout_disable,
|
||||
.dpy_gl_scanout_texture = dbus_scanout_texture,
|
||||
.dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf,
|
||||
.dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf,
|
||||
.dpy_gl_cursor_position = dbus_cursor_position,
|
||||
.dpy_gl_release_dmabuf = dbus_release_dmabuf,
|
||||
.dpy_gl_update = dbus_scanout_update,
|
||||
};
|
||||
|
||||
const DisplayChangeListenerOps dbus_dcl_ops = {
|
||||
.dpy_name = "dbus",
|
||||
.dpy_gfx_update = dbus_gfx_update,
|
||||
.dpy_gfx_switch = dbus_gfx_switch,
|
||||
.dpy_refresh = dbus_refresh,
|
||||
.dpy_mouse_set = dbus_mouse_set,
|
||||
.dpy_cursor_define = dbus_cursor_define,
|
||||
};
|
||||
|
||||
static void
|
||||
dbus_display_listener_dispose(GObject *object)
|
||||
{
|
||||
DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
|
||||
|
||||
unregister_displaychangelistener(&ddl->dcl);
|
||||
g_clear_object(&ddl->conn);
|
||||
g_clear_pointer(&ddl->bus_name, g_free);
|
||||
g_clear_object(&ddl->proxy);
|
||||
g_clear_pointer(&ddl->gls, qemu_gl_fini_shader);
|
||||
|
||||
G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_listener_constructed(GObject *object)
|
||||
{
|
||||
DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
|
||||
|
||||
if (display_opengl) {
|
||||
ddl->gls = qemu_gl_init_shader();
|
||||
ddl->dcl.ops = &dbus_gl_dcl_ops;
|
||||
} else {
|
||||
ddl->dcl.ops = &dbus_dcl_ops;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
||||
|
||||
object_class->dispose = dbus_display_listener_dispose;
|
||||
object_class->constructed = dbus_display_listener_constructed;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_listener_init(DBusDisplayListener *ddl)
|
||||
{
|
||||
}
|
||||
|
||||
const char *
|
||||
dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
|
||||
{
|
||||
return ddl->bus_name ?: "p2p";
|
||||
}
|
||||
|
||||
DBusDisplayConsole *
|
||||
dbus_display_listener_get_console(DBusDisplayListener *ddl)
|
||||
{
|
||||
return ddl->console;
|
||||
}
|
||||
|
||||
DBusDisplayListener *
|
||||
dbus_display_listener_new(const char *bus_name,
|
||||
GDBusConnection *conn,
|
||||
DBusDisplayConsole *console)
|
||||
{
|
||||
DBusDisplayListener *ddl;
|
||||
QemuConsole *con;
|
||||
g_autoptr(GError) err = NULL;
|
||||
|
||||
ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
|
||||
ddl->proxy =
|
||||
qemu_dbus_display1_listener_proxy_new_sync(conn,
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
||||
NULL,
|
||||
"/org/qemu/Display1/Listener",
|
||||
NULL,
|
||||
&err);
|
||||
if (!ddl->proxy) {
|
||||
error_report("Failed to setup proxy: %s", err->message);
|
||||
g_object_unref(conn);
|
||||
g_object_unref(ddl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ddl->bus_name = g_strdup(bus_name);
|
||||
ddl->conn = conn;
|
||||
ddl->console = console;
|
||||
|
||||
con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
|
||||
assert(con);
|
||||
ddl->dcl.con = con;
|
||||
register_displaychangelistener(&ddl->dcl);
|
||||
|
||||
return ddl;
|
||||
}
|
35
ui/dbus-module.c
Normal file
35
ui/dbus-module.c
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* D-Bus module support.
|
||||
*
|
||||
* Copyright (C) 2021 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "ui/dbus-module.h"
|
||||
|
||||
int using_dbus_display;
|
||||
|
||||
static bool
|
||||
qemu_dbus_display_add_client(int csock, Error **errp)
|
||||
{
|
||||
error_setg(errp, "D-Bus display isn't enabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct QemuDBusDisplayOps qemu_dbus_display = {
|
||||
.add_client = qemu_dbus_display_add_client,
|
||||
};
|
482
ui/dbus.c
Normal file
482
ui/dbus.c
Normal file
@ -0,0 +1,482 @@
|
||||
/*
|
||||
* QEMU DBus display
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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/cutils.h"
|
||||
#include "qemu/dbus.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "ui/dbus-module.h"
|
||||
#include "ui/egl-helpers.h"
|
||||
#include "ui/egl-context.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_int.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "dbus.h"
|
||||
|
||||
static DBusDisplay *dbus_display;
|
||||
|
||||
static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
qemu_egl_rn_ctx);
|
||||
return qemu_egl_create_context(dgc, params);
|
||||
}
|
||||
|
||||
static const DisplayGLCtxOps dbus_gl_ops = {
|
||||
.compatible_dcl = &dbus_gl_dcl_ops,
|
||||
.dpy_gl_ctx_create = dbus_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = qemu_egl_make_context_current,
|
||||
};
|
||||
|
||||
static NotifierList dbus_display_notifiers =
|
||||
NOTIFIER_LIST_INITIALIZER(dbus_display_notifiers);
|
||||
|
||||
void
|
||||
dbus_display_notifier_add(Notifier *notifier)
|
||||
{
|
||||
notifier_list_add(&dbus_display_notifiers, notifier);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_notifier_remove(Notifier *notifier)
|
||||
{
|
||||
notifier_remove(notifier);
|
||||
}
|
||||
|
||||
void
|
||||
dbus_display_notify(DBusDisplayEvent *event)
|
||||
{
|
||||
notifier_list_notify(&dbus_display_notifiers, event);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_init(Object *o)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
g_autoptr(GDBusObjectSkeleton) vm = NULL;
|
||||
|
||||
dd->glctx.ops = &dbus_gl_ops;
|
||||
dd->iface = qemu_dbus_display1_vm_skeleton_new();
|
||||
dd->consoles = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
|
||||
dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
|
||||
|
||||
vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM");
|
||||
g_dbus_object_skeleton_add_interface(
|
||||
vm, G_DBUS_INTERFACE_SKELETON(dd->iface));
|
||||
g_dbus_object_manager_server_export(dd->server, vm);
|
||||
|
||||
dbus_clipboard_init(dd);
|
||||
dbus_chardev_init(dd);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_finalize(Object *o)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
if (dd->notifier.notify) {
|
||||
dbus_display_notifier_remove(&dd->notifier);
|
||||
}
|
||||
|
||||
qemu_clipboard_peer_unregister(&dd->clipboard_peer);
|
||||
g_clear_object(&dd->clipboard);
|
||||
|
||||
g_clear_object(&dd->server);
|
||||
g_clear_pointer(&dd->consoles, g_ptr_array_unref);
|
||||
if (dd->add_client_cancellable) {
|
||||
g_cancellable_cancel(dd->add_client_cancellable);
|
||||
}
|
||||
g_clear_object(&dd->add_client_cancellable);
|
||||
g_clear_object(&dd->bus);
|
||||
g_clear_object(&dd->iface);
|
||||
g_free(dd->dbus_addr);
|
||||
g_free(dd->audiodev);
|
||||
dbus_display = NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
|
||||
{
|
||||
QemuConsole *con;
|
||||
DBusDisplayConsole *dbus_console;
|
||||
|
||||
con = qemu_console_lookup_by_index(idx);
|
||||
assert(con);
|
||||
|
||||
if (qemu_console_is_graphic(con) &&
|
||||
dd->gl_mode != DISPLAYGL_MODE_OFF) {
|
||||
qemu_console_set_display_gl_ctx(con, &dd->glctx);
|
||||
}
|
||||
|
||||
dbus_console = dbus_display_console_new(dd, con);
|
||||
g_ptr_array_insert(dd->consoles, idx, dbus_console);
|
||||
g_dbus_object_manager_server_export(dd->server,
|
||||
G_DBUS_OBJECT_SKELETON(dbus_console));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_complete(UserCreatable *uc, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(uc);
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
|
||||
g_autoptr(GArray) consoles = NULL;
|
||||
GVariant *console_ids;
|
||||
int idx;
|
||||
|
||||
if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
|
||||
error_setg(errp, "There is already an instance of %s",
|
||||
TYPE_DBUS_DISPLAY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dd->p2p) {
|
||||
/* wait for dbus_display_add_client() */
|
||||
dbus_display = dd;
|
||||
} else if (dd->dbus_addr && *dd->dbus_addr) {
|
||||
dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
||||
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
|
||||
NULL, NULL, &err);
|
||||
} else {
|
||||
dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
|
||||
}
|
||||
if (err) {
|
||||
error_setg(errp, "failed to connect to DBus: %s", err->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dd->audiodev && *dd->audiodev) {
|
||||
AudioState *audio_state = audio_state_by_name(dd->audiodev);
|
||||
if (!audio_state) {
|
||||
error_setg(errp, "Audiodev '%s' not found", dd->audiodev);
|
||||
return;
|
||||
}
|
||||
if (!g_str_equal(audio_state->drv->name, "dbus")) {
|
||||
error_setg(errp, "Audiodev '%s' is not compatible with DBus",
|
||||
dd->audiodev);
|
||||
return;
|
||||
}
|
||||
audio_state->drv->set_dbus_server(audio_state, dd->server);
|
||||
}
|
||||
|
||||
consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
|
||||
for (idx = 0;; idx++) {
|
||||
if (!qemu_console_lookup_by_index(idx)) {
|
||||
break;
|
||||
}
|
||||
if (!dbus_display_add_console(dd, idx, errp)) {
|
||||
return;
|
||||
}
|
||||
g_array_append_val(consoles, idx);
|
||||
}
|
||||
|
||||
console_ids = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("au"),
|
||||
consoles->data, consoles->len * sizeof(guint32), TRUE,
|
||||
(GDestroyNotify)g_array_unref, consoles);
|
||||
g_steal_pointer(&consoles);
|
||||
g_object_set(dd->iface,
|
||||
"name", qemu_name ?: "QEMU " QEMU_VERSION,
|
||||
"uuid", uuid,
|
||||
"console-ids", console_ids,
|
||||
NULL);
|
||||
|
||||
if (dd->bus) {
|
||||
g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
|
||||
g_bus_own_name_on_connection(dd->bus, "org.qemu",
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
NULL, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_add_client_ready(GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GDBusConnection) conn = NULL;
|
||||
|
||||
g_clear_object(&dbus_display->add_client_cancellable);
|
||||
|
||||
conn = g_dbus_connection_new_finish(res, &err);
|
||||
if (!conn) {
|
||||
error_printf("Failed to accept D-Bus client: %s", err->message);
|
||||
}
|
||||
|
||||
g_dbus_object_manager_server_set_connection(dbus_display->server, conn);
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
dbus_display_add_client(int csock, Error **errp)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GSocket) socket = NULL;
|
||||
g_autoptr(GSocketConnection) conn = NULL;
|
||||
g_autofree char *guid = g_dbus_generate_guid();
|
||||
|
||||
if (!dbus_display) {
|
||||
error_setg(errp, "p2p connections not accepted in bus mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dbus_display->add_client_cancellable) {
|
||||
g_cancellable_cancel(dbus_display->add_client_cancellable);
|
||||
}
|
||||
|
||||
socket = g_socket_new_from_fd(csock, &err);
|
||||
if (!socket) {
|
||||
error_setg(errp, "Failed to setup D-Bus socket: %s", err->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
conn = g_socket_connection_factory_create_connection(socket);
|
||||
|
||||
dbus_display->add_client_cancellable = g_cancellable_new();
|
||||
|
||||
g_dbus_connection_new(G_IO_STREAM(conn),
|
||||
guid,
|
||||
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
|
||||
NULL,
|
||||
dbus_display->add_client_cancellable,
|
||||
dbus_display_add_client_ready,
|
||||
NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
get_dbus_p2p(Object *o, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
return dd->p2p;
|
||||
}
|
||||
|
||||
static void
|
||||
set_dbus_p2p(Object *o, bool p2p, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
dd->p2p = p2p;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_dbus_addr(Object *o, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
return g_strdup(dd->dbus_addr);
|
||||
}
|
||||
|
||||
static void
|
||||
set_dbus_addr(Object *o, const char *str, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
g_free(dd->dbus_addr);
|
||||
dd->dbus_addr = g_strdup(str);
|
||||
}
|
||||
|
||||
static char *
|
||||
get_audiodev(Object *o, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
return g_strdup(dd->audiodev);
|
||||
}
|
||||
|
||||
static void
|
||||
set_audiodev(Object *o, const char *str, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
g_free(dd->audiodev);
|
||||
dd->audiodev = g_strdup(str);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
get_gl_mode(Object *o, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
return dd->gl_mode;
|
||||
}
|
||||
|
||||
static void
|
||||
set_gl_mode(Object *o, int val, Error **errp)
|
||||
{
|
||||
DBusDisplay *dd = DBUS_DISPLAY(o);
|
||||
|
||||
dd->gl_mode = val;
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_display_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
||||
|
||||
ucc->complete = dbus_display_complete;
|
||||
object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p);
|
||||
object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
|
||||
object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev);
|
||||
object_class_property_add_enum(oc, "gl-mode",
|
||||
"DisplayGLMode", &DisplayGLMode_lookup,
|
||||
get_gl_mode, set_gl_mode);
|
||||
}
|
||||
|
||||
#define TYPE_CHARDEV_VC "chardev-vc"
|
||||
|
||||
typedef struct DBusVCClass {
|
||||
DBusChardevClass parent_class;
|
||||
|
||||
void (*parent_parse)(QemuOpts *opts, ChardevBackend *b, Error **errp);
|
||||
} DBusVCClass;
|
||||
|
||||
DECLARE_CLASS_CHECKERS(DBusVCClass, DBUS_VC,
|
||||
TYPE_CHARDEV_VC)
|
||||
|
||||
static void
|
||||
dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
|
||||
Error **errp)
|
||||
{
|
||||
DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
|
||||
const char *name = qemu_opt_get(opts, "name");
|
||||
const char *id = qemu_opts_id(opts);
|
||||
|
||||
if (name == NULL) {
|
||||
if (g_str_has_prefix(id, "compat_monitor")) {
|
||||
name = "org.qemu.monitor.hmp.0";
|
||||
} else if (g_str_has_prefix(id, "serial")) {
|
||||
name = "org.qemu.console.serial.0";
|
||||
} else {
|
||||
name = "";
|
||||
}
|
||||
if (!qemu_opt_set(opts, "name", name, errp)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
klass->parent_parse(opts, backend, errp);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_vc_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DBusVCClass *klass = DBUS_VC_CLASS(oc);
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
|
||||
klass->parent_parse = cc->parse;
|
||||
cc->parse = dbus_vc_parse;
|
||||
}
|
||||
|
||||
static const TypeInfo dbus_vc_type_info = {
|
||||
.name = TYPE_CHARDEV_VC,
|
||||
.parent = TYPE_CHARDEV_DBUS,
|
||||
.class_init = dbus_vc_class_init,
|
||||
};
|
||||
|
||||
static void
|
||||
early_dbus_init(DisplayOptions *opts)
|
||||
{
|
||||
DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
|
||||
|
||||
if (mode != DISPLAYGL_MODE_OFF) {
|
||||
if (egl_rendernode_init(opts->u.dbus.rendernode, mode) < 0) {
|
||||
error_report("dbus: render node init failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
display_opengl = 1;
|
||||
}
|
||||
|
||||
type_register(&dbus_vc_type_info);
|
||||
}
|
||||
|
||||
static void
|
||||
dbus_init(DisplayState *ds, DisplayOptions *opts)
|
||||
{
|
||||
DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
|
||||
|
||||
if (opts->u.dbus.addr && opts->u.dbus.p2p) {
|
||||
error_report("dbus: can't accept both addr=X and p2p=yes options");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
using_dbus_display = 1;
|
||||
|
||||
object_new_with_props(TYPE_DBUS_DISPLAY,
|
||||
object_get_objects_root(),
|
||||
"dbus-display", &error_fatal,
|
||||
"addr", opts->u.dbus.addr ?: "",
|
||||
"audiodev", opts->u.dbus.audiodev ?: "",
|
||||
"gl-mode", DisplayGLMode_str(mode),
|
||||
"p2p", yes_no(opts->u.dbus.p2p),
|
||||
NULL);
|
||||
}
|
||||
|
||||
static const TypeInfo dbus_display_info = {
|
||||
.name = TYPE_DBUS_DISPLAY,
|
||||
.parent = TYPE_OBJECT,
|
||||
.instance_size = sizeof(DBusDisplay),
|
||||
.instance_init = dbus_display_init,
|
||||
.instance_finalize = dbus_display_finalize,
|
||||
.class_init = dbus_display_class_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ TYPE_USER_CREATABLE },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static QemuDisplay qemu_display_dbus = {
|
||||
.type = DISPLAY_TYPE_DBUS,
|
||||
.early_init = early_dbus_init,
|
||||
.init = dbus_init,
|
||||
};
|
||||
|
||||
static void register_dbus(void)
|
||||
{
|
||||
qemu_dbus_display = (struct QemuDBusDisplayOps) {
|
||||
.add_client = dbus_display_add_client,
|
||||
};
|
||||
type_register_static(&dbus_display_info);
|
||||
qemu_display_register(&qemu_display_dbus);
|
||||
}
|
||||
|
||||
type_init(register_dbus);
|
||||
|
||||
#ifdef CONFIG_OPENGL
|
||||
module_dep("ui-opengl");
|
||||
#endif
|
144
ui/dbus.h
Normal file
144
ui/dbus.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* QEMU DBus display
|
||||
*
|
||||
* Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef UI_DBUS_H_
|
||||
#define UI_DBUS_H_
|
||||
|
||||
#include "chardev/char-socket.h"
|
||||
#include "qemu/dbus.h"
|
||||
#include "qom/object.h"
|
||||
#include "ui/console.h"
|
||||
#include "ui/clipboard.h"
|
||||
|
||||
#include "dbus-display1.h"
|
||||
|
||||
typedef struct DBusClipboardRequest {
|
||||
GDBusMethodInvocation *invocation;
|
||||
QemuClipboardType type;
|
||||
guint timeout_id;
|
||||
} DBusClipboardRequest;
|
||||
|
||||
struct DBusDisplay {
|
||||
Object parent;
|
||||
|
||||
DisplayGLMode gl_mode;
|
||||
bool p2p;
|
||||
char *dbus_addr;
|
||||
char *audiodev;
|
||||
DisplayGLCtx glctx;
|
||||
|
||||
GDBusConnection *bus;
|
||||
GDBusObjectManagerServer *server;
|
||||
QemuDBusDisplay1VM *iface;
|
||||
GPtrArray *consoles;
|
||||
GCancellable *add_client_cancellable;
|
||||
|
||||
QemuClipboardPeer clipboard_peer;
|
||||
QemuDBusDisplay1Clipboard *clipboard;
|
||||
QemuDBusDisplay1Clipboard *clipboard_proxy;
|
||||
DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||
|
||||
Notifier notifier;
|
||||
};
|
||||
|
||||
#define TYPE_DBUS_DISPLAY "dbus-display"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
|
||||
|
||||
void dbus_display_notifier_add(Notifier *notifier);
|
||||
|
||||
#define DBUS_DISPLAY_TYPE_CONSOLE dbus_display_console_get_type()
|
||||
G_DECLARE_FINAL_TYPE(DBusDisplayConsole,
|
||||
dbus_display_console,
|
||||
DBUS_DISPLAY,
|
||||
CONSOLE,
|
||||
GDBusObjectSkeleton)
|
||||
|
||||
DBusDisplayConsole *
|
||||
dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
|
||||
|
||||
int
|
||||
dbus_display_console_get_index(DBusDisplayConsole *ddc);
|
||||
|
||||
#define DBUS_DISPLAY_TYPE_LISTENER dbus_display_listener_get_type()
|
||||
G_DECLARE_FINAL_TYPE(DBusDisplayListener,
|
||||
dbus_display_listener,
|
||||
DBUS_DISPLAY,
|
||||
LISTENER,
|
||||
GObject)
|
||||
|
||||
DBusDisplayListener *
|
||||
dbus_display_listener_new(const char *bus_name,
|
||||
GDBusConnection *conn,
|
||||
DBusDisplayConsole *console);
|
||||
|
||||
DBusDisplayConsole *
|
||||
dbus_display_listener_get_console(DBusDisplayListener *ddl);
|
||||
|
||||
const char *
|
||||
dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
|
||||
|
||||
extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
|
||||
extern const DisplayChangeListenerOps dbus_dcl_ops;
|
||||
|
||||
#define TYPE_CHARDEV_DBUS "chardev-dbus"
|
||||
|
||||
typedef struct DBusChardevClass {
|
||||
SocketChardevClass parent_class;
|
||||
|
||||
void (*parent_chr_be_event)(Chardev *s, QEMUChrEvent event);
|
||||
} DBusChardevClass;
|
||||
|
||||
DECLARE_CLASS_CHECKERS(DBusChardevClass, DBUS_CHARDEV,
|
||||
TYPE_CHARDEV_DBUS)
|
||||
|
||||
typedef struct DBusChardev {
|
||||
SocketChardev parent;
|
||||
|
||||
bool exported;
|
||||
QemuDBusDisplay1Chardev *iface;
|
||||
} DBusChardev;
|
||||
|
||||
DECLARE_INSTANCE_CHECKER(DBusChardev, DBUS_CHARDEV, TYPE_CHARDEV_DBUS)
|
||||
|
||||
#define CHARDEV_IS_DBUS(chr) \
|
||||
object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_DBUS)
|
||||
|
||||
typedef enum {
|
||||
DBUS_DISPLAY_CHARDEV_OPEN,
|
||||
DBUS_DISPLAY_CHARDEV_CLOSE,
|
||||
} DBusDisplayEventType;
|
||||
|
||||
typedef struct DBusDisplayEvent {
|
||||
DBusDisplayEventType type;
|
||||
union {
|
||||
DBusChardev *chardev;
|
||||
};
|
||||
} DBusDisplayEvent;
|
||||
|
||||
void dbus_display_notify(DBusDisplayEvent *event);
|
||||
|
||||
void dbus_chardev_init(DBusDisplay *dpy);
|
||||
|
||||
void dbus_clipboard_init(DBusDisplay *dpy);
|
||||
|
||||
#endif /* UI_DBUS_H_ */
|
@ -1,7 +1,7 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "ui/egl-context.h"
|
||||
|
||||
QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
EGLContext ctx;
|
||||
@ -24,12 +24,12 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
|
||||
void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
|
||||
{
|
||||
eglDestroyContext(qemu_egl_display, ctx);
|
||||
}
|
||||
|
||||
int qemu_egl_make_context_current(DisplayChangeListener *dcl,
|
||||
int qemu_egl_make_context_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx)
|
||||
{
|
||||
return eglMakeCurrent(qemu_egl_display,
|
||||
|
@ -38,12 +38,12 @@ static void egl_gfx_switch(DisplayChangeListener *dcl,
|
||||
edpy->ds = new_surface;
|
||||
}
|
||||
|
||||
static QEMUGLContext egl_create_context(DisplayChangeListener *dcl,
|
||||
static QEMUGLContext egl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
qemu_egl_rn_ctx);
|
||||
return qemu_egl_create_context(dcl, params);
|
||||
return qemu_egl_create_context(dgc, params);
|
||||
}
|
||||
|
||||
static void egl_scanout_disable(DisplayChangeListener *dcl)
|
||||
@ -157,10 +157,6 @@ static const DisplayChangeListenerOps egl_ops = {
|
||||
.dpy_gfx_update = egl_gfx_update,
|
||||
.dpy_gfx_switch = egl_gfx_switch,
|
||||
|
||||
.dpy_gl_ctx_create = egl_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = qemu_egl_make_context_current,
|
||||
|
||||
.dpy_gl_scanout_disable = egl_scanout_disable,
|
||||
.dpy_gl_scanout_texture = egl_scanout_texture,
|
||||
.dpy_gl_scanout_dmabuf = egl_scanout_dmabuf,
|
||||
@ -170,6 +166,13 @@ static const DisplayChangeListenerOps egl_ops = {
|
||||
.dpy_gl_update = egl_scanout_flush,
|
||||
};
|
||||
|
||||
static const DisplayGLCtxOps eglctx_ops = {
|
||||
.compatible_dcl = &egl_ops,
|
||||
.dpy_gl_ctx_create = egl_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = qemu_egl_make_context_current,
|
||||
};
|
||||
|
||||
static void early_egl_headless_init(DisplayOptions *opts)
|
||||
{
|
||||
display_opengl = 1;
|
||||
@ -188,6 +191,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
|
||||
}
|
||||
|
||||
for (idx = 0;; idx++) {
|
||||
DisplayGLCtx *ctx;
|
||||
|
||||
con = qemu_console_lookup_by_index(idx);
|
||||
if (!con || !qemu_console_is_graphic(con)) {
|
||||
break;
|
||||
@ -197,6 +202,9 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
|
||||
edpy->dcl.con = con;
|
||||
edpy->dcl.ops = &egl_ops;
|
||||
edpy->gls = qemu_gl_init_shader();
|
||||
ctx = g_new0(DisplayGLCtx, 1);
|
||||
ctx->ops = &eglctx_ops;
|
||||
qemu_console_set_display_gl_ctx(con, ctx);
|
||||
register_displaychangelistener(&edpy->dcl);
|
||||
}
|
||||
}
|
||||
|
@ -74,10 +74,9 @@ static void gd_clipboard_clear(GtkClipboard *clipboard,
|
||||
gd->cbowner[s] = false;
|
||||
}
|
||||
|
||||
static void gd_clipboard_notify(Notifier *notifier, void *data)
|
||||
static void gd_clipboard_update_info(GtkDisplayState *gd,
|
||||
QemuClipboardInfo *info)
|
||||
{
|
||||
GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);
|
||||
QemuClipboardInfo *info = data;
|
||||
QemuClipboardSelection s = info->selection;
|
||||
bool self_update = info->owner == &gd->cbpeer;
|
||||
|
||||
@ -118,6 +117,22 @@ static void gd_clipboard_notify(Notifier *notifier, void *data)
|
||||
*/
|
||||
}
|
||||
|
||||
static void gd_clipboard_notify(Notifier *notifier, void *data)
|
||||
{
|
||||
GtkDisplayState *gd =
|
||||
container_of(notifier, GtkDisplayState, cbpeer.notifier);
|
||||
QemuClipboardNotify *notify = data;
|
||||
|
||||
switch (notify->type) {
|
||||
case QEMU_CLIPBOARD_UPDATE_INFO:
|
||||
gd_clipboard_update_info(gd, notify->info);
|
||||
return;
|
||||
case QEMU_CLIPBOARD_RESET_SERIAL:
|
||||
/* ignore */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void gd_clipboard_request(QemuClipboardInfo *info,
|
||||
QemuClipboardType type)
|
||||
{
|
||||
@ -172,7 +187,7 @@ static void gd_owner_change(GtkClipboard *clipboard,
|
||||
void gd_clipboard_init(GtkDisplayState *gd)
|
||||
{
|
||||
gd->cbpeer.name = "gtk";
|
||||
gd->cbpeer.update.notify = gd_clipboard_notify;
|
||||
gd->cbpeer.notifier.notify = gd_clipboard_notify;
|
||||
gd->cbpeer.request = gd_clipboard_request;
|
||||
qemu_clipboard_peer_register(&gd->cbpeer);
|
||||
|
||||
|
12
ui/gtk-egl.c
12
ui/gtk-egl.c
@ -119,8 +119,6 @@ void gd_egl_draw(VirtualConsole *vc)
|
||||
|
||||
glFlush();
|
||||
}
|
||||
|
||||
graphic_hw_gl_flushed(vc->gfx.dcl.con);
|
||||
}
|
||||
|
||||
void gd_egl_update(DisplayChangeListener *dcl,
|
||||
@ -199,14 +197,14 @@ void gd_egl_switch(DisplayChangeListener *dcl,
|
||||
}
|
||||
}
|
||||
|
||||
QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||
VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
|
||||
|
||||
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
|
||||
vc->gfx.esurface, vc->gfx.ectx);
|
||||
return qemu_egl_create_context(dcl, params);
|
||||
return qemu_egl_create_context(dgc, params);
|
||||
}
|
||||
|
||||
void gd_egl_scanout_disable(DisplayChangeListener *dcl)
|
||||
@ -362,10 +360,10 @@ void gtk_egl_init(DisplayGLMode mode)
|
||||
display_opengl = 1;
|
||||
}
|
||||
|
||||
int gd_egl_make_current(DisplayChangeListener *dcl,
|
||||
int gd_egl_make_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx)
|
||||
{
|
||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||
VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
|
||||
|
||||
return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
|
||||
vc->gfx.esurface, ctx);
|
||||
|
@ -101,8 +101,6 @@ void gd_gl_area_draw(VirtualConsole *vc)
|
||||
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
|
||||
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
|
||||
}
|
||||
|
||||
graphic_hw_gl_flushed(vc->gfx.dcl.con);
|
||||
}
|
||||
|
||||
void gd_gl_area_update(DisplayChangeListener *dcl,
|
||||
@ -172,10 +170,10 @@ void gd_gl_area_switch(DisplayChangeListener *dcl,
|
||||
}
|
||||
}
|
||||
|
||||
QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||
VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
|
||||
GdkWindow *window;
|
||||
GdkGLContext *ctx;
|
||||
GError *err = NULL;
|
||||
@ -201,7 +199,7 @@ QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
|
||||
void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
|
||||
{
|
||||
/* FIXME */
|
||||
}
|
||||
@ -280,7 +278,7 @@ void gtk_gl_area_init(void)
|
||||
display_opengl = 1;
|
||||
}
|
||||
|
||||
int gd_gl_area_make_current(DisplayChangeListener *dcl,
|
||||
int gd_gl_area_make_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx)
|
||||
{
|
||||
gdk_gl_context_make_current(ctx);
|
||||
|
28
ui/gtk.c
28
ui/gtk.c
@ -593,7 +593,6 @@ void gd_hw_gl_flushed(void *vcon)
|
||||
close(dmabuf->fence_fd);
|
||||
dmabuf->fence_fd = -1;
|
||||
graphic_hw_gl_block(vc->gfx.dcl.con, false);
|
||||
graphic_hw_gl_flushed(vc->gfx.dcl.con);
|
||||
}
|
||||
|
||||
/** DisplayState Callbacks (opengl version) **/
|
||||
@ -607,9 +606,6 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
|
||||
.dpy_mouse_set = gd_mouse_set,
|
||||
.dpy_cursor_define = gd_cursor_define,
|
||||
|
||||
.dpy_gl_ctx_create = gd_gl_area_create_context,
|
||||
.dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
|
||||
.dpy_gl_ctx_make_current = gd_gl_area_make_current,
|
||||
.dpy_gl_scanout_texture = gd_gl_area_scanout_texture,
|
||||
.dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
|
||||
.dpy_gl_update = gd_gl_area_scanout_flush,
|
||||
@ -618,8 +614,14 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
|
||||
.dpy_has_dmabuf = gd_has_dmabuf,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_X11
|
||||
static const DisplayGLCtxOps gl_area_ctx_ops = {
|
||||
.compatible_dcl = &dcl_gl_area_ops,
|
||||
.dpy_gl_ctx_create = gd_gl_area_create_context,
|
||||
.dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
|
||||
.dpy_gl_ctx_make_current = gd_gl_area_make_current,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_X11
|
||||
static const DisplayChangeListenerOps dcl_egl_ops = {
|
||||
.dpy_name = "gtk-egl",
|
||||
.dpy_gfx_update = gd_egl_update,
|
||||
@ -629,9 +631,6 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
|
||||
.dpy_mouse_set = gd_mouse_set,
|
||||
.dpy_cursor_define = gd_cursor_define,
|
||||
|
||||
.dpy_gl_ctx_create = gd_egl_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = gd_egl_make_current,
|
||||
.dpy_gl_scanout_disable = gd_egl_scanout_disable,
|
||||
.dpy_gl_scanout_texture = gd_egl_scanout_texture,
|
||||
.dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
|
||||
@ -642,6 +641,12 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
|
||||
.dpy_has_dmabuf = gd_has_dmabuf,
|
||||
};
|
||||
|
||||
static const DisplayGLCtxOps egl_ctx_ops = {
|
||||
.compatible_dcl = &dcl_egl_ops,
|
||||
.dpy_gl_ctx_create = gd_egl_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = gd_egl_make_current,
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_OPENGL */
|
||||
@ -698,7 +703,7 @@ static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
dpy_set_ui_info(vc->gfx.dcl.con, &info);
|
||||
dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_OPENGL)
|
||||
@ -2035,6 +2040,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
|
||||
g_signal_connect(vc->gfx.drawing_area, "realize",
|
||||
G_CALLBACK(gl_area_realize), vc);
|
||||
vc->gfx.dcl.ops = &dcl_gl_area_ops;
|
||||
vc->gfx.dgc.ops = &gl_area_ctx_ops;
|
||||
} else {
|
||||
#ifdef CONFIG_X11
|
||||
vc->gfx.drawing_area = gtk_drawing_area_new();
|
||||
@ -2049,6 +2055,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
|
||||
gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
|
||||
#pragma GCC diagnostic pop
|
||||
vc->gfx.dcl.ops = &dcl_egl_ops;
|
||||
vc->gfx.dgc.ops = &egl_ctx_ops;
|
||||
vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
|
||||
#else
|
||||
abort();
|
||||
@ -2083,6 +2090,9 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
|
||||
vc->gfx.kbd = qkbd_state_init(con);
|
||||
vc->gfx.dcl.con = con;
|
||||
|
||||
if (display_opengl) {
|
||||
qemu_console_set_display_gl_ctx(con, &vc->gfx.dgc);
|
||||
}
|
||||
register_displaychangelistener(&vc->gfx.dcl);
|
||||
|
||||
gd_connect_vc_gfx_signals(vc);
|
||||
|
@ -12,7 +12,11 @@ softmmu_ss.add(files(
|
||||
'kbd-state.c',
|
||||
'keymaps.c',
|
||||
'qemu-pixman.c',
|
||||
'util.c',
|
||||
))
|
||||
if dbus_display
|
||||
softmmu_ss.add(files('dbus-module.c'))
|
||||
endif
|
||||
softmmu_ss.add([spice_headers, files('spice-module.c')])
|
||||
softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
|
||||
|
||||
@ -64,6 +68,30 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
|
||||
ui_modules += {'egl-headless' : egl_headless_ss}
|
||||
endif
|
||||
|
||||
if dbus_display
|
||||
dbus_ss = ss.source_set()
|
||||
dbus_display1 = custom_target('dbus-display gdbus-codegen',
|
||||
output: ['dbus-display1.h', 'dbus-display1.c'],
|
||||
input: files('dbus-display1.xml'),
|
||||
command: [config_host['GDBUS_CODEGEN'],
|
||||
'@INPUT@',
|
||||
'--glib-min-required', '2.64',
|
||||
'--output-directory', meson.current_build_dir(),
|
||||
'--interface-prefix', 'org.qemu.',
|
||||
'--c-namespace', 'QemuDBus',
|
||||
'--generate-c-code', '@BASENAME@'])
|
||||
dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
|
||||
if_true: [files(
|
||||
'dbus-chardev.c',
|
||||
'dbus-clipboard.c',
|
||||
'dbus-console.c',
|
||||
'dbus-error.c',
|
||||
'dbus-listener.c',
|
||||
'dbus.c',
|
||||
), dbus_display1])
|
||||
ui_modules += {'dbus' : dbus_ss}
|
||||
endif
|
||||
|
||||
if gtk.found()
|
||||
softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
|
||||
|
||||
|
12
ui/sdl2-gl.c
12
ui/sdl2-gl.c
@ -58,7 +58,6 @@ static void sdl2_gl_render_surface(struct sdl2_console *scon)
|
||||
|
||||
surface_gl_render_texture(scon->gls, scon->surface);
|
||||
SDL_GL_SwapWindow(scon->real_window);
|
||||
graphic_hw_gl_flushed(scon->dcl.con);
|
||||
}
|
||||
|
||||
void sdl2_gl_update(DisplayChangeListener *dcl,
|
||||
@ -133,10 +132,10 @@ void sdl2_gl_redraw(struct sdl2_console *scon)
|
||||
}
|
||||
}
|
||||
|
||||
QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
|
||||
QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
|
||||
struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
|
||||
SDL_GLContext ctx;
|
||||
|
||||
assert(scon->opengl);
|
||||
@ -168,17 +167,17 @@ QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
|
||||
return (QEMUGLContext)ctx;
|
||||
}
|
||||
|
||||
void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
|
||||
void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
|
||||
{
|
||||
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
|
||||
|
||||
SDL_GL_DeleteContext(sdlctx);
|
||||
}
|
||||
|
||||
int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
|
||||
int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
|
||||
QEMUGLContext ctx)
|
||||
{
|
||||
struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
|
||||
struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
|
||||
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
|
||||
|
||||
assert(scon->opengl);
|
||||
@ -241,5 +240,4 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
|
||||
egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
|
||||
|
||||
SDL_GL_SwapWindow(scon->real_window);
|
||||
graphic_hw_gl_flushed(dcl->con);
|
||||
}
|
||||
|
16
ui/sdl2.c
16
ui/sdl2.c
@ -561,7 +561,7 @@ static void handle_windowevent(SDL_Event *ev)
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.width = ev->window.data1;
|
||||
info.height = ev->window.data2;
|
||||
dpy_set_ui_info(scon->dcl.con, &info);
|
||||
dpy_set_ui_info(scon->dcl.con, &info, true);
|
||||
}
|
||||
sdl2_redraw(scon);
|
||||
break;
|
||||
@ -778,13 +778,17 @@ static const DisplayChangeListenerOps dcl_gl_ops = {
|
||||
.dpy_mouse_set = sdl_mouse_warp,
|
||||
.dpy_cursor_define = sdl_mouse_define,
|
||||
|
||||
.dpy_gl_ctx_create = sdl2_gl_create_context,
|
||||
.dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
|
||||
.dpy_gl_scanout_disable = sdl2_gl_scanout_disable,
|
||||
.dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
|
||||
.dpy_gl_update = sdl2_gl_scanout_flush,
|
||||
};
|
||||
|
||||
static const DisplayGLCtxOps gl_ctx_ops = {
|
||||
.compatible_dcl = &dcl_gl_ops,
|
||||
.dpy_gl_ctx_create = sdl2_gl_create_context,
|
||||
.dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
|
||||
};
|
||||
#endif
|
||||
|
||||
static void sdl2_display_early_init(DisplayOptions *o)
|
||||
@ -860,12 +864,16 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
||||
#ifdef CONFIG_OPENGL
|
||||
sdl2_console[i].opengl = display_opengl;
|
||||
sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
|
||||
sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL;
|
||||
#else
|
||||
sdl2_console[i].opengl = 0;
|
||||
sdl2_console[i].dcl.ops = &dcl_2d_ops;
|
||||
#endif
|
||||
sdl2_console[i].dcl.con = con;
|
||||
sdl2_console[i].kbd = qkbd_state_init(con);
|
||||
if (display_opengl) {
|
||||
qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc);
|
||||
}
|
||||
register_displaychangelistener(&sdl2_console[i].dcl);
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
|
||||
|
@ -884,56 +884,6 @@ bool qemu_spice_have_display_interface(QemuConsole *con)
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursively (in reverse order) appends addresses of PCI devices as it moves
|
||||
* up in the PCI hierarchy.
|
||||
*
|
||||
* @returns true on success, false when the buffer wasn't large enough
|
||||
*/
|
||||
static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
|
||||
{
|
||||
PCIBus *bus = pci_get_bus(pci);
|
||||
/*
|
||||
* equivalent to if (!pci_bus_is_root(bus)), but the function is not built
|
||||
* with PCI_CONFIG=n, avoid using an #ifdef by checking directly
|
||||
*/
|
||||
if (bus->parent_dev != NULL) {
|
||||
append_pci_address(buf, buf_size, bus->parent_dev);
|
||||
}
|
||||
|
||||
size_t len = strlen(buf);
|
||||
ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
|
||||
PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
|
||||
|
||||
return written > 0 && written < buf_size - len;
|
||||
}
|
||||
|
||||
bool qemu_spice_fill_device_address(QemuConsole *con,
|
||||
char *device_address,
|
||||
size_t size)
|
||||
{
|
||||
DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
|
||||
"device",
|
||||
&error_abort));
|
||||
PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
|
||||
TYPE_PCI_DEVICE);
|
||||
|
||||
if (pci == NULL) {
|
||||
warn_report("Setting device address of a display device to SPICE: "
|
||||
"Not a PCI device.");
|
||||
return false;
|
||||
}
|
||||
|
||||
strncpy(device_address, "pci/0000", size);
|
||||
if (!append_pci_address(device_address, size, pci)) {
|
||||
warn_report("Setting device address of a display device to SPICE: "
|
||||
"Too many PCI devices in the chain.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
|
||||
{
|
||||
if (g_slist_find(spice_consoles, con)) {
|
||||
|
@ -692,7 +692,7 @@ static int interface_client_monitors_config(QXLInstance *sin,
|
||||
}
|
||||
|
||||
trace_qemu_spice_ui_info(ssd->qxl.id, info.width, info.height);
|
||||
dpy_set_ui_info(ssd->dcl.con, &info);
|
||||
dpy_set_ui_info(ssd->dcl.con, &info, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -830,7 +830,6 @@ static void qemu_spice_gl_unblock_bh(void *opaque)
|
||||
SimpleSpiceDisplay *ssd = opaque;
|
||||
|
||||
qemu_spice_gl_block(ssd, false);
|
||||
graphic_hw_gl_flushed(ssd->dcl.con);
|
||||
}
|
||||
|
||||
static void qemu_spice_gl_block_timer(void *opaque)
|
||||
@ -909,12 +908,12 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
|
||||
}
|
||||
}
|
||||
|
||||
static QEMUGLContext qemu_spice_gl_create_context(DisplayChangeListener *dcl,
|
||||
static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
|
||||
QEMUGLParams *params)
|
||||
{
|
||||
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
qemu_egl_rn_ctx);
|
||||
return qemu_egl_create_context(dcl, params);
|
||||
return qemu_egl_create_context(dgc, params);
|
||||
}
|
||||
|
||||
static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
|
||||
@ -1106,10 +1105,6 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
|
||||
.dpy_mouse_set = display_mouse_set,
|
||||
.dpy_cursor_define = display_mouse_define,
|
||||
|
||||
.dpy_gl_ctx_create = qemu_spice_gl_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = qemu_egl_make_context_current,
|
||||
|
||||
.dpy_gl_scanout_disable = qemu_spice_gl_scanout_disable,
|
||||
.dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
|
||||
.dpy_gl_scanout_dmabuf = qemu_spice_gl_scanout_dmabuf,
|
||||
@ -1119,6 +1114,13 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
|
||||
.dpy_gl_update = qemu_spice_gl_update,
|
||||
};
|
||||
|
||||
static const DisplayGLCtxOps gl_ctx_ops = {
|
||||
.compatible_dcl = &display_listener_gl_ops,
|
||||
.dpy_gl_ctx_create = qemu_spice_gl_create_context,
|
||||
.dpy_gl_ctx_destroy = qemu_egl_destroy_context,
|
||||
.dpy_gl_ctx_make_current = qemu_egl_make_context_current,
|
||||
};
|
||||
|
||||
#endif /* HAVE_SPICE_GL */
|
||||
|
||||
static void qemu_spice_display_init_one(QemuConsole *con)
|
||||
@ -1131,6 +1133,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
|
||||
#ifdef HAVE_SPICE_GL
|
||||
if (spice_opengl) {
|
||||
ssd->dcl.ops = &display_listener_gl_ops;
|
||||
ssd->dgc.ops = &gl_ctx_ops;
|
||||
ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
|
||||
ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
||||
qemu_spice_gl_block_timer, ssd);
|
||||
@ -1145,17 +1148,23 @@ static void qemu_spice_display_init_one(QemuConsole *con)
|
||||
qemu_spice_add_display_interface(&ssd->qxl, con);
|
||||
|
||||
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
|
||||
Error *err = NULL;
|
||||
char device_address[256] = "";
|
||||
if (qemu_spice_fill_device_address(con, device_address, 256)) {
|
||||
if (qemu_console_fill_device_address(con, device_address, 256, &err)) {
|
||||
spice_qxl_set_device_info(&ssd->qxl,
|
||||
device_address,
|
||||
qemu_console_get_head(con),
|
||||
1);
|
||||
} else {
|
||||
error_report_err(err);
|
||||
}
|
||||
#endif
|
||||
|
||||
qemu_spice_create_host_memslot(ssd);
|
||||
|
||||
if (spice_opengl) {
|
||||
qemu_console_set_display_gl_ctx(con, &ssd->dgc);
|
||||
}
|
||||
register_displaychangelistener(&ssd->dcl);
|
||||
}
|
||||
|
||||
|
@ -135,3 +135,18 @@ vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d"
|
||||
vdagent_peer_cap(const char *name) "cap %s"
|
||||
vdagent_cb_grab_selection(const char *name) "selection %s"
|
||||
vdagent_cb_grab_type(const char *name) "type %s"
|
||||
vdagent_cb_serial_discard(uint32_t current, uint32_t received) "current=%u, received=%u"
|
||||
|
||||
# dbus.c
|
||||
dbus_registered_listener(const char *bus_name) "peer %s"
|
||||
dbus_listener_vanished(const char *bus_name) "peer %s"
|
||||
dbus_kbd_press(unsigned int keycode) "keycode %u"
|
||||
dbus_kbd_release(unsigned int keycode) "keycode %u"
|
||||
dbus_mouse_press(unsigned int button) "button %u"
|
||||
dbus_mouse_release(unsigned int button) "button %u"
|
||||
dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u"
|
||||
dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d"
|
||||
dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d"
|
||||
dbus_clipboard_grab_failed(void) ""
|
||||
dbus_clipboard_register(const char *bus_name) "peer %s"
|
||||
dbus_clipboard_unregister(const char *bus_name) "peer %s"
|
||||
|
75
ui/util.c
Normal file
75
ui/util.c
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/pci_bus.h"
|
||||
#include "qapi/error.h"
|
||||
#include "ui/console.h"
|
||||
|
||||
/*
|
||||
* Recursively (in reverse order) appends addresses of PCI devices as it moves
|
||||
* up in the PCI hierarchy.
|
||||
*
|
||||
* @returns true on success, false when the buffer wasn't large enough
|
||||
*/
|
||||
static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
|
||||
{
|
||||
PCIBus *bus = pci_get_bus(pci);
|
||||
/*
|
||||
* equivalent to if (!pci_bus_is_root(bus)), but the function is not built
|
||||
* with PCI_CONFIG=n, avoid using an #ifdef by checking directly
|
||||
*/
|
||||
if (bus->parent_dev != NULL) {
|
||||
append_pci_address(buf, buf_size, bus->parent_dev);
|
||||
}
|
||||
|
||||
size_t len = strlen(buf);
|
||||
ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
|
||||
PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
|
||||
|
||||
return written > 0 && written < buf_size - len;
|
||||
}
|
||||
|
||||
bool qemu_console_fill_device_address(QemuConsole *con,
|
||||
char *device_address,
|
||||
size_t size,
|
||||
Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
|
||||
"device",
|
||||
&error_abort));
|
||||
PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
|
||||
TYPE_PCI_DEVICE);
|
||||
|
||||
if (pci == NULL) {
|
||||
error_setg(errp, "Setting device address of a display device: "
|
||||
"Not a PCI device.");
|
||||
return false;
|
||||
}
|
||||
|
||||
strncpy(device_address, "pci/0000", size);
|
||||
if (!append_pci_address(device_address, size, pci)) {
|
||||
error_setg(errp, "Setting device address of a display device: "
|
||||
"Too many PCI devices in the chain.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
94
ui/vdagent.c
94
ui/vdagent.c
@ -17,6 +17,14 @@
|
||||
|
||||
#include "spice/vd_agent.h"
|
||||
|
||||
#define CHECK_SPICE_PROTOCOL_VERSION(major, minor, micro) \
|
||||
(CONFIG_SPICE_PROTOCOL_MAJOR > (major) || \
|
||||
(CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \
|
||||
CONFIG_SPICE_PROTOCOL_MINOR > (minor)) || \
|
||||
(CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \
|
||||
CONFIG_SPICE_PROTOCOL_MINOR == (minor) && \
|
||||
CONFIG_SPICE_PROTOCOL_MICRO >= (micro)))
|
||||
|
||||
#define VDAGENT_BUFFER_LIMIT (1 * MiB)
|
||||
#define VDAGENT_MOUSE_DEFAULT true
|
||||
#define VDAGENT_CLIPBOARD_DEFAULT false
|
||||
@ -51,6 +59,7 @@ struct VDAgentChardev {
|
||||
|
||||
/* clipboard */
|
||||
QemuClipboardPeer cbpeer;
|
||||
uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||
uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||
};
|
||||
typedef struct VDAgentChardev VDAgentChardev;
|
||||
@ -79,8 +88,10 @@ static const char *cap_name[] = {
|
||||
[VD_AGENT_CAP_MONITORS_CONFIG_POSITION] = "monitors-config-position",
|
||||
[VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled",
|
||||
[VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors",
|
||||
#if 0
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
|
||||
[VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
|
||||
#endif
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
|
||||
[VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
|
||||
[VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial",
|
||||
#endif
|
||||
@ -102,7 +113,7 @@ static const char *msg_name[] = {
|
||||
[VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected",
|
||||
[VD_AGENT_MAX_CLIPBOARD] = "max-clipboard",
|
||||
[VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync",
|
||||
#if 0
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
|
||||
[VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
|
||||
#endif
|
||||
};
|
||||
@ -120,7 +131,7 @@ static const char *type_name[] = {
|
||||
[VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp",
|
||||
[VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
|
||||
[VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg",
|
||||
#if 0
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3)
|
||||
[VD_AGENT_CLIPBOARD_FILE_LIST] = "files",
|
||||
#endif
|
||||
};
|
||||
@ -193,6 +204,9 @@ static void vdagent_send_caps(VDAgentChardev *vd)
|
||||
if (vd->clipboard) {
|
||||
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
|
||||
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
|
||||
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL);
|
||||
#endif
|
||||
}
|
||||
|
||||
vdagent_send_msg(vd, msg);
|
||||
@ -323,7 +337,8 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
|
||||
{
|
||||
g_autofree VDAgentMessage *msg =
|
||||
g_malloc0(sizeof(VDAgentMessage) +
|
||||
sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));
|
||||
sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1) +
|
||||
sizeof(uint32_t));
|
||||
uint8_t *s = msg->data;
|
||||
uint32_t *data = (uint32_t *)msg->data;
|
||||
uint32_t q, type;
|
||||
@ -336,6 +351,19 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
|
||||
return;
|
||||
}
|
||||
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
|
||||
if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
|
||||
if (!info->has_serial) {
|
||||
/* client should win */
|
||||
info->serial = vd->last_serial[info->selection]++;
|
||||
info->has_serial = true;
|
||||
}
|
||||
*data = info->serial;
|
||||
data++;
|
||||
msg->size += sizeof(uint32_t);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
|
||||
type = type_qemu_to_vdagent(q);
|
||||
if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
|
||||
@ -407,10 +435,9 @@ static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd,
|
||||
vdagent_send_clipboard_data(vd, info, type);
|
||||
}
|
||||
|
||||
static void vdagent_clipboard_notify(Notifier *notifier, void *data)
|
||||
static void vdagent_clipboard_update_info(VDAgentChardev *vd,
|
||||
QemuClipboardInfo *info)
|
||||
{
|
||||
VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update);
|
||||
QemuClipboardInfo *info = data;
|
||||
QemuClipboardSelection s = info->selection;
|
||||
QemuClipboardType type;
|
||||
bool self_update = info->owner == &vd->cbpeer;
|
||||
@ -439,6 +466,31 @@ static void vdagent_clipboard_notify(Notifier *notifier, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static void vdagent_clipboard_reset_serial(VDAgentChardev *vd)
|
||||
{
|
||||
Chardev *chr = CHARDEV(vd);
|
||||
|
||||
/* reopen the agent connection to reset the serial state */
|
||||
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
|
||||
qemu_chr_be_event(chr, CHR_EVENT_OPENED);
|
||||
}
|
||||
|
||||
static void vdagent_clipboard_notify(Notifier *notifier, void *data)
|
||||
{
|
||||
VDAgentChardev *vd =
|
||||
container_of(notifier, VDAgentChardev, cbpeer.notifier);
|
||||
QemuClipboardNotify *notify = data;
|
||||
|
||||
switch (notify->type) {
|
||||
case QEMU_CLIPBOARD_UPDATE_INFO:
|
||||
vdagent_clipboard_update_info(vd, notify->info);
|
||||
return;
|
||||
case QEMU_CLIPBOARD_RESET_SERIAL:
|
||||
vdagent_clipboard_reset_serial(vd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void vdagent_clipboard_request(QemuClipboardInfo *info,
|
||||
QemuClipboardType qtype)
|
||||
{
|
||||
@ -472,6 +524,24 @@ static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t
|
||||
|
||||
trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
|
||||
info = qemu_clipboard_info_new(&vd->cbpeer, s);
|
||||
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
|
||||
if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
|
||||
if (size < sizeof(uint32_t)) {
|
||||
/* this shouldn't happen! */
|
||||
return;
|
||||
}
|
||||
|
||||
info->has_serial = true;
|
||||
info->serial = *(uint32_t *)data;
|
||||
if (info->serial < vd->last_serial[s]) {
|
||||
/* discard lower-ordering guest grab */
|
||||
return;
|
||||
}
|
||||
vd->last_serial[s] = info->serial;
|
||||
data += sizeof(uint32_t);
|
||||
size -= sizeof(uint32_t);
|
||||
}
|
||||
#endif
|
||||
if (size > sizeof(uint32_t) * 10) {
|
||||
/*
|
||||
* spice has 6 types as of 2021. Limiting to 10 entries
|
||||
@ -648,9 +718,10 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
|
||||
if (have_mouse(vd) && vd->mouse_hs) {
|
||||
qemu_input_handler_activate(vd->mouse_hs);
|
||||
}
|
||||
if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) {
|
||||
if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) {
|
||||
memset(vd->last_serial, 0, sizeof(vd->last_serial));
|
||||
vd->cbpeer.name = "vdagent";
|
||||
vd->cbpeer.update.notify = vdagent_clipboard_notify;
|
||||
vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
|
||||
vd->cbpeer.request = vdagent_clipboard_request;
|
||||
qemu_clipboard_peer_register(&vd->cbpeer);
|
||||
}
|
||||
@ -789,7 +860,7 @@ static void vdagent_disconnect(VDAgentChardev *vd)
|
||||
if (vd->mouse_hs) {
|
||||
qemu_input_handler_deactivate(vd->mouse_hs);
|
||||
}
|
||||
if (vd->cbpeer.update.notify) {
|
||||
if (vd->cbpeer.notifier.notify) {
|
||||
qemu_clipboard_peer_unregister(&vd->cbpeer);
|
||||
memset(&vd->cbpeer, 0, sizeof(vd->cbpeer));
|
||||
}
|
||||
@ -797,11 +868,8 @@ static void vdagent_disconnect(VDAgentChardev *vd)
|
||||
|
||||
static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
|
||||
{
|
||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
|
||||
|
||||
if (!fe_open) {
|
||||
trace_vdagent_close();
|
||||
vdagent_disconnect(vd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -189,10 +189,8 @@ static void vnc_clipboard_provide(VncState *vs,
|
||||
vnc_flush(vs);
|
||||
}
|
||||
|
||||
static void vnc_clipboard_notify(Notifier *notifier, void *data)
|
||||
static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
|
||||
{
|
||||
VncState *vs = container_of(notifier, VncState, cbpeer.update);
|
||||
QemuClipboardInfo *info = data;
|
||||
QemuClipboardType type;
|
||||
bool self_update = info->owner == &vs->cbpeer;
|
||||
uint32_t flags = 0;
|
||||
@ -223,6 +221,21 @@ static void vnc_clipboard_notify(Notifier *notifier, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static void vnc_clipboard_notify(Notifier *notifier, void *data)
|
||||
{
|
||||
VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
|
||||
QemuClipboardNotify *notify = data;
|
||||
|
||||
switch (notify->type) {
|
||||
case QEMU_CLIPBOARD_UPDATE_INFO:
|
||||
vnc_clipboard_update_info(vs, notify->info);
|
||||
return;
|
||||
case QEMU_CLIPBOARD_RESET_SERIAL:
|
||||
/* ignore */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void vnc_clipboard_request(QemuClipboardInfo *info,
|
||||
QemuClipboardType type)
|
||||
{
|
||||
@ -316,9 +329,9 @@ void vnc_server_cut_text_caps(VncState *vs)
|
||||
caps[1] = 0;
|
||||
vnc_clipboard_send(vs, 2, caps);
|
||||
|
||||
if (!vs->cbpeer.update.notify) {
|
||||
if (!vs->cbpeer.notifier.notify) {
|
||||
vs->cbpeer.name = "vnc";
|
||||
vs->cbpeer.update.notify = vnc_clipboard_notify;
|
||||
vs->cbpeer.notifier.notify = vnc_clipboard_notify;
|
||||
vs->cbpeer.request = vnc_clipboard_request;
|
||||
qemu_clipboard_peer_register(&vs->cbpeer);
|
||||
}
|
||||
|
4
ui/vnc.c
4
ui/vnc.c
@ -1354,7 +1354,7 @@ void vnc_disconnect_finish(VncState *vs)
|
||||
/* last client gone */
|
||||
vnc_update_server_surface(vs->vd);
|
||||
}
|
||||
if (vs->cbpeer.update.notify) {
|
||||
if (vs->cbpeer.notifier.notify) {
|
||||
qemu_clipboard_peer_unregister(&vs->cbpeer);
|
||||
}
|
||||
|
||||
@ -2596,7 +2596,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.width = w;
|
||||
info.height = h;
|
||||
dpy_set_ui_info(vs->vd->dcl.con, &info);
|
||||
dpy_set_ui_info(vs->vd->dcl.con, &info, false);
|
||||
vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
|
||||
} else {
|
||||
vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);
|
||||
|
Loading…
Reference in New Issue
Block a user