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>
|
M: Marc-André Lureau <marcandre.lureau@redhat.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: backends/dbus-vmstate.c
|
F: backends/dbus-vmstate.c
|
||||||
F: tests/dbus-vmstate*
|
F: ui/dbus*
|
||||||
|
F: audio/dbus*
|
||||||
F: util/dbus.c
|
F: util/dbus.c
|
||||||
|
F: include/ui/dbus*
|
||||||
F: include/qemu/dbus.h
|
F: include/qemu/dbus.h
|
||||||
F: docs/interop/dbus.rst
|
F: docs/interop/dbus*
|
||||||
F: docs/interop/dbus-vmstate.rst
|
F: docs/sphinx/dbus*
|
||||||
|
F: docs/sphinx/fakedbusdoc.py
|
||||||
|
F: tests/qtest/dbus*
|
||||||
|
|
||||||
Seccomp
|
Seccomp
|
||||||
M: Eduardo Otubo <otubo@redhat.com>
|
M: Eduardo Otubo <otubo@redhat.com>
|
||||||
|
@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)
|
|||||||
CASE(NONE, none, );
|
CASE(NONE, none, );
|
||||||
CASE(ALSA, alsa, Alsa);
|
CASE(ALSA, alsa, Alsa);
|
||||||
CASE(COREAUDIO, coreaudio, Coreaudio);
|
CASE(COREAUDIO, coreaudio, Coreaudio);
|
||||||
|
CASE(DBUS, dbus, );
|
||||||
CASE(DSOUND, dsound, );
|
CASE(DSOUND, dsound, );
|
||||||
CASE(JACK, jack, Jack);
|
CASE(JACK, jack, Jack);
|
||||||
CASE(OSS, oss, Oss);
|
CASE(OSS, oss, Oss);
|
||||||
|
@ -31,6 +31,10 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "mixeng.h"
|
#include "mixeng.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_GIO
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
struct audio_pcm_ops;
|
struct audio_pcm_ops;
|
||||||
|
|
||||||
struct audio_callback {
|
struct audio_callback {
|
||||||
@ -140,6 +144,9 @@ struct audio_driver {
|
|||||||
const char *descr;
|
const char *descr;
|
||||||
void *(*init) (Audiodev *);
|
void *(*init) (Audiodev *);
|
||||||
void (*fini) (void *);
|
void (*fini) (void *);
|
||||||
|
#ifdef CONFIG_GIO
|
||||||
|
void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
|
||||||
|
#endif
|
||||||
struct audio_pcm_ops *pcm_ops;
|
struct audio_pcm_ops *pcm_ops;
|
||||||
int can_be_default;
|
int can_be_default;
|
||||||
int max_voices_out;
|
int max_voices_out;
|
||||||
|
@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
|
|||||||
case AUDIODEV_DRIVER_COREAUDIO:
|
case AUDIODEV_DRIVER_COREAUDIO:
|
||||||
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
|
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
|
||||||
dev->u.coreaudio.TYPE);
|
dev->u.coreaudio.TYPE);
|
||||||
|
case AUDIODEV_DRIVER_DBUS:
|
||||||
|
return dev->u.dbus.TYPE;
|
||||||
case AUDIODEV_DRIVER_DSOUND:
|
case AUDIODEV_DRIVER_DSOUND:
|
||||||
return dev->u.dsound.TYPE;
|
return dev->u.dsound.TYPE;
|
||||||
case AUDIODEV_DRIVER_JACK:
|
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
|
endif
|
||||||
endforeach
|
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}
|
modules += {'audio': audio_modules}
|
||||||
|
@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
|
|||||||
# ossaudio.c
|
# ossaudio.c
|
||||||
oss_version(int version) "OSS version = 0x%x"
|
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.c
|
||||||
audio_timer_start(int interval) "interval %d ms"
|
audio_timer_start(int interval) "interval %d ms"
|
||||||
audio_timer_stop(void) ""
|
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 "qemu/osdep.h"
|
||||||
#include "chardev/char.h"
|
#include "chardev/char.h"
|
||||||
#include "io/channel-socket.h"
|
#include "io/channel-socket.h"
|
||||||
#include "io/channel-tls.h"
|
|
||||||
#include "io/channel-websock.h"
|
#include "io/channel-websock.h"
|
||||||
#include "io/net-listener.h"
|
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qemu/option.h"
|
#include "qemu/option.h"
|
||||||
@ -37,61 +35,7 @@
|
|||||||
#include "qemu/yank.h"
|
#include "qemu/yank.h"
|
||||||
|
|
||||||
#include "chardev/char-io.h"
|
#include "chardev/char-io.h"
|
||||||
#include "qom/object.h"
|
#include "chardev/char-socket.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)
|
|
||||||
|
|
||||||
static gboolean socket_reconnect_timeout(gpointer opaque);
|
static gboolean socket_reconnect_timeout(gpointer opaque);
|
||||||
static void tcp_chr_telnet_init(Chardev *chr);
|
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);
|
qio_net_listener_set_name(s->listener, name);
|
||||||
g_free(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) {
|
if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
|
||||||
object_unref(OBJECT(s->listener));
|
object_unref(OBJECT(s->listener));
|
||||||
s->listener = NULL;
|
s->listener = NULL;
|
||||||
@ -1256,6 +1204,8 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
|
|||||||
|
|
||||||
qapi_free_SocketAddress(s->addr);
|
qapi_free_SocketAddress(s->addr);
|
||||||
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
|
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
|
||||||
|
|
||||||
|
skip_listen:
|
||||||
update_disconnected_filename(s);
|
update_disconnected_filename(s);
|
||||||
|
|
||||||
if (is_waitconnect) {
|
if (is_waitconnect) {
|
||||||
@ -1466,9 +1416,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
|
|||||||
SocketAddressLegacy *addr;
|
SocketAddressLegacy *addr;
|
||||||
ChardevSocket *sock;
|
ChardevSocket *sock;
|
||||||
|
|
||||||
if ((!!path + !!fd + !!host) != 1) {
|
if ((!!path + !!fd + !!host) > 1) {
|
||||||
error_setg(errp,
|
error_setg(errp,
|
||||||
"Exactly one of 'path', 'fd' or 'host' required");
|
"None or one of 'path', 'fd' or 'host' option required.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1542,12 +1492,10 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
|
|||||||
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
|
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
|
||||||
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
|
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
|
||||||
};
|
};
|
||||||
} else if (fd) {
|
} else {
|
||||||
addr->type = SOCKET_ADDRESS_TYPE_FD;
|
addr->type = SOCKET_ADDRESS_TYPE_FD;
|
||||||
addr->u.fd.data = g_new(String, 1);
|
addr->u.fd.data = g_new(String, 1);
|
||||||
addr->u.fd.data->str = g_strdup(fd);
|
addr->u.fd.data->str = g_strdup(fd);
|
||||||
} else {
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
}
|
||||||
sock->addr = addr;
|
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 "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
|
||||||
echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
|
echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
|
||||||
echo "GLIB_LIBS=$glib_libs" >> $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 "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
|
||||||
echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
|
echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
|
||||||
echo "EXESUF=$EXESUF" >> $config_host_mak
|
echo "EXESUF=$EXESUF" >> $config_host_mak
|
||||||
|
@ -73,6 +73,12 @@ needs_sphinx = '1.6'
|
|||||||
# ones.
|
# ones.
|
||||||
extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
|
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.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = [os.path.join(qemu_docdir, '_templates')]
|
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, '..')
|
kerneldoc_srctree = os.path.join(qemu_docdir, '..')
|
||||||
hxtool_srctree = os.path.join(qemu_docdir, '..')
|
hxtool_srctree = os.path.join(qemu_docdir, '..')
|
||||||
qapidoc_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
|
D-Bus VMState
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Introduction
|
|
||||||
============
|
|
||||||
|
|
||||||
The QEMU dbus-vmstate object's aim is to migrate helpers' data running
|
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
|
on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
|
||||||
some recommendations on D-Bus usage)
|
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
|
helpers by setting its ``id-list`` property, with a comma-separated
|
||||||
``Id`` list.
|
``Id`` list.
|
||||||
|
|
||||||
Interface
|
.. only:: sphinx4
|
||||||
=========
|
|
||||||
|
|
||||||
On object path ``/org/qemu/VMState1``, the following
|
.. dbus-doc:: backends/dbus-vmstate1.xml
|
||||||
``org.qemu.VMState1`` interface should be implemented:
|
|
||||||
|
|
||||||
.. code:: xml
|
.. only:: not sphinx4
|
||||||
|
|
||||||
<interface name="org.qemu.VMState1">
|
.. warning::
|
||||||
<property name="Id" type="s" access="read"/>
|
Sphinx 4 is required to build D-Bus documentation.
|
||||||
<method name="Load">
|
|
||||||
<arg type="ay" name="data" direction="in"/>
|
|
||||||
</method>
|
|
||||||
<method name="Save">
|
|
||||||
<arg type="ay" name="data" direction="out"/>
|
|
||||||
</method>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
"Id" property
|
This is the content of ``backends/dbus-vmstate1.xml``:
|
||||||
-------------
|
|
||||||
|
|
||||||
A string that identifies the helper uniquely. (maximum 256 bytes
|
.. literalinclude:: ../../backends/dbus-vmstate1.xml
|
||||||
including terminating NUL byte)
|
:language: xml
|
||||||
|
|
||||||
.. 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.
|
|
||||||
|
@ -108,3 +108,5 @@ QEMU Interfaces
|
|||||||
===============
|
===============
|
||||||
|
|
||||||
:doc:`dbus-vmstate`
|
:doc:`dbus-vmstate`
|
||||||
|
|
||||||
|
:doc:`dbus-display`
|
||||||
|
@ -12,6 +12,7 @@ are useful for making QEMU interoperate with other software.
|
|||||||
bitmaps
|
bitmaps
|
||||||
dbus
|
dbus
|
||||||
dbus-vmstate
|
dbus-vmstate
|
||||||
|
dbus-display
|
||||||
live-block-operations
|
live-block-operations
|
||||||
pr-helper
|
pr-helper
|
||||||
qemu-ga
|
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 */
|
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
|
||||||
|
Error *err = NULL;
|
||||||
char device_address[256] = "";
|
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,
|
spice_qxl_set_device_info(&qxl->ssd.qxl,
|
||||||
device_address,
|
device_address,
|
||||||
0,
|
0,
|
||||||
qxl->max_outputs);
|
qxl->max_outputs);
|
||||||
|
} else {
|
||||||
|
error_report_err(err);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -254,8 +254,8 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
|
|||||||
vhost_user_gpu_unblock(g);
|
vhost_user_gpu_unblock(g);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
dpy_gl_update(con, m->x, m->y, m->width, m->height);
|
|
||||||
g->backend_blocked = true;
|
g->backend_blocked = true;
|
||||||
|
dpy_gl_update(con, m->x, m->y, m->width, m->height);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case VHOST_USER_GPU_UPDATE: {
|
case VHOST_USER_GPU_UPDATE: {
|
||||||
|
@ -117,6 +117,10 @@ virtio_gpu_gl_block(void *opaque, bool block)
|
|||||||
g->renderer_blocked--;
|
g->renderer_blocked--;
|
||||||
}
|
}
|
||||||
assert(g->renderer_blocked >= 0);
|
assert(g->renderer_blocked >= 0);
|
||||||
|
|
||||||
|
if (!block && g->renderer_blocked == 0) {
|
||||||
|
virtio_gpu_gl_flushed(g);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -143,7 +147,6 @@ static const GraphicHwOps virtio_gpu_ops = {
|
|||||||
.text_update = virtio_gpu_text_update,
|
.text_update = virtio_gpu_text_update,
|
||||||
.ui_info = virtio_gpu_ui_info,
|
.ui_info = virtio_gpu_ui_info,
|
||||||
.gl_block = virtio_gpu_gl_block,
|
.gl_block = virtio_gpu_gl_block,
|
||||||
.gl_flushed = virtio_gpu_gl_flushed,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -175,7 +175,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
|
|||||||
virgl_renderer_force_ctx_0();
|
virgl_renderer_force_ctx_0();
|
||||||
dpy_gl_scanout_texture(
|
dpy_gl_scanout_texture(
|
||||||
g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
|
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,
|
info.width, info.height,
|
||||||
ss.r.x, ss.r.y, ss.r.width, ss.r.height);
|
ss.r.x, ss.r.y, ss.r.width, ss.r.height);
|
||||||
} else {
|
} else {
|
||||||
@ -609,6 +609,7 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
|
|||||||
|
|
||||||
ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
|
ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
|
error_report("virgl could not be initialized: %d", ret);
|
||||||
return 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)
|
static int virtio_vga_base_get_flags(void *opaque)
|
||||||
{
|
{
|
||||||
VirtIOVGABase *vvga = opaque;
|
VirtIOVGABase *vvga = opaque;
|
||||||
@ -93,7 +83,6 @@ static const GraphicHwOps virtio_vga_base_ops = {
|
|||||||
.text_update = virtio_vga_base_text_update,
|
.text_update = virtio_vga_base_text_update,
|
||||||
.ui_info = virtio_vga_base_ui_info,
|
.ui_info = virtio_vga_base_ui_info,
|
||||||
.gl_block = virtio_vga_base_gl_block,
|
.gl_block = virtio_vga_base_gl_block,
|
||||||
.gl_flushed = virtio_vga_base_gl_flushed,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const VMStateDescription vmstate_virtio_vga_base = {
|
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);
|
char *get_relocated_path(const char *dir);
|
||||||
|
|
||||||
|
static inline const char *yes_no(bool b)
|
||||||
|
{
|
||||||
|
return b ? "yes" : "no";
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -12,6 +12,30 @@
|
|||||||
|
|
||||||
#include <gio/gio.h>
|
#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,
|
GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
|
||||||
const char *name,
|
const char *name,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
|
@ -150,4 +150,6 @@ QDict *keyval_parse(const char *params, const char *implied_key,
|
|||||||
bool *help, Error **errp);
|
bool *help, Error **errp);
|
||||||
void keyval_merge(QDict *old, const QDict *new, Error **errp);
|
void keyval_merge(QDict *old, const QDict *new, Error **errp);
|
||||||
|
|
||||||
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(QemuOpts, qemu_opts_del)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,8 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
typedef enum QemuClipboardType QemuClipboardType;
|
typedef enum QemuClipboardType QemuClipboardType;
|
||||||
|
typedef enum QemuClipboardNotifyType QemuClipboardNotifyType;
|
||||||
typedef enum QemuClipboardSelection QemuClipboardSelection;
|
typedef enum QemuClipboardSelection QemuClipboardSelection;
|
||||||
typedef struct QemuClipboardPeer QemuClipboardPeer;
|
typedef struct QemuClipboardPeer QemuClipboardPeer;
|
||||||
|
typedef struct QemuClipboardNotify QemuClipboardNotify;
|
||||||
typedef struct QemuClipboardInfo QemuClipboardInfo;
|
typedef struct QemuClipboardInfo QemuClipboardInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,18 +57,46 @@ enum QemuClipboardSelection {
|
|||||||
* struct QemuClipboardPeer
|
* struct QemuClipboardPeer
|
||||||
*
|
*
|
||||||
* @name: peer name.
|
* @name: peer name.
|
||||||
* @update: notifier for clipboard updates.
|
* @notifier: notifier for clipboard updates.
|
||||||
* @request: callback for clipboard data requests.
|
* @request: callback for clipboard data requests.
|
||||||
*
|
*
|
||||||
* Clipboard peer description.
|
* Clipboard peer description.
|
||||||
*/
|
*/
|
||||||
struct QemuClipboardPeer {
|
struct QemuClipboardPeer {
|
||||||
const char *name;
|
const char *name;
|
||||||
Notifier update;
|
Notifier notifier;
|
||||||
void (*request)(QemuClipboardInfo *info,
|
void (*request)(QemuClipboardInfo *info,
|
||||||
QemuClipboardType type);
|
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
|
* struct QemuClipboardInfo
|
||||||
*
|
*
|
||||||
@ -74,6 +104,8 @@ struct QemuClipboardPeer {
|
|||||||
* @owner: clipboard owner.
|
* @owner: clipboard owner.
|
||||||
* @selection: clipboard selection.
|
* @selection: clipboard selection.
|
||||||
* @types: clipboard data array (one entry per type).
|
* @types: clipboard data array (one entry per type).
|
||||||
|
* @has_serial: whether @serial is available.
|
||||||
|
* @serial: the grab serial counter.
|
||||||
*
|
*
|
||||||
* Clipboard content data and metadata.
|
* Clipboard content data and metadata.
|
||||||
*/
|
*/
|
||||||
@ -81,6 +113,8 @@ struct QemuClipboardInfo {
|
|||||||
uint32_t refcount;
|
uint32_t refcount;
|
||||||
QemuClipboardPeer *owner;
|
QemuClipboardPeer *owner;
|
||||||
QemuClipboardSelection selection;
|
QemuClipboardSelection selection;
|
||||||
|
bool has_serial;
|
||||||
|
uint32_t serial;
|
||||||
struct {
|
struct {
|
||||||
bool available;
|
bool available;
|
||||||
bool requested;
|
bool requested;
|
||||||
@ -140,6 +174,16 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
|
|||||||
*/
|
*/
|
||||||
QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection);
|
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
|
* qemu_clipboard_info_new
|
||||||
*
|
*
|
||||||
@ -188,6 +232,13 @@ void qemu_clipboard_info_unref(QemuClipboardInfo *info);
|
|||||||
*/
|
*/
|
||||||
void qemu_clipboard_update(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
|
* qemu_clipboard_request
|
||||||
*
|
*
|
||||||
|
@ -108,6 +108,17 @@ struct QemuConsoleClass {
|
|||||||
#define QEMU_ALLOCATED_FLAG 0x01
|
#define QEMU_ALLOCATED_FLAG 0x01
|
||||||
#define QEMU_PLACEHOLDER_FLAG 0x02
|
#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 {
|
typedef struct DisplaySurface {
|
||||||
pixman_format_code_t format;
|
pixman_format_code_t format;
|
||||||
pixman_image_t *image;
|
pixman_image_t *image;
|
||||||
@ -178,7 +189,24 @@ typedef struct QemuDmaBuf {
|
|||||||
bool draw_submitted;
|
bool draw_submitted;
|
||||||
} QemuDmaBuf;
|
} 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 DisplayState DisplayState;
|
||||||
|
typedef struct DisplayGLCtx DisplayGLCtx;
|
||||||
|
|
||||||
typedef struct DisplayChangeListenerOps {
|
typedef struct DisplayChangeListenerOps {
|
||||||
const char *dpy_name;
|
const char *dpy_name;
|
||||||
@ -213,16 +241,6 @@ typedef struct DisplayChangeListenerOps {
|
|||||||
void (*dpy_cursor_define)(DisplayChangeListener *dcl,
|
void (*dpy_cursor_define)(DisplayChangeListener *dcl,
|
||||||
QEMUCursor *cursor);
|
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 */
|
/* required if GL */
|
||||||
void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl);
|
void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl);
|
||||||
/* required if GL */
|
/* required if GL */
|
||||||
@ -263,6 +281,26 @@ struct DisplayChangeListener {
|
|||||||
QLIST_ENTRY(DisplayChangeListener) next;
|
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);
|
DisplayState *init_displaystate(void);
|
||||||
DisplaySurface *qemu_create_displaysurface_from(int width, int height,
|
DisplaySurface *qemu_create_displaysurface_from(int width, int height,
|
||||||
pixman_format_code_t format,
|
pixman_format_code_t format,
|
||||||
@ -292,7 +330,7 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl);
|
|||||||
|
|
||||||
bool dpy_ui_info_supported(QemuConsole *con);
|
bool dpy_ui_info_supported(QemuConsole *con);
|
||||||
const QemuUIInfo *dpy_get_ui_info(const 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(QemuConsole *con, int x, int y, int w, int h);
|
||||||
void dpy_gfx_update_full(QemuConsole *con);
|
void dpy_gfx_update_full(QemuConsole *con);
|
||||||
@ -391,7 +429,6 @@ typedef struct GraphicHwOps {
|
|||||||
void (*update_interval)(void *opaque, uint64_t interval);
|
void (*update_interval)(void *opaque, uint64_t interval);
|
||||||
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
|
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
|
||||||
void (*gl_block)(void *opaque, bool block);
|
void (*gl_block)(void *opaque, bool block);
|
||||||
void (*gl_flushed)(void *opaque);
|
|
||||||
} GraphicHwOps;
|
} GraphicHwOps;
|
||||||
|
|
||||||
QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
|
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_invalidate(QemuConsole *con);
|
||||||
void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
|
void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
|
||||||
void graphic_hw_gl_block(QemuConsole *con, bool block);
|
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_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_index(unsigned int index);
|
||||||
QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
|
QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
|
||||||
QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
|
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);
|
int udmabuf_fd(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* util.c */
|
||||||
|
bool qemu_console_fill_device_address(QemuConsole *con,
|
||||||
|
char *device_address,
|
||||||
|
size_t size,
|
||||||
|
Error **errp);
|
||||||
|
|
||||||
#endif
|
#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/console.h"
|
||||||
#include "ui/egl-helpers.h"
|
#include "ui/egl-helpers.h"
|
||||||
|
|
||||||
QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
|
QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params);
|
QEMUGLParams *params);
|
||||||
void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
|
void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
|
||||||
int qemu_egl_make_context_current(DisplayChangeListener *dcl,
|
int qemu_egl_make_context_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx);
|
QEMUGLContext ctx);
|
||||||
|
|
||||||
#endif /* EGL_CONTEXT_H */
|
#endif /* EGL_CONTEXT_H */
|
||||||
|
@ -35,6 +35,7 @@ typedef struct GtkDisplayState GtkDisplayState;
|
|||||||
|
|
||||||
typedef struct VirtualGfxConsole {
|
typedef struct VirtualGfxConsole {
|
||||||
GtkWidget *drawing_area;
|
GtkWidget *drawing_area;
|
||||||
|
DisplayGLCtx dgc;
|
||||||
DisplayChangeListener dcl;
|
DisplayChangeListener dcl;
|
||||||
QKbdState *kbd;
|
QKbdState *kbd;
|
||||||
DisplaySurface *ds;
|
DisplaySurface *ds;
|
||||||
@ -165,7 +166,7 @@ void gd_egl_update(DisplayChangeListener *dcl,
|
|||||||
void gd_egl_refresh(DisplayChangeListener *dcl);
|
void gd_egl_refresh(DisplayChangeListener *dcl);
|
||||||
void gd_egl_switch(DisplayChangeListener *dcl,
|
void gd_egl_switch(DisplayChangeListener *dcl,
|
||||||
DisplaySurface *surface);
|
DisplaySurface *surface);
|
||||||
QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
|
QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params);
|
QEMUGLParams *params);
|
||||||
void gd_egl_scanout_disable(DisplayChangeListener *dcl);
|
void gd_egl_scanout_disable(DisplayChangeListener *dcl);
|
||||||
void gd_egl_scanout_texture(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,
|
void gd_egl_scanout_flush(DisplayChangeListener *dcl,
|
||||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
||||||
void gtk_egl_init(DisplayGLMode mode);
|
void gtk_egl_init(DisplayGLMode mode);
|
||||||
int gd_egl_make_current(DisplayChangeListener *dcl,
|
int gd_egl_make_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx);
|
QEMUGLContext ctx);
|
||||||
|
|
||||||
/* ui/gtk-gl-area.c */
|
/* 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_refresh(DisplayChangeListener *dcl);
|
||||||
void gd_gl_area_switch(DisplayChangeListener *dcl,
|
void gd_gl_area_switch(DisplayChangeListener *dcl,
|
||||||
DisplaySurface *surface);
|
DisplaySurface *surface);
|
||||||
QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
|
QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params);
|
QEMUGLParams *params);
|
||||||
void gd_gl_area_destroy_context(DisplayChangeListener *dcl,
|
void gd_gl_area_destroy_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx);
|
QEMUGLContext ctx);
|
||||||
void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
|
void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
|
||||||
QemuDmaBuf *dmabuf);
|
QemuDmaBuf *dmabuf);
|
||||||
@ -215,7 +216,7 @@ void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
|
|||||||
void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
|
void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
|
||||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
|
||||||
void gtk_gl_area_init(void);
|
void gtk_gl_area_init(void);
|
||||||
int gd_gl_area_make_current(DisplayChangeListener *dcl,
|
int gd_gl_area_make_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx);
|
QEMUGLContext ctx);
|
||||||
|
|
||||||
/* gtk-clipboard.c */
|
/* gtk-clipboard.c */
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct sdl2_console {
|
struct sdl2_console {
|
||||||
|
DisplayGLCtx dgc;
|
||||||
DisplayChangeListener dcl;
|
DisplayChangeListener dcl;
|
||||||
DisplaySurface *surface;
|
DisplaySurface *surface;
|
||||||
DisplayOptions *opts;
|
DisplayOptions *opts;
|
||||||
@ -65,10 +66,10 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
|
|||||||
void sdl2_gl_refresh(DisplayChangeListener *dcl);
|
void sdl2_gl_refresh(DisplayChangeListener *dcl);
|
||||||
void sdl2_gl_redraw(struct sdl2_console *scon);
|
void sdl2_gl_redraw(struct sdl2_console *scon);
|
||||||
|
|
||||||
QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
|
QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params);
|
QEMUGLParams *params);
|
||||||
void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
|
void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
|
||||||
int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
|
int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx);
|
QEMUGLContext ctx);
|
||||||
|
|
||||||
void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
|
void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
|
||||||
|
@ -86,6 +86,7 @@ typedef struct SimpleSpiceCursor SimpleSpiceCursor;
|
|||||||
|
|
||||||
struct SimpleSpiceDisplay {
|
struct SimpleSpiceDisplay {
|
||||||
DisplaySurface *ds;
|
DisplaySurface *ds;
|
||||||
|
DisplayGLCtx dgc;
|
||||||
DisplayChangeListener dcl;
|
DisplayChangeListener dcl;
|
||||||
void *buf;
|
void *buf;
|
||||||
int bufsize;
|
int bufsize;
|
||||||
@ -183,8 +184,4 @@ void qemu_spice_display_start(void);
|
|||||||
void qemu_spice_display_stop(void);
|
void qemu_spice_display_stop(void);
|
||||||
int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
|
int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
|
||||||
|
|
||||||
bool qemu_spice_fill_device_address(QemuConsole *con,
|
|
||||||
char *device_address,
|
|
||||||
size_t size);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
22
meson.build
22
meson.build
@ -404,14 +404,16 @@ endif
|
|||||||
add_project_arguments(config_host['GLIB_CFLAGS'].split(),
|
add_project_arguments(config_host['GLIB_CFLAGS'].split(),
|
||||||
native: false, language: ['c', 'cpp', 'objc'])
|
native: false, language: ['c', 'cpp', 'objc'])
|
||||||
glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
|
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)
|
# override glib dep with the configure results (for subprojects)
|
||||||
meson.override_dependency('glib-2.0', glib)
|
meson.override_dependency('glib-2.0', glib)
|
||||||
|
|
||||||
gio = not_found
|
gio = not_found
|
||||||
if 'CONFIG_GIO' in config_host
|
if 'CONFIG_GIO' in config_host
|
||||||
gio = declare_dependency(compile_args: config_host['GIO_CFLAGS'].split(),
|
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
|
endif
|
||||||
lttng = not_found
|
lttng = not_found
|
||||||
if 'ust' in get_option('trace_backends')
|
if 'ust' in get_option('trace_backends')
|
||||||
@ -1395,6 +1397,15 @@ endif
|
|||||||
have_host_block_device = (targetos != 'darwin' or
|
have_host_block_device = (targetos != 'darwin' or
|
||||||
cc.has_header('IOKit/storage/IOMedia.h'))
|
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_virtfs = (targetos == 'linux' and
|
||||||
have_system and
|
have_system and
|
||||||
libattr.found() 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', fuse.found())
|
||||||
config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
|
config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
|
||||||
config_host_data.set('CONFIG_SPICE_PROTOCOL', spice_protocol.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_SPICE', spice.found())
|
||||||
config_host_data.set('CONFIG_X11', x11.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_CFI', get_option('cfi'))
|
||||||
config_host_data.set('CONFIG_SELINUX', selinux.found())
|
config_host_data.set('CONFIG_SELINUX', selinux.found())
|
||||||
config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
|
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')
|
if 'simple' in get_option('trace_backends')
|
||||||
summary_info += {'Trace output file': get_option('trace_file') + '-<pid>'}
|
summary_info += {'Trace output file': get_option('trace_file') + '-<pid>'}
|
||||||
endif
|
endif
|
||||||
|
summary_info += {'D-Bus display': dbus_display}
|
||||||
summary_info += {'QOM debugging': config_host.has_key('CONFIG_QOM_CAST_DEBUG')}
|
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-kernel support': config_host.has_key('CONFIG_VHOST_KERNEL')}
|
||||||
summary_info += {'vhost-net support': config_host.has_key('CONFIG_VHOST_NET')}
|
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')
|
description: 'Verbose errors in case of CFI violation')
|
||||||
option('multiprocess', type: 'feature', value: 'auto',
|
option('multiprocess', type: 'feature', value: 'auto',
|
||||||
description: 'Out of process device emulation support')
|
description: 'Out of process device emulation support')
|
||||||
|
option('dbus_display', type: 'feature', value: 'auto',
|
||||||
|
description: '-display dbus support')
|
||||||
|
|
||||||
option('attr', type : 'feature', value : 'auto',
|
option('attr', type : 'feature', value : 'auto',
|
||||||
description: 'attr/xattr support')
|
description: 'attr/xattr support')
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "chardev/char.h"
|
#include "chardev/char.h"
|
||||||
#include "ui/qemu-spice.h"
|
#include "ui/qemu-spice.h"
|
||||||
#include "ui/console.h"
|
#include "ui/console.h"
|
||||||
|
#include "ui/dbus-display.h"
|
||||||
#include "sysemu/kvm.h"
|
#include "sysemu/kvm.h"
|
||||||
#include "sysemu/runstate.h"
|
#include "sysemu/runstate.h"
|
||||||
#include "sysemu/runstate-action.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;
|
skipauth = has_skipauth ? skipauth : false;
|
||||||
vnc_display_add_client(NULL, fd, skipauth);
|
vnc_display_add_client(NULL, fd, skipauth);
|
||||||
return;
|
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
|
#endif
|
||||||
} else if ((s = qemu_chr_find(protocol)) != NULL) {
|
} else if ((s = qemu_chr_find(protocol)) != NULL) {
|
||||||
if (qemu_chr_add_client(s, fd) < 0) {
|
if (qemu_chr_add_client(s, fd) < 0) {
|
||||||
|
@ -386,7 +386,7 @@
|
|||||||
# Since: 4.0
|
# Since: 4.0
|
||||||
##
|
##
|
||||||
{ 'enum': 'AudiodevDriver',
|
{ 'enum': 'AudiodevDriver',
|
||||||
'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',
|
'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',
|
||||||
'sdl', 'spice', 'wav' ] }
|
'sdl', 'spice', 'wav' ] }
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -412,6 +412,7 @@
|
|||||||
'none': 'AudiodevGenericOptions',
|
'none': 'AudiodevGenericOptions',
|
||||||
'alsa': 'AudiodevAlsaOptions',
|
'alsa': 'AudiodevAlsaOptions',
|
||||||
'coreaudio': 'AudiodevCoreaudioOptions',
|
'coreaudio': 'AudiodevCoreaudioOptions',
|
||||||
|
'dbus': 'AudiodevGenericOptions',
|
||||||
'dsound': 'AudiodevDsoundOptions',
|
'dsound': 'AudiodevDsoundOptions',
|
||||||
'jack': 'AudiodevJackOptions',
|
'jack': 'AudiodevJackOptions',
|
||||||
'oss': 'AudiodevOssOptions',
|
'oss': 'AudiodevOssOptions',
|
||||||
|
@ -358,6 +358,20 @@
|
|||||||
'base': 'ChardevCommon',
|
'base': 'ChardevCommon',
|
||||||
'if': 'CONFIG_SPICE' }
|
'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:
|
# @ChardevVC:
|
||||||
#
|
#
|
||||||
@ -422,6 +436,7 @@
|
|||||||
# @spicevmc: Since 1.5
|
# @spicevmc: Since 1.5
|
||||||
# @spiceport: Since 1.5
|
# @spiceport: Since 1.5
|
||||||
# @qemu-vdagent: Since 6.1
|
# @qemu-vdagent: Since 6.1
|
||||||
|
# @dbus: Since 7.0
|
||||||
# @vc: v1.5
|
# @vc: v1.5
|
||||||
# @ringbuf: Since 1.6
|
# @ringbuf: Since 1.6
|
||||||
# @memory: Since 1.5
|
# @memory: Since 1.5
|
||||||
@ -447,6 +462,7 @@
|
|||||||
{ 'name': 'spicevmc', 'if': 'CONFIG_SPICE' },
|
{ 'name': 'spicevmc', 'if': 'CONFIG_SPICE' },
|
||||||
{ 'name': 'spiceport', 'if': 'CONFIG_SPICE' },
|
{ 'name': 'spiceport', 'if': 'CONFIG_SPICE' },
|
||||||
{ 'name': 'qemu-vdagent', 'if': 'CONFIG_SPICE_PROTOCOL' },
|
{ 'name': 'qemu-vdagent', 'if': 'CONFIG_SPICE_PROTOCOL' },
|
||||||
|
{ 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' },
|
||||||
'vc',
|
'vc',
|
||||||
'ringbuf',
|
'ringbuf',
|
||||||
# next one is just for compatibility
|
# next one is just for compatibility
|
||||||
@ -535,6 +551,15 @@
|
|||||||
'data': { 'data': 'ChardevQemuVDAgent' },
|
'data': { 'data': 'ChardevQemuVDAgent' },
|
||||||
'if': 'CONFIG_SPICE_PROTOCOL' }
|
'if': 'CONFIG_SPICE_PROTOCOL' }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @ChardevDBusWrapper:
|
||||||
|
#
|
||||||
|
# Since: 7.0
|
||||||
|
##
|
||||||
|
{ 'struct': 'ChardevDBusWrapper',
|
||||||
|
'data': { 'data': 'ChardevDBus' },
|
||||||
|
'if': 'CONFIG_DBUS_DISPLAY' }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @ChardevVCWrapper:
|
# @ChardevVCWrapper:
|
||||||
#
|
#
|
||||||
@ -582,6 +607,8 @@
|
|||||||
'if': 'CONFIG_SPICE' },
|
'if': 'CONFIG_SPICE' },
|
||||||
'qemu-vdagent': { 'type': 'ChardevQemuVDAgentWrapper',
|
'qemu-vdagent': { 'type': 'ChardevQemuVDAgentWrapper',
|
||||||
'if': 'CONFIG_SPICE_PROTOCOL' },
|
'if': 'CONFIG_SPICE_PROTOCOL' },
|
||||||
|
'dbus': { 'type': 'ChardevDBusWrapper',
|
||||||
|
'if': 'CONFIG_DBUS_DISPLAY' },
|
||||||
'vc': 'ChardevVCWrapper',
|
'vc': 'ChardevVCWrapper',
|
||||||
'ringbuf': 'ChardevRingbufWrapper',
|
'ringbuf': 'ChardevRingbufWrapper',
|
||||||
# next one is just for compatibility
|
# next one is just for compatibility
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
# Allow client connections for VNC, Spice and socket based
|
# Allow client connections for VNC, Spice and socket based
|
||||||
# character devices to be passed in to QEMU via SCM_RIGHTS.
|
# character devices to be passed in to QEMU via SCM_RIGHTS.
|
||||||
#
|
#
|
||||||
# @protocol: protocol name. Valid names are "vnc", "spice" or the
|
# @protocol: protocol name. Valid names are "vnc", "spice", "@dbus-display" or
|
||||||
# name of a character device (eg. from -chardev id=XXXX)
|
# the name of a character device (eg. from -chardev id=XXXX)
|
||||||
#
|
#
|
||||||
# @fdname: file descriptor name previously passed via 'getfd' command
|
# @fdname: file descriptor name previously passed via 'getfd' command
|
||||||
#
|
#
|
||||||
|
34
qapi/ui.json
34
qapi/ui.json
@ -1121,6 +1121,30 @@
|
|||||||
{ 'struct' : 'DisplayEGLHeadless',
|
{ 'struct' : 'DisplayEGLHeadless',
|
||||||
'data' : { '*rendernode' : 'str' } }
|
'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:
|
# @DisplayGLMode:
|
||||||
#
|
#
|
||||||
@ -1186,6 +1210,8 @@
|
|||||||
# application to connect to it. The server will redirect
|
# application to connect to it. The server will redirect
|
||||||
# the serial console and QEMU monitors. (Since 4.0)
|
# the serial console and QEMU monitors. (Since 4.0)
|
||||||
#
|
#
|
||||||
|
# @dbus: Start a D-Bus service for the display. (Since 7.0)
|
||||||
|
#
|
||||||
# Since: 2.12
|
# Since: 2.12
|
||||||
#
|
#
|
||||||
##
|
##
|
||||||
@ -1199,7 +1225,10 @@
|
|||||||
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
|
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
|
||||||
{ 'name': 'curses', 'if': 'CONFIG_CURSES' },
|
{ 'name': 'curses', 'if': 'CONFIG_CURSES' },
|
||||||
{ 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
|
{ 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
|
||||||
{ 'name': 'spice-app', 'if': 'CONFIG_SPICE'} ] }
|
{ 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
|
||||||
|
{ 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# @DisplayOptions:
|
# @DisplayOptions:
|
||||||
@ -1227,7 +1256,8 @@
|
|||||||
'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
|
'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
|
||||||
'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
|
'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
|
||||||
'egl-headless': { 'type': 'DisplayEGLHeadless',
|
'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
|
#endif
|
||||||
#ifdef CONFIG_SPICE
|
#ifdef CONFIG_SPICE
|
||||||
"-audiodev spice,id=id[,prop[=value][,...]]\n"
|
"-audiodev spice,id=id[,prop[=value][,...]]\n"
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_DBUS_DISPLAY
|
||||||
|
"-audiodev dbus,id=id[,prop[=value][,...]]\n"
|
||||||
#endif
|
#endif
|
||||||
"-audiodev wav,id=id[,prop[=value][,...]]\n"
|
"-audiodev wav,id=id[,prop[=value][,...]]\n"
|
||||||
" path= path of wav file to record\n",
|
" path= path of wav file to record\n",
|
||||||
@ -1862,6 +1865,10 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
|
|||||||
#endif
|
#endif
|
||||||
#if defined(CONFIG_OPENGL)
|
#if defined(CONFIG_OPENGL)
|
||||||
"-display egl-headless[,rendernode=<file>]\n"
|
"-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
|
#endif
|
||||||
"-display none\n"
|
"-display none\n"
|
||||||
" select display backend type\n"
|
" select display backend type\n"
|
||||||
@ -1889,6 +1896,19 @@ SRST
|
|||||||
application. The Spice server will redirect the serial consoles
|
application. The Spice server will redirect the serial consoles
|
||||||
and QEMU monitors. (Since 4.0)
|
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``
|
``sdl``
|
||||||
Display video output via SDL (usually in a separate graphics
|
Display video output via SDL (usually in a separate graphics
|
||||||
window; see the SDL documentation for other possibilities).
|
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" ' coreaudio CoreAudio sound support'
|
||||||
printf "%s\n" ' curl CURL block device driver'
|
printf "%s\n" ' curl CURL block device driver'
|
||||||
printf "%s\n" ' curses curses UI'
|
printf "%s\n" ' curses curses UI'
|
||||||
|
printf "%s\n" ' dbus-display -display dbus support'
|
||||||
printf "%s\n" ' docs Documentations build support'
|
printf "%s\n" ' docs Documentations build support'
|
||||||
printf "%s\n" ' dsound DirectSound sound support'
|
printf "%s\n" ' dsound DirectSound sound support'
|
||||||
printf "%s\n" ' fuse FUSE block device export'
|
printf "%s\n" ' fuse FUSE block device export'
|
||||||
@ -131,6 +132,8 @@ _meson_option_parse() {
|
|||||||
--disable-curl) printf "%s" -Dcurl=disabled ;;
|
--disable-curl) printf "%s" -Dcurl=disabled ;;
|
||||||
--enable-curses) printf "%s" -Dcurses=enabled ;;
|
--enable-curses) printf "%s" -Dcurses=enabled ;;
|
||||||
--disable-curses) printf "%s" -Dcurses=disabled ;;
|
--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 ;;
|
--enable-docs) printf "%s" -Ddocs=enabled ;;
|
||||||
--disable-docs) printf "%s" -Ddocs=disabled ;;
|
--disable-docs) printf "%s" -Ddocs=disabled ;;
|
||||||
--enable-dsound) printf "%s" -Ddsound=enabled ;;
|
--enable-dsound) printf "%s" -Ddsound=enabled ;;
|
||||||
|
@ -51,6 +51,9 @@ def main(args):
|
|||||||
with open('compile_commands.json') as f:
|
with open('compile_commands.json') as f:
|
||||||
compile_commands = json.load(f)
|
compile_commands = json.load(f)
|
||||||
for src in args:
|
for src in args:
|
||||||
|
if not src.endswith('.c'):
|
||||||
|
print("MODINFO_DEBUG skip %s" % src)
|
||||||
|
continue
|
||||||
print("MODINFO_DEBUG src %s" % src)
|
print("MODINFO_DEBUG src %s" % src)
|
||||||
command = find_command(src, target, compile_commands)
|
command = find_command(src, target, compile_commands)
|
||||||
cmdline = process_command(src, command)
|
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,
|
void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
|
||||||
const char *fmt, ...) GCC_FMT_ATTR(4, 5);
|
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:
|
* qtest_qmp_device_del:
|
||||||
* @qts: QTestState instance to operate on
|
* @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);
|
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.
|
* Generic hot-unplugging test via the device_del QMP command.
|
||||||
|
@ -92,13 +92,17 @@ qtests_i386 = \
|
|||||||
'test-x86-cpuid-compat',
|
'test-x86-cpuid-compat',
|
||||||
'numa-test']
|
'numa-test']
|
||||||
|
|
||||||
|
if dbus_display
|
||||||
|
qtests_i386 += ['dbus-display-test']
|
||||||
|
endif
|
||||||
|
|
||||||
dbus_daemon = find_program('dbus-daemon', required: false)
|
dbus_daemon = find_program('dbus-daemon', required: false)
|
||||||
if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
|
if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
|
||||||
# Temporarily disabled due to Patchew failures:
|
# Temporarily disabled due to Patchew failures:
|
||||||
#qtests_i386 += ['dbus-vmstate-test']
|
#qtests_i386 += ['dbus-vmstate-test']
|
||||||
dbus_vmstate1 = custom_target('dbus-vmstate description',
|
dbus_vmstate1 = custom_target('dbus-vmstate description',
|
||||||
output: ['dbus-vmstate1.h', 'dbus-vmstate1.c'],
|
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'],
|
command: [config_host['GDBUS_CODEGEN'],
|
||||||
'@INPUT@',
|
'@INPUT@',
|
||||||
'--interface-prefix', 'org.qemu',
|
'--interface-prefix', 'org.qemu',
|
||||||
@ -265,6 +269,10 @@ qtests = {
|
|||||||
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
|
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dbus_display
|
||||||
|
qtests += {'dbus-display-test': [dbus_display1, gio]}
|
||||||
|
endif
|
||||||
|
|
||||||
qtest_executables = {}
|
qtest_executables = {}
|
||||||
foreach dir : target_dirs
|
foreach dir : target_dirs
|
||||||
if not dir.endswith('-softmmu')
|
if not dir.endswith('-softmmu')
|
||||||
|
@ -8,7 +8,7 @@ static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
|
|||||||
|
|
||||||
void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
|
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)
|
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++) {
|
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
||||||
qemu_clipboard_peer_release(peer, i);
|
qemu_clipboard_peer_release(peer, i);
|
||||||
}
|
}
|
||||||
|
notifier_remove(&peer->notifier);
|
||||||
notifier_remove(&peer->update);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
|
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)
|
void qemu_clipboard_update(QemuClipboardInfo *info)
|
||||||
{
|
{
|
||||||
|
QemuClipboardNotify notify = {
|
||||||
|
.type = QEMU_CLIPBOARD_UPDATE_INFO,
|
||||||
|
.info = info,
|
||||||
|
};
|
||||||
g_autoptr(QemuClipboardInfo) old = NULL;
|
g_autoptr(QemuClipboardInfo) old = NULL;
|
||||||
|
|
||||||
assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
|
assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
|
||||||
|
|
||||||
notifier_list_notify(&clipboard_notifiers, info);
|
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||||
|
|
||||||
old = cbinfo[info->selection];
|
old = cbinfo[info->selection];
|
||||||
cbinfo[info->selection] = qemu_clipboard_info_ref(info);
|
cbinfo[info->selection] = qemu_clipboard_info_ref(info);
|
||||||
@ -110,6 +129,13 @@ void qemu_clipboard_request(QemuClipboardInfo *info,
|
|||||||
info->owner->request(info, type);
|
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,
|
void qemu_clipboard_set_data(QemuClipboardPeer *peer,
|
||||||
QemuClipboardInfo *info,
|
QemuClipboardInfo *info,
|
||||||
QemuClipboardType type,
|
QemuClipboardType type,
|
||||||
|
22
ui/cocoa.m
22
ui/cocoa.m
@ -552,7 +552,7 @@ QemuCocoaView *cocoaView;
|
|||||||
info.width = frameSize.width;
|
info.width = frameSize.width;
|
||||||
info.height = frameSize.height;
|
info.height = frameSize.height;
|
||||||
|
|
||||||
dpy_set_ui_info(dcl.con, &info);
|
dpy_set_ui_info(dcl.con, &info, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidMoveToWindow
|
- (void)viewDidMoveToWindow
|
||||||
@ -1808,14 +1808,12 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
|
|||||||
|
|
||||||
static QemuClipboardPeer cbpeer = {
|
static QemuClipboardPeer cbpeer = {
|
||||||
.name = "cocoa",
|
.name = "cocoa",
|
||||||
.update = { .notify = cocoa_clipboard_notify },
|
.notifier = { .notify = cocoa_clipboard_notify },
|
||||||
.request = cocoa_clipboard_request
|
.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) {
|
if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1831,6 +1829,20 @@ static void cocoa_clipboard_notify(Notifier *notifier, void *data)
|
|||||||
qemu_event_set(&cbevent);
|
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,
|
static void cocoa_clipboard_request(QemuClipboardInfo *info,
|
||||||
QemuClipboardType type)
|
QemuClipboardType type)
|
||||||
{
|
{
|
||||||
|
301
ui/console.c
301
ui/console.c
@ -77,9 +77,11 @@ struct QemuConsole {
|
|||||||
console_type_t console_type;
|
console_type_t console_type;
|
||||||
DisplayState *ds;
|
DisplayState *ds;
|
||||||
DisplaySurface *surface;
|
DisplaySurface *surface;
|
||||||
|
DisplayScanout scanout;
|
||||||
int dcls;
|
int dcls;
|
||||||
DisplayChangeListener *gl;
|
DisplayGLCtx *gl;
|
||||||
bool gl_block;
|
int gl_block;
|
||||||
|
QEMUTimer *gl_unblock_timer;
|
||||||
int window_id;
|
int window_id;
|
||||||
|
|
||||||
/* Graphic console state. */
|
/* Graphic console state. */
|
||||||
@ -145,6 +147,7 @@ static void dpy_refresh(DisplayState *s);
|
|||||||
static DisplayState *get_alloc_displaystate(void);
|
static DisplayState *get_alloc_displaystate(void);
|
||||||
static void text_console_update_cursor_timer(void);
|
static void text_console_update_cursor_timer(void);
|
||||||
static void text_console_update_cursor(void *opaque);
|
static void text_console_update_cursor(void *opaque);
|
||||||
|
static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
|
||||||
|
|
||||||
static void gui_update(void *opaque)
|
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);
|
warn_report("console: no gl-unblock within one second");
|
||||||
|
|
||||||
con->gl_block = block;
|
|
||||||
if (con->hw_ops->gl_block) {
|
|
||||||
con->hw_ops->gl_block(con->hw, block);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void graphic_hw_gl_flushed(QemuConsole *con)
|
void graphic_hw_gl_block(QemuConsole *con, bool block)
|
||||||
{
|
{
|
||||||
|
uint64_t timeout;
|
||||||
assert(con != NULL);
|
assert(con != NULL);
|
||||||
|
|
||||||
if (con->hw_ops->gl_flushed) {
|
if (block) {
|
||||||
con->hw_ops->gl_flushed(con->hw);
|
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;
|
TextCell *cells, *c, *c1;
|
||||||
int w1, x, y, last_width;
|
int w1, x, y, last_width;
|
||||||
|
|
||||||
|
assert(s->scanout.kind == SCANOUT_SURFACE);
|
||||||
|
|
||||||
last_width = s->width;
|
last_width = s->width;
|
||||||
s->width = surface_width(s->surface) / FONT_WIDTH;
|
s->width = surface_width(s->surface) / FONT_WIDTH;
|
||||||
s->height = surface_height(s->surface) / FONT_HEIGHT;
|
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)
|
void console_select(unsigned int index)
|
||||||
{
|
{
|
||||||
DisplayChangeListener *dcl;
|
DisplayChangeListener *dcl;
|
||||||
@ -1053,13 +1114,7 @@ void console_select(unsigned int index)
|
|||||||
if (dcl->con != NULL) {
|
if (dcl->con != NULL) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (dcl->ops->dpy_gfx_switch) {
|
displaychangelistener_display_console(dcl, s);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ds->have_text) {
|
if (ds->have_text) {
|
||||||
@ -1443,25 +1498,37 @@ static bool dpy_compatible_with(QemuConsole *con,
|
|||||||
return true;
|
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)
|
void register_displaychangelistener(DisplayChangeListener *dcl)
|
||||||
{
|
{
|
||||||
static const char nodev[] =
|
|
||||||
"This VM has no graphic display device.";
|
|
||||||
static DisplaySurface *dummy;
|
|
||||||
QemuConsole *con;
|
QemuConsole *con;
|
||||||
|
|
||||||
assert(!dcl->ds);
|
assert(!dcl->ds);
|
||||||
|
|
||||||
if (dcl->ops->dpy_gl_ctx_create) {
|
if (dcl->con && !dpy_gl_compatible_with(dcl->con, dcl)) {
|
||||||
/* display has opengl support */
|
error_report("Display %s is incompatible with the GL context",
|
||||||
assert(dcl->con);
|
dcl->ops->dpy_name);
|
||||||
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);
|
exit(1);
|
||||||
}
|
}
|
||||||
dcl->con->gl = dcl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dcl->con) {
|
if (dcl->con) {
|
||||||
dpy_compatible_with(dcl->con, dcl, &error_fatal);
|
dpy_compatible_with(dcl->con, dcl, &error_fatal);
|
||||||
@ -1477,16 +1544,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
|
|||||||
} else {
|
} else {
|
||||||
con = active_console;
|
con = active_console;
|
||||||
}
|
}
|
||||||
if (dcl->ops->dpy_gfx_switch) {
|
displaychangelistener_display_console(dcl, con);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text_console_update_cursor(NULL);
|
text_console_update_cursor(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1538,7 +1596,7 @@ const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
|
|||||||
return &con->ui_info;
|
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) {
|
if (con == NULL) {
|
||||||
con = active_console;
|
con = active_console;
|
||||||
@ -1558,7 +1616,8 @@ int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
|
|||||||
* go notify the guest.
|
* go notify the guest.
|
||||||
*/
|
*/
|
||||||
con->ui_info = *info;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1566,13 +1625,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
|
|||||||
{
|
{
|
||||||
DisplayState *s = con->ds;
|
DisplayState *s = con->ds;
|
||||||
DisplayChangeListener *dcl;
|
DisplayChangeListener *dcl;
|
||||||
int width = w;
|
int width = qemu_console_get_width(con, x + w);
|
||||||
int height = h;
|
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);
|
x = MAX(x, 0);
|
||||||
y = MAX(y, 0);
|
y = MAX(y, 0);
|
||||||
x = MIN(x, width);
|
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)
|
void dpy_gfx_update_full(QemuConsole *con)
|
||||||
{
|
{
|
||||||
if (!con->surface) {
|
int w = qemu_console_get_width(con, 0);
|
||||||
return;
|
int h = qemu_console_get_height(con, 0);
|
||||||
}
|
|
||||||
dpy_gfx_update(con, 0, 0,
|
dpy_gfx_update(con, 0, 0, w, h);
|
||||||
surface_width(con->surface),
|
|
||||||
surface_height(con->surface));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dpy_gfx_replace_surface(QemuConsole *con,
|
void dpy_gfx_replace_surface(QemuConsole *con,
|
||||||
@ -1627,6 +1680,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
|
|||||||
|
|
||||||
assert(old_surface != surface);
|
assert(old_surface != surface);
|
||||||
|
|
||||||
|
con->scanout.kind = SCANOUT_SURFACE;
|
||||||
con->surface = surface;
|
con->surface = surface;
|
||||||
QLIST_FOREACH(dcl, &s->listeners, next) {
|
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||||
if (con != (dcl->con ? dcl->con : active_console)) {
|
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)
|
void dpy_gl_scanout_disable(QemuConsole *con)
|
||||||
{
|
{
|
||||||
assert(con->gl);
|
DisplayState *s = con->ds;
|
||||||
con->gl->ops->dpy_gl_scanout_disable(con->gl);
|
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,
|
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 x, uint32_t y,
|
||||||
uint32_t width, uint32_t height)
|
uint32_t width, uint32_t height)
|
||||||
{
|
{
|
||||||
assert(con->gl);
|
DisplayState *s = con->ds;
|
||||||
con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id,
|
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_y_0_top,
|
||||||
backing_width, backing_height,
|
backing_width, backing_height,
|
||||||
x, y, width, height);
|
x, y, width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dpy_gl_scanout_dmabuf(QemuConsole *con,
|
void dpy_gl_scanout_dmabuf(QemuConsole *con,
|
||||||
QemuDmaBuf *dmabuf)
|
QemuDmaBuf *dmabuf)
|
||||||
{
|
{
|
||||||
assert(con->gl);
|
DisplayState *s = con->ds;
|
||||||
con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf);
|
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,
|
void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
|
||||||
bool have_hot, uint32_t hot_x, uint32_t hot_y)
|
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) {
|
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||||
con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf,
|
if (dcl->ops->dpy_gl_cursor_dmabuf) {
|
||||||
|
dcl->ops->dpy_gl_cursor_dmabuf(dcl, dmabuf,
|
||||||
have_hot, hot_x, hot_y);
|
have_hot, hot_x, hot_y);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dpy_gl_cursor_position(QemuConsole *con,
|
void dpy_gl_cursor_position(QemuConsole *con,
|
||||||
uint32_t pos_x, uint32_t pos_y)
|
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) {
|
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||||
con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y);
|
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,
|
void dpy_gl_release_dmabuf(QemuConsole *con,
|
||||||
QemuDmaBuf *dmabuf)
|
QemuDmaBuf *dmabuf)
|
||||||
{
|
{
|
||||||
assert(con->gl);
|
DisplayState *s = con->ds;
|
||||||
|
DisplayChangeListener *dcl;
|
||||||
|
|
||||||
if (con->gl->ops->dpy_gl_release_dmabuf) {
|
QLIST_FOREACH(dcl, &s->listeners, next) {
|
||||||
con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf);
|
if (dcl->ops->dpy_gl_release_dmabuf) {
|
||||||
|
dcl->ops->dpy_gl_release_dmabuf(dcl, dmabuf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dpy_gl_update(QemuConsole *con,
|
void dpy_gl_update(QemuConsole *con,
|
||||||
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
|
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
|
||||||
{
|
{
|
||||||
|
DisplayState *s = con->ds;
|
||||||
|
DisplayChangeListener *dcl;
|
||||||
|
|
||||||
assert(con->gl);
|
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();
|
s = qemu_console_lookup_unused();
|
||||||
if (s) {
|
if (s) {
|
||||||
trace_console_gfx_reuse(s->index);
|
trace_console_gfx_reuse(s->index);
|
||||||
if (s->surface) {
|
width = qemu_console_get_width(s, 0);
|
||||||
width = surface_width(s->surface);
|
height = qemu_console_get_height(s, 0);
|
||||||
height = surface_height(s->surface);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
trace_console_gfx_new();
|
trace_console_gfx_new();
|
||||||
s = new_console(ds, GRAPHIC_CONSOLE, head);
|
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);
|
surface = qemu_create_placeholder_surface(width, height, noinit);
|
||||||
dpy_gfx_replace_surface(s, surface);
|
dpy_gfx_replace_surface(s, surface);
|
||||||
|
s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
||||||
|
graphic_hw_gl_unblock_timer, s);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1959,13 +2052,8 @@ void graphic_console_close(QemuConsole *con)
|
|||||||
static const char unplugged[] =
|
static const char unplugged[] =
|
||||||
"Guest display has been unplugged";
|
"Guest display has been unplugged";
|
||||||
DisplaySurface *surface;
|
DisplaySurface *surface;
|
||||||
int width = 640;
|
int width = qemu_console_get_width(con, 640);
|
||||||
int height = 480;
|
int height = qemu_console_get_height(con, 480);
|
||||||
|
|
||||||
if (con->surface) {
|
|
||||||
width = surface_width(con->surface);
|
|
||||||
height = surface_height(con->surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace_console_gfx_close(con->index);
|
trace_console_gfx_close(con->index);
|
||||||
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
|
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) {
|
if (con == NULL) {
|
||||||
con = active_console;
|
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)
|
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) {
|
if (con == NULL) {
|
||||||
con = active_console;
|
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)
|
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->total_height = DEFAULT_BACKSCROLL;
|
||||||
s->x = 0;
|
s->x = 0;
|
||||||
s->y = 0;
|
s->y = 0;
|
||||||
if (!s->surface) {
|
if (s->scanout.kind != SCANOUT_SURFACE) {
|
||||||
if (active_console && active_console->surface) {
|
if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
|
||||||
g_width = surface_width(active_console->surface);
|
g_width = qemu_console_get_width(active_console, g_width);
|
||||||
g_height = surface_height(active_console->surface);
|
g_height = qemu_console_get_height(active_console, g_height);
|
||||||
}
|
}
|
||||||
s->surface = qemu_create_displaysurface(g_width, g_height);
|
s->surface = qemu_create_displaysurface(g_width, g_height);
|
||||||
|
s->scanout.kind = SCANOUT_SURFACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
s->hw_ops = &text_console_ops;
|
s->hw_ops = &text_console_ops;
|
||||||
@ -2255,6 +2368,7 @@ static void vc_chr_open(Chardev *chr,
|
|||||||
s = new_console(NULL, TEXT_CONSOLE, 0);
|
s = new_console(NULL, TEXT_CONSOLE, 0);
|
||||||
} else {
|
} else {
|
||||||
s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
|
s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
|
||||||
|
s->scanout.kind = SCANOUT_SURFACE;
|
||||||
s->surface = qemu_create_displaysurface(width, height);
|
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)
|
void qemu_console_resize(QemuConsole *s, int width, int height)
|
||||||
{
|
{
|
||||||
DisplaySurface *surface;
|
DisplaySurface *surface = qemu_console_surface(s);
|
||||||
|
|
||||||
assert(s->console_type == GRAPHIC_CONSOLE);
|
assert(s->console_type == GRAPHIC_CONSOLE);
|
||||||
|
|
||||||
if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
|
if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
|
||||||
pixman_image_get_width(s->surface->image) == width &&
|
pixman_image_get_width(surface->image) == width &&
|
||||||
pixman_image_get_height(s->surface->image) == height) {
|
pixman_image_get_height(surface->image) == height) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2294,7 +2408,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
|
|||||||
|
|
||||||
DisplaySurface *qemu_console_surface(QemuConsole *console)
|
DisplaySurface *qemu_console_surface(QemuConsole *console)
|
||||||
{
|
{
|
||||||
|
switch (console->scanout.kind) {
|
||||||
|
case SCANOUT_SURFACE:
|
||||||
return console->surface;
|
return console->surface;
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PixelFormat qemu_default_pixelformat(int bpp)
|
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 "qemu/osdep.h"
|
||||||
#include "ui/egl-context.h"
|
#include "ui/egl-context.h"
|
||||||
|
|
||||||
QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
|
QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params)
|
QEMUGLParams *params)
|
||||||
{
|
{
|
||||||
EGLContext ctx;
|
EGLContext ctx;
|
||||||
@ -24,12 +24,12 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
|
|||||||
return ctx;
|
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);
|
eglDestroyContext(qemu_egl_display, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
int qemu_egl_make_context_current(DisplayChangeListener *dcl,
|
int qemu_egl_make_context_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx)
|
QEMUGLContext ctx)
|
||||||
{
|
{
|
||||||
return eglMakeCurrent(qemu_egl_display,
|
return eglMakeCurrent(qemu_egl_display,
|
||||||
|
@ -38,12 +38,12 @@ static void egl_gfx_switch(DisplayChangeListener *dcl,
|
|||||||
edpy->ds = new_surface;
|
edpy->ds = new_surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QEMUGLContext egl_create_context(DisplayChangeListener *dcl,
|
static QEMUGLContext egl_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params)
|
QEMUGLParams *params)
|
||||||
{
|
{
|
||||||
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||||
qemu_egl_rn_ctx);
|
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)
|
static void egl_scanout_disable(DisplayChangeListener *dcl)
|
||||||
@ -157,10 +157,6 @@ static const DisplayChangeListenerOps egl_ops = {
|
|||||||
.dpy_gfx_update = egl_gfx_update,
|
.dpy_gfx_update = egl_gfx_update,
|
||||||
.dpy_gfx_switch = egl_gfx_switch,
|
.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_disable = egl_scanout_disable,
|
||||||
.dpy_gl_scanout_texture = egl_scanout_texture,
|
.dpy_gl_scanout_texture = egl_scanout_texture,
|
||||||
.dpy_gl_scanout_dmabuf = egl_scanout_dmabuf,
|
.dpy_gl_scanout_dmabuf = egl_scanout_dmabuf,
|
||||||
@ -170,6 +166,13 @@ static const DisplayChangeListenerOps egl_ops = {
|
|||||||
.dpy_gl_update = egl_scanout_flush,
|
.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)
|
static void early_egl_headless_init(DisplayOptions *opts)
|
||||||
{
|
{
|
||||||
display_opengl = 1;
|
display_opengl = 1;
|
||||||
@ -188,6 +191,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (idx = 0;; idx++) {
|
for (idx = 0;; idx++) {
|
||||||
|
DisplayGLCtx *ctx;
|
||||||
|
|
||||||
con = qemu_console_lookup_by_index(idx);
|
con = qemu_console_lookup_by_index(idx);
|
||||||
if (!con || !qemu_console_is_graphic(con)) {
|
if (!con || !qemu_console_is_graphic(con)) {
|
||||||
break;
|
break;
|
||||||
@ -197,6 +202,9 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
|
|||||||
edpy->dcl.con = con;
|
edpy->dcl.con = con;
|
||||||
edpy->dcl.ops = &egl_ops;
|
edpy->dcl.ops = &egl_ops;
|
||||||
edpy->gls = qemu_gl_init_shader();
|
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);
|
register_displaychangelistener(&edpy->dcl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,10 +74,9 @@ static void gd_clipboard_clear(GtkClipboard *clipboard,
|
|||||||
gd->cbowner[s] = false;
|
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;
|
QemuClipboardSelection s = info->selection;
|
||||||
bool self_update = info->owner == &gd->cbpeer;
|
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,
|
static void gd_clipboard_request(QemuClipboardInfo *info,
|
||||||
QemuClipboardType type)
|
QemuClipboardType type)
|
||||||
{
|
{
|
||||||
@ -172,7 +187,7 @@ static void gd_owner_change(GtkClipboard *clipboard,
|
|||||||
void gd_clipboard_init(GtkDisplayState *gd)
|
void gd_clipboard_init(GtkDisplayState *gd)
|
||||||
{
|
{
|
||||||
gd->cbpeer.name = "gtk";
|
gd->cbpeer.name = "gtk";
|
||||||
gd->cbpeer.update.notify = gd_clipboard_notify;
|
gd->cbpeer.notifier.notify = gd_clipboard_notify;
|
||||||
gd->cbpeer.request = gd_clipboard_request;
|
gd->cbpeer.request = gd_clipboard_request;
|
||||||
qemu_clipboard_peer_register(&gd->cbpeer);
|
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();
|
glFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
graphic_hw_gl_flushed(vc->gfx.dcl.con);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gd_egl_update(DisplayChangeListener *dcl,
|
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)
|
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,
|
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
|
||||||
vc->gfx.esurface, vc->gfx.ectx);
|
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)
|
void gd_egl_scanout_disable(DisplayChangeListener *dcl)
|
||||||
@ -362,10 +360,10 @@ void gtk_egl_init(DisplayGLMode mode)
|
|||||||
display_opengl = 1;
|
display_opengl = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int gd_egl_make_current(DisplayChangeListener *dcl,
|
int gd_egl_make_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx)
|
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,
|
return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
|
||||||
vc->gfx.esurface, ctx);
|
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_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
|
||||||
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
|
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,
|
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)
|
QEMUGLParams *params)
|
||||||
{
|
{
|
||||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
|
||||||
GdkWindow *window;
|
GdkWindow *window;
|
||||||
GdkGLContext *ctx;
|
GdkGLContext *ctx;
|
||||||
GError *err = NULL;
|
GError *err = NULL;
|
||||||
@ -201,7 +199,7 @@ QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
|
void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
|
||||||
{
|
{
|
||||||
/* FIXME */
|
/* FIXME */
|
||||||
}
|
}
|
||||||
@ -280,7 +278,7 @@ void gtk_gl_area_init(void)
|
|||||||
display_opengl = 1;
|
display_opengl = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int gd_gl_area_make_current(DisplayChangeListener *dcl,
|
int gd_gl_area_make_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx)
|
QEMUGLContext ctx)
|
||||||
{
|
{
|
||||||
gdk_gl_context_make_current(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);
|
close(dmabuf->fence_fd);
|
||||||
dmabuf->fence_fd = -1;
|
dmabuf->fence_fd = -1;
|
||||||
graphic_hw_gl_block(vc->gfx.dcl.con, false);
|
graphic_hw_gl_block(vc->gfx.dcl.con, false);
|
||||||
graphic_hw_gl_flushed(vc->gfx.dcl.con);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DisplayState Callbacks (opengl version) **/
|
/** DisplayState Callbacks (opengl version) **/
|
||||||
@ -607,9 +606,6 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
|
|||||||
.dpy_mouse_set = gd_mouse_set,
|
.dpy_mouse_set = gd_mouse_set,
|
||||||
.dpy_cursor_define = gd_cursor_define,
|
.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_texture = gd_gl_area_scanout_texture,
|
||||||
.dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
|
.dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
|
||||||
.dpy_gl_update = gd_gl_area_scanout_flush,
|
.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,
|
.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 = {
|
static const DisplayChangeListenerOps dcl_egl_ops = {
|
||||||
.dpy_name = "gtk-egl",
|
.dpy_name = "gtk-egl",
|
||||||
.dpy_gfx_update = gd_egl_update,
|
.dpy_gfx_update = gd_egl_update,
|
||||||
@ -629,9 +631,6 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
|
|||||||
.dpy_mouse_set = gd_mouse_set,
|
.dpy_mouse_set = gd_mouse_set,
|
||||||
.dpy_cursor_define = gd_cursor_define,
|
.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_disable = gd_egl_scanout_disable,
|
||||||
.dpy_gl_scanout_texture = gd_egl_scanout_texture,
|
.dpy_gl_scanout_texture = gd_egl_scanout_texture,
|
||||||
.dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
|
.dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
|
||||||
@ -642,6 +641,12 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
|
|||||||
.dpy_has_dmabuf = gd_has_dmabuf,
|
.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
|
||||||
|
|
||||||
#endif /* CONFIG_OPENGL */
|
#endif /* CONFIG_OPENGL */
|
||||||
@ -698,7 +703,7 @@ static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
|
|||||||
memset(&info, 0, sizeof(info));
|
memset(&info, 0, sizeof(info));
|
||||||
info.width = width;
|
info.width = width;
|
||||||
info.height = height;
|
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)
|
#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_signal_connect(vc->gfx.drawing_area, "realize",
|
||||||
G_CALLBACK(gl_area_realize), vc);
|
G_CALLBACK(gl_area_realize), vc);
|
||||||
vc->gfx.dcl.ops = &dcl_gl_area_ops;
|
vc->gfx.dcl.ops = &dcl_gl_area_ops;
|
||||||
|
vc->gfx.dgc.ops = &gl_area_ctx_ops;
|
||||||
} else {
|
} else {
|
||||||
#ifdef CONFIG_X11
|
#ifdef CONFIG_X11
|
||||||
vc->gfx.drawing_area = gtk_drawing_area_new();
|
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);
|
gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
vc->gfx.dcl.ops = &dcl_egl_ops;
|
vc->gfx.dcl.ops = &dcl_egl_ops;
|
||||||
|
vc->gfx.dgc.ops = &egl_ctx_ops;
|
||||||
vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
|
vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
|
||||||
#else
|
#else
|
||||||
abort();
|
abort();
|
||||||
@ -2083,6 +2090,9 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
|
|||||||
vc->gfx.kbd = qkbd_state_init(con);
|
vc->gfx.kbd = qkbd_state_init(con);
|
||||||
vc->gfx.dcl.con = con;
|
vc->gfx.dcl.con = con;
|
||||||
|
|
||||||
|
if (display_opengl) {
|
||||||
|
qemu_console_set_display_gl_ctx(con, &vc->gfx.dgc);
|
||||||
|
}
|
||||||
register_displaychangelistener(&vc->gfx.dcl);
|
register_displaychangelistener(&vc->gfx.dcl);
|
||||||
|
|
||||||
gd_connect_vc_gfx_signals(vc);
|
gd_connect_vc_gfx_signals(vc);
|
||||||
|
@ -12,7 +12,11 @@ softmmu_ss.add(files(
|
|||||||
'kbd-state.c',
|
'kbd-state.c',
|
||||||
'keymaps.c',
|
'keymaps.c',
|
||||||
'qemu-pixman.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([spice_headers, files('spice-module.c')])
|
||||||
softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.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}
|
ui_modules += {'egl-headless' : egl_headless_ss}
|
||||||
endif
|
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()
|
if gtk.found()
|
||||||
softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
|
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);
|
surface_gl_render_texture(scon->gls, scon->surface);
|
||||||
SDL_GL_SwapWindow(scon->real_window);
|
SDL_GL_SwapWindow(scon->real_window);
|
||||||
graphic_hw_gl_flushed(scon->dcl.con);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sdl2_gl_update(DisplayChangeListener *dcl,
|
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)
|
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;
|
SDL_GLContext ctx;
|
||||||
|
|
||||||
assert(scon->opengl);
|
assert(scon->opengl);
|
||||||
@ -168,17 +167,17 @@ QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
|
|||||||
return (QEMUGLContext)ctx;
|
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_GLContext sdlctx = (SDL_GLContext)ctx;
|
||||||
|
|
||||||
SDL_GL_DeleteContext(sdlctx);
|
SDL_GL_DeleteContext(sdlctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
|
int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
|
||||||
QEMUGLContext ctx)
|
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;
|
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
|
||||||
|
|
||||||
assert(scon->opengl);
|
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);
|
egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
|
||||||
|
|
||||||
SDL_GL_SwapWindow(scon->real_window);
|
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));
|
memset(&info, 0, sizeof(info));
|
||||||
info.width = ev->window.data1;
|
info.width = ev->window.data1;
|
||||||
info.height = ev->window.data2;
|
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);
|
sdl2_redraw(scon);
|
||||||
break;
|
break;
|
||||||
@ -778,13 +778,17 @@ static const DisplayChangeListenerOps dcl_gl_ops = {
|
|||||||
.dpy_mouse_set = sdl_mouse_warp,
|
.dpy_mouse_set = sdl_mouse_warp,
|
||||||
.dpy_cursor_define = sdl_mouse_define,
|
.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_disable = sdl2_gl_scanout_disable,
|
||||||
.dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
|
.dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
|
||||||
.dpy_gl_update = sdl2_gl_scanout_flush,
|
.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
|
#endif
|
||||||
|
|
||||||
static void sdl2_display_early_init(DisplayOptions *o)
|
static void sdl2_display_early_init(DisplayOptions *o)
|
||||||
@ -860,12 +864,16 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
|||||||
#ifdef CONFIG_OPENGL
|
#ifdef CONFIG_OPENGL
|
||||||
sdl2_console[i].opengl = display_opengl;
|
sdl2_console[i].opengl = display_opengl;
|
||||||
sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
|
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
|
#else
|
||||||
sdl2_console[i].opengl = 0;
|
sdl2_console[i].opengl = 0;
|
||||||
sdl2_console[i].dcl.ops = &dcl_2d_ops;
|
sdl2_console[i].dcl.ops = &dcl_2d_ops;
|
||||||
#endif
|
#endif
|
||||||
sdl2_console[i].dcl.con = con;
|
sdl2_console[i].dcl.con = con;
|
||||||
sdl2_console[i].kbd = qkbd_state_init(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);
|
register_displaychangelistener(&sdl2_console[i].dcl);
|
||||||
|
|
||||||
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
|
#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;
|
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)
|
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
|
||||||
{
|
{
|
||||||
if (g_slist_find(spice_consoles, 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);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,7 +830,6 @@ static void qemu_spice_gl_unblock_bh(void *opaque)
|
|||||||
SimpleSpiceDisplay *ssd = opaque;
|
SimpleSpiceDisplay *ssd = opaque;
|
||||||
|
|
||||||
qemu_spice_gl_block(ssd, false);
|
qemu_spice_gl_block(ssd, false);
|
||||||
graphic_hw_gl_flushed(ssd->dcl.con);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qemu_spice_gl_block_timer(void *opaque)
|
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)
|
QEMUGLParams *params)
|
||||||
{
|
{
|
||||||
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||||
qemu_egl_rn_ctx);
|
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)
|
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_mouse_set = display_mouse_set,
|
||||||
.dpy_cursor_define = display_mouse_define,
|
.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_disable = qemu_spice_gl_scanout_disable,
|
||||||
.dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
|
.dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
|
||||||
.dpy_gl_scanout_dmabuf = qemu_spice_gl_scanout_dmabuf,
|
.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,
|
.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 */
|
#endif /* HAVE_SPICE_GL */
|
||||||
|
|
||||||
static void qemu_spice_display_init_one(QemuConsole *con)
|
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
|
#ifdef HAVE_SPICE_GL
|
||||||
if (spice_opengl) {
|
if (spice_opengl) {
|
||||||
ssd->dcl.ops = &display_listener_gl_ops;
|
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_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
|
||||||
ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
||||||
qemu_spice_gl_block_timer, ssd);
|
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);
|
qemu_spice_add_display_interface(&ssd->qxl, con);
|
||||||
|
|
||||||
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
|
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
|
||||||
|
Error *err = NULL;
|
||||||
char device_address[256] = "";
|
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,
|
spice_qxl_set_device_info(&ssd->qxl,
|
||||||
device_address,
|
device_address,
|
||||||
qemu_console_get_head(con),
|
qemu_console_get_head(con),
|
||||||
1);
|
1);
|
||||||
|
} else {
|
||||||
|
error_report_err(err);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qemu_spice_create_host_memslot(ssd);
|
qemu_spice_create_host_memslot(ssd);
|
||||||
|
|
||||||
|
if (spice_opengl) {
|
||||||
|
qemu_console_set_display_gl_ctx(con, &ssd->dgc);
|
||||||
|
}
|
||||||
register_displaychangelistener(&ssd->dcl);
|
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_peer_cap(const char *name) "cap %s"
|
||||||
vdagent_cb_grab_selection(const char *name) "selection %s"
|
vdagent_cb_grab_selection(const char *name) "selection %s"
|
||||||
vdagent_cb_grab_type(const char *name) "type %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"
|
#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_BUFFER_LIMIT (1 * MiB)
|
||||||
#define VDAGENT_MOUSE_DEFAULT true
|
#define VDAGENT_MOUSE_DEFAULT true
|
||||||
#define VDAGENT_CLIPBOARD_DEFAULT false
|
#define VDAGENT_CLIPBOARD_DEFAULT false
|
||||||
@ -51,6 +59,7 @@ struct VDAgentChardev {
|
|||||||
|
|
||||||
/* clipboard */
|
/* clipboard */
|
||||||
QemuClipboardPeer cbpeer;
|
QemuClipboardPeer cbpeer;
|
||||||
|
uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||||
uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
|
uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||||
};
|
};
|
||||||
typedef struct VDAgentChardev VDAgentChardev;
|
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_MONITORS_CONFIG_POSITION] = "monitors-config-position",
|
||||||
[VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled",
|
[VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled",
|
||||||
[VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors",
|
[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",
|
[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_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
|
||||||
[VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial",
|
[VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial",
|
||||||
#endif
|
#endif
|
||||||
@ -102,7 +113,7 @@ static const char *msg_name[] = {
|
|||||||
[VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected",
|
[VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected",
|
||||||
[VD_AGENT_MAX_CLIPBOARD] = "max-clipboard",
|
[VD_AGENT_MAX_CLIPBOARD] = "max-clipboard",
|
||||||
[VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync",
|
[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",
|
[VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@ -120,7 +131,7 @@ static const char *type_name[] = {
|
|||||||
[VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp",
|
[VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp",
|
||||||
[VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
|
[VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
|
||||||
[VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg",
|
[VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg",
|
||||||
#if 0
|
#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3)
|
||||||
[VD_AGENT_CLIPBOARD_FILE_LIST] = "files",
|
[VD_AGENT_CLIPBOARD_FILE_LIST] = "files",
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@ -193,6 +204,9 @@ static void vdagent_send_caps(VDAgentChardev *vd)
|
|||||||
if (vd->clipboard) {
|
if (vd->clipboard) {
|
||||||
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
|
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
|
||||||
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
|
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);
|
vdagent_send_msg(vd, msg);
|
||||||
@ -323,7 +337,8 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
|
|||||||
{
|
{
|
||||||
g_autofree VDAgentMessage *msg =
|
g_autofree VDAgentMessage *msg =
|
||||||
g_malloc0(sizeof(VDAgentMessage) +
|
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;
|
uint8_t *s = msg->data;
|
||||||
uint32_t *data = (uint32_t *)msg->data;
|
uint32_t *data = (uint32_t *)msg->data;
|
||||||
uint32_t q, type;
|
uint32_t q, type;
|
||||||
@ -336,6 +351,19 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
|
|||||||
return;
|
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++) {
|
for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
|
||||||
type = type_qemu_to_vdagent(q);
|
type = type_qemu_to_vdagent(q);
|
||||||
if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
|
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);
|
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;
|
QemuClipboardSelection s = info->selection;
|
||||||
QemuClipboardType type;
|
QemuClipboardType type;
|
||||||
bool self_update = info->owner == &vd->cbpeer;
|
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,
|
static void vdagent_clipboard_request(QemuClipboardInfo *info,
|
||||||
QemuClipboardType qtype)
|
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));
|
trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
|
||||||
info = qemu_clipboard_info_new(&vd->cbpeer, 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) {
|
if (size > sizeof(uint32_t) * 10) {
|
||||||
/*
|
/*
|
||||||
* spice has 6 types as of 2021. Limiting to 10 entries
|
* 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) {
|
if (have_mouse(vd) && vd->mouse_hs) {
|
||||||
qemu_input_handler_activate(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.name = "vdagent";
|
||||||
vd->cbpeer.update.notify = vdagent_clipboard_notify;
|
vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
|
||||||
vd->cbpeer.request = vdagent_clipboard_request;
|
vd->cbpeer.request = vdagent_clipboard_request;
|
||||||
qemu_clipboard_peer_register(&vd->cbpeer);
|
qemu_clipboard_peer_register(&vd->cbpeer);
|
||||||
}
|
}
|
||||||
@ -789,7 +860,7 @@ static void vdagent_disconnect(VDAgentChardev *vd)
|
|||||||
if (vd->mouse_hs) {
|
if (vd->mouse_hs) {
|
||||||
qemu_input_handler_deactivate(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);
|
qemu_clipboard_peer_unregister(&vd->cbpeer);
|
||||||
memset(&vd->cbpeer, 0, sizeof(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)
|
static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
|
||||||
{
|
{
|
||||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
|
|
||||||
|
|
||||||
if (!fe_open) {
|
if (!fe_open) {
|
||||||
trace_vdagent_close();
|
trace_vdagent_close();
|
||||||
vdagent_disconnect(vd);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,10 +189,8 @@ static void vnc_clipboard_provide(VncState *vs,
|
|||||||
vnc_flush(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;
|
QemuClipboardType type;
|
||||||
bool self_update = info->owner == &vs->cbpeer;
|
bool self_update = info->owner == &vs->cbpeer;
|
||||||
uint32_t flags = 0;
|
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,
|
static void vnc_clipboard_request(QemuClipboardInfo *info,
|
||||||
QemuClipboardType type)
|
QemuClipboardType type)
|
||||||
{
|
{
|
||||||
@ -316,9 +329,9 @@ void vnc_server_cut_text_caps(VncState *vs)
|
|||||||
caps[1] = 0;
|
caps[1] = 0;
|
||||||
vnc_clipboard_send(vs, 2, caps);
|
vnc_clipboard_send(vs, 2, caps);
|
||||||
|
|
||||||
if (!vs->cbpeer.update.notify) {
|
if (!vs->cbpeer.notifier.notify) {
|
||||||
vs->cbpeer.name = "vnc";
|
vs->cbpeer.name = "vnc";
|
||||||
vs->cbpeer.update.notify = vnc_clipboard_notify;
|
vs->cbpeer.notifier.notify = vnc_clipboard_notify;
|
||||||
vs->cbpeer.request = vnc_clipboard_request;
|
vs->cbpeer.request = vnc_clipboard_request;
|
||||||
qemu_clipboard_peer_register(&vs->cbpeer);
|
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 */
|
/* last client gone */
|
||||||
vnc_update_server_surface(vs->vd);
|
vnc_update_server_surface(vs->vd);
|
||||||
}
|
}
|
||||||
if (vs->cbpeer.update.notify) {
|
if (vs->cbpeer.notifier.notify) {
|
||||||
qemu_clipboard_peer_unregister(&vs->cbpeer);
|
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));
|
memset(&info, 0, sizeof(info));
|
||||||
info.width = w;
|
info.width = w;
|
||||||
info.height = h;
|
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 */);
|
vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
|
||||||
} else {
|
} else {
|
||||||
vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);
|
vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user