mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging
UI-related - vdagent migration support - gtk: improve scale handling # -----BEGIN PGP SIGNATURE----- # # iQJQBAABCgA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmgyAywcHG1hcmNhbmRy # ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5TbTD/4/3da55xtH0yVSol+G # T6S7F1GKUbSCi0y/t+PiG2usiE17euZ+m1mKBAOT2kObehS3gWybIreNBZ3IAPZg # Hm8RoIU6yStui2RZ/rKx2d1P2nq3mMmaB7uToHwPl6KsO2hYm75zmXCGH6oBI+ah # muNkxzsIky9qnOaKeZRODPkPiG1LXfte0IYV6wkpBcXUqE595rQfD9WDuOJ7WKJe # T1GiwxdClZGBM6SahDhfwdoFx1yTk8rTtTdYM0K3W7xMQGvCKLGQHDmS7JeQ1lm3 # 42H3kh760UwNqSyk4L0TWZiGrsEWCsbU7heiRLeVL/FBA8N2TWL6xKB/xSO4duJ3 # v63652/rNh18AFnSRu0CaIBwf/hXfLpU/cKv/Sqmm2X8FjD7dLMyaAHCz1BC0acU # +VA8JKRu7F5GbsisaYyG4lY6u8IBw9sq/FhyNBflBy3MQl0v4PV5x1eqDbGT1560 # k2bQbIChumn8U4TmEA1IHDFX45cdxZmlgnt7yq+SUn5/CmRJrXAlacPRje4A4omL # YS3Y1Qn4X7sXyIdoHvxofng2NU0nfZX9az27aXsGkRNtzjibZzjl8rJKcaCx0LGQ # uFJwA+ArCJjTJ3EB09373iGixW0Vuv4dM4IkNp+QGoCOVH0jeER6bs9cNROG6WmS # e1nWuW0hZgmdusw1S0ncxFc+YA== # =SjRP # -----END PGP SIGNATURE----- # gpg: Signature made Sat 24 May 2025 13:34:36 EDT # gpg: using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5 # gpg: issuer "marcandre.lureau@redhat.com" # gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [full] # gpg: aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [full] # Primary key fingerprint: 87A9 BD93 3F87 C606 D276 F62D DAE8 E109 7596 9CE5 * tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu: ui/gtk-egl: Render guest content with padding in fixed-scale mode ui/gtk-gl-area: Render guest content with padding in fixed-scale mode ui/gtk: Consider scaling when propagating ui info ui/gtk: Don't update scale in fixed scale mode in gtk-egl.c ui/sdl: Consider scaling in mouse event handling ui/gtk: Update scales in fixed-scale mode when rendering GL area gtk/ui: Introduce helper gd_update_scale ui/gtk: Use consistent naming for variables in different coordinates ui/gtk: Document scale and coordinate handling ui/vdagent: remove migration blocker ui/vdagent: add migration support ui/vdagent: factor out clipboard peer registration ui/vdagent: keep "connected" state ui/vdagent: replace Buffer with GByteArray ui/clipboard: delay clipboard update when not running ui/clipboard: add vmstate_cbinfo ui/clipboard: split out QemuClipboardContent ui/clipboard: use int for selection field ui/gtk: warn if setting the clipboard failed Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
+24
-7
@@ -2,6 +2,7 @@
|
||||
#define QEMU_CLIPBOARD_H
|
||||
|
||||
#include "qemu/notify.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
/**
|
||||
* DOC: Introduction
|
||||
@@ -25,6 +26,9 @@ typedef enum QemuClipboardSelection QemuClipboardSelection;
|
||||
typedef struct QemuClipboardPeer QemuClipboardPeer;
|
||||
typedef struct QemuClipboardNotify QemuClipboardNotify;
|
||||
typedef struct QemuClipboardInfo QemuClipboardInfo;
|
||||
typedef struct QemuClipboardContent QemuClipboardContent;
|
||||
|
||||
extern const VMStateDescription vmstate_cbinfo;
|
||||
|
||||
/**
|
||||
* enum QemuClipboardType
|
||||
@@ -97,6 +101,24 @@ struct QemuClipboardNotify {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* struct QemuClipboardContent
|
||||
*
|
||||
* @available: whether the data is available
|
||||
* @requested: whether the data was requested
|
||||
* @size: the size of the @data
|
||||
* @data: the clipboard data
|
||||
*
|
||||
* Clipboard content.
|
||||
*/
|
||||
struct QemuClipboardContent {
|
||||
bool available;
|
||||
bool requested;
|
||||
uint32_t size;
|
||||
void *data;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct QemuClipboardInfo
|
||||
*
|
||||
@@ -112,15 +134,10 @@ struct QemuClipboardNotify {
|
||||
struct QemuClipboardInfo {
|
||||
uint32_t refcount;
|
||||
QemuClipboardPeer *owner;
|
||||
QemuClipboardSelection selection;
|
||||
int selection; /* QemuClipboardSelection */
|
||||
bool has_serial;
|
||||
uint32_t serial;
|
||||
struct {
|
||||
bool available;
|
||||
bool requested;
|
||||
size_t size;
|
||||
void *data;
|
||||
} types[QEMU_CLIPBOARD_TYPE__COUNT];
|
||||
QemuClipboardContent types[QEMU_CLIPBOARD_TYPE__COUNT];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,8 @@ extern bool qemu_egl_angle_d3d;
|
||||
typedef struct egl_fb {
|
||||
int width;
|
||||
int height;
|
||||
int x;
|
||||
int y;
|
||||
GLuint texture;
|
||||
GLuint framebuffer;
|
||||
bool delete_texture;
|
||||
@@ -26,7 +28,7 @@ typedef struct egl_fb {
|
||||
#define EGL_FB_INIT { 0, }
|
||||
|
||||
void egl_fb_destroy(egl_fb *fb);
|
||||
void egl_fb_setup_default(egl_fb *fb, int width, int height);
|
||||
void egl_fb_setup_default(egl_fb *fb, int width, int height, int x, int y);
|
||||
void egl_fb_setup_for_tex(egl_fb *fb, int width, int height,
|
||||
GLuint texture, bool delete);
|
||||
void egl_fb_setup_new_tex(egl_fb *fb, int width, int height);
|
||||
|
||||
@@ -224,4 +224,6 @@ int gd_gl_area_make_current(DisplayGLCtx *dgc,
|
||||
/* gtk-clipboard.c */
|
||||
void gd_clipboard_init(GtkDisplayState *gd);
|
||||
|
||||
void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh);
|
||||
|
||||
#endif /* UI_GTK_H */
|
||||
|
||||
+64
-2
@@ -1,4 +1,5 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "system/runstate.h"
|
||||
#include "ui/clipboard.h"
|
||||
#include "trace.h"
|
||||
|
||||
@@ -7,8 +8,62 @@ static NotifierList clipboard_notifiers =
|
||||
|
||||
static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||
|
||||
static VMChangeStateEntry *cb_change_state_entry = NULL;
|
||||
|
||||
static bool cb_reset_serial_on_resume = false;
|
||||
|
||||
static const VMStateDescription vmstate_cbcontent = {
|
||||
.name = "clipboard/content",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_BOOL(available, QemuClipboardContent),
|
||||
VMSTATE_BOOL(requested, QemuClipboardContent),
|
||||
VMSTATE_UINT32(size, QemuClipboardContent),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(data, QemuClipboardContent, 0, 0, size),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
const VMStateDescription vmstate_cbinfo = {
|
||||
.name = "clipboard",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_INT32(selection, QemuClipboardInfo),
|
||||
VMSTATE_BOOL(has_serial, QemuClipboardInfo),
|
||||
VMSTATE_UINT32(serial, QemuClipboardInfo),
|
||||
VMSTATE_STRUCT_ARRAY(types, QemuClipboardInfo, QEMU_CLIPBOARD_TYPE__COUNT, 0, vmstate_cbcontent, QemuClipboardContent),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void qemu_clipboard_change_state(void *opaque, bool running, RunState state)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cb_reset_serial_on_resume) {
|
||||
qemu_clipboard_reset_serial();
|
||||
}
|
||||
|
||||
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
||||
if (cbinfo[i]) {
|
||||
qemu_clipboard_update(cbinfo[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
|
||||
{
|
||||
if (cb_change_state_entry == NULL) {
|
||||
cb_change_state_entry = qemu_add_vm_change_state_handler(qemu_clipboard_change_state, NULL);
|
||||
}
|
||||
|
||||
notifier_list_add(&clipboard_notifiers, &peer->notifier);
|
||||
}
|
||||
|
||||
@@ -83,7 +138,9 @@ void qemu_clipboard_update(QemuClipboardInfo *info)
|
||||
}
|
||||
}
|
||||
|
||||
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||
if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||
}
|
||||
|
||||
if (cbinfo[info->selection] != info) {
|
||||
qemu_clipboard_info_unref(cbinfo[info->selection]);
|
||||
@@ -163,7 +220,12 @@ void qemu_clipboard_reset_serial(void)
|
||||
info->serial = 0;
|
||||
}
|
||||
}
|
||||
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||
|
||||
if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
||||
} else {
|
||||
cb_reset_serial_on_resume = true;
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_clipboard_set_data(QemuClipboardPeer *peer,
|
||||
|
||||
+8
-2
@@ -93,14 +93,18 @@ void egl_fb_destroy(egl_fb *fb)
|
||||
|
||||
fb->width = 0;
|
||||
fb->height = 0;
|
||||
fb->x = 0;
|
||||
fb->y = 0;
|
||||
fb->texture = 0;
|
||||
fb->framebuffer = 0;
|
||||
}
|
||||
|
||||
void egl_fb_setup_default(egl_fb *fb, int width, int height)
|
||||
void egl_fb_setup_default(egl_fb *fb, int width, int height, int x, int y)
|
||||
{
|
||||
fb->width = width;
|
||||
fb->height = height;
|
||||
fb->x = x;
|
||||
fb->y = y;
|
||||
fb->framebuffer = 0; /* default framebuffer */
|
||||
}
|
||||
|
||||
@@ -145,6 +149,7 @@ void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip)
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->framebuffer);
|
||||
glViewport(0, 0, dst->width, dst->height);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (src->dmabuf) {
|
||||
x1 = qemu_dmabuf_get_x(src->dmabuf);
|
||||
@@ -161,7 +166,8 @@ void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip)
|
||||
x2 = x1 + w;
|
||||
|
||||
glBlitFramebuffer(x1, y1, x2, y2,
|
||||
0, 0, dst->width, dst->height,
|
||||
dst->x, dst->y,
|
||||
dst->x + dst->width, dst->y + dst->height,
|
||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/main-loop.h"
|
||||
|
||||
#include "ui/gtk.h"
|
||||
@@ -95,11 +96,13 @@ static void gd_clipboard_update_info(GtkDisplayState *gd,
|
||||
gtk_clipboard_clear(gd->gtkcb[s]);
|
||||
if (targets) {
|
||||
gd->cbowner[s] = true;
|
||||
gtk_clipboard_set_with_data(gd->gtkcb[s],
|
||||
targets, n_targets,
|
||||
gd_clipboard_get_data,
|
||||
gd_clipboard_clear,
|
||||
gd);
|
||||
if (!gtk_clipboard_set_with_data(gd->gtkcb[s],
|
||||
targets, n_targets,
|
||||
gd_clipboard_get_data,
|
||||
gd_clipboard_clear,
|
||||
gd)) {
|
||||
warn_report("Failed to set GTK clipboard");
|
||||
}
|
||||
|
||||
gtk_target_table_free(targets, n_targets);
|
||||
}
|
||||
|
||||
+44
-14
@@ -70,16 +70,18 @@ void gd_egl_draw(VirtualConsole *vc)
|
||||
QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
|
||||
int fence_fd;
|
||||
#endif
|
||||
int ww, wh, ws;
|
||||
int ww, wh, pw, ph, gs;
|
||||
|
||||
if (!vc->gfx.gls) {
|
||||
return;
|
||||
}
|
||||
|
||||
window = gtk_widget_get_window(vc->gfx.drawing_area);
|
||||
ws = gdk_window_get_scale_factor(window);
|
||||
ww = gdk_window_get_width(window) * ws;
|
||||
wh = gdk_window_get_height(window) * ws;
|
||||
gs = gdk_window_get_scale_factor(window);
|
||||
ww = gdk_window_get_width(window);
|
||||
wh = gdk_window_get_height(window);
|
||||
pw = ww * gs;
|
||||
ph = wh * gs;
|
||||
|
||||
if (vc->gfx.scanout_mode) {
|
||||
#ifdef CONFIG_GBM
|
||||
@@ -93,8 +95,9 @@ void gd_egl_draw(VirtualConsole *vc)
|
||||
#endif
|
||||
gd_egl_scanout_flush(&vc->gfx.dcl, 0, 0, vc->gfx.w, vc->gfx.h);
|
||||
|
||||
vc->gfx.scale_x = (double)ww / surface_width(vc->gfx.ds);
|
||||
vc->gfx.scale_y = (double)wh / surface_height(vc->gfx.ds);
|
||||
gd_update_scale(vc, ww, wh,
|
||||
surface_width(vc->gfx.ds),
|
||||
surface_height(vc->gfx.ds));
|
||||
|
||||
glFlush();
|
||||
#ifdef CONFIG_GBM
|
||||
@@ -115,13 +118,14 @@ void gd_egl_draw(VirtualConsole *vc)
|
||||
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
|
||||
vc->gfx.esurface, vc->gfx.ectx);
|
||||
|
||||
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
|
||||
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph);
|
||||
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
|
||||
|
||||
eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
|
||||
|
||||
vc->gfx.scale_x = (double)ww / surface_width(vc->gfx.ds);
|
||||
vc->gfx.scale_y = (double)wh / surface_height(vc->gfx.ds);
|
||||
gd_update_scale(vc, ww, wh,
|
||||
surface_width(vc->gfx.ds),
|
||||
surface_height(vc->gfx.ds));
|
||||
|
||||
glFlush();
|
||||
}
|
||||
@@ -336,7 +340,11 @@ void gd_egl_scanout_flush(DisplayChangeListener *dcl,
|
||||
{
|
||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||
GdkWindow *window;
|
||||
int ww, wh, ws;
|
||||
int px_offset, py_offset;
|
||||
int gs;
|
||||
int pw_widget, ph_widget, pw_surface, ph_surface;
|
||||
int ww_widget, wh_widget, ww_surface, wh_surface;
|
||||
int fbw, fbh;
|
||||
|
||||
if (!vc->gfx.scanout_mode) {
|
||||
return;
|
||||
@@ -349,10 +357,32 @@ void gd_egl_scanout_flush(DisplayChangeListener *dcl,
|
||||
vc->gfx.esurface, vc->gfx.ectx);
|
||||
|
||||
window = gtk_widget_get_window(vc->gfx.drawing_area);
|
||||
ws = gdk_window_get_scale_factor(window);
|
||||
ww = gdk_window_get_width(window) * ws;
|
||||
wh = gdk_window_get_height(window) * ws;
|
||||
egl_fb_setup_default(&vc->gfx.win_fb, ww, wh);
|
||||
gs = gdk_window_get_scale_factor(window);
|
||||
ww_widget = gdk_window_get_width(window);
|
||||
wh_widget = gdk_window_get_height(window);
|
||||
fbw = surface_width(vc->gfx.ds);
|
||||
fbh = surface_height(vc->gfx.ds);
|
||||
|
||||
gd_update_scale(vc, ww_widget, wh_widget, fbw, fbh);
|
||||
|
||||
ww_surface = fbw * vc->gfx.scale_x;
|
||||
wh_surface = fbh * vc->gfx.scale_y;
|
||||
pw_widget = ww_widget * gs;
|
||||
ph_widget = wh_widget * gs;
|
||||
pw_surface = ww_surface * gs;
|
||||
ph_surface = wh_surface * gs;
|
||||
|
||||
px_offset = 0;
|
||||
py_offset = 0;
|
||||
if (pw_widget > pw_surface) {
|
||||
px_offset = (pw_widget - pw_surface) / 2;
|
||||
}
|
||||
if (ph_widget > ph_surface) {
|
||||
py_offset = (ph_widget - ph_surface) / 2;
|
||||
}
|
||||
|
||||
egl_fb_setup_default(&vc->gfx.win_fb, pw_surface, ph_surface,
|
||||
px_offset, py_offset);
|
||||
if (vc->gfx.cursor_fb.texture) {
|
||||
egl_texture_blit(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.guest_fb,
|
||||
vc->gfx.y0_top);
|
||||
|
||||
+46
-7
@@ -42,16 +42,37 @@ void gd_gl_area_draw(VirtualConsole *vc)
|
||||
#ifdef CONFIG_GBM
|
||||
QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
|
||||
#endif
|
||||
int ww, wh, ws, y1, y2;
|
||||
int pw, ph, gs, y1, y2;
|
||||
int ww, wh;
|
||||
int ww_surface, wh_surface;
|
||||
int fbw, fbh;
|
||||
int wx_offset, wy_offset;
|
||||
|
||||
if (!vc->gfx.gls) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
|
||||
ws = gdk_window_get_scale_factor(gtk_widget_get_window(vc->gfx.drawing_area));
|
||||
ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area) * ws;
|
||||
wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area) * ws;
|
||||
gs = gdk_window_get_scale_factor(gtk_widget_get_window(vc->gfx.drawing_area));
|
||||
fbw = surface_width(vc->gfx.ds);
|
||||
fbh = surface_height(vc->gfx.ds);
|
||||
ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area);
|
||||
wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area);
|
||||
pw = ww * gs;
|
||||
ph = wh * gs;
|
||||
|
||||
gd_update_scale(vc, ww, wh, fbw, fbh);
|
||||
|
||||
ww_surface = fbw * vc->gfx.scale_x;
|
||||
wh_surface = fbh * vc->gfx.scale_y;
|
||||
|
||||
wx_offset = wy_offset = 0;
|
||||
if (ww > ww_surface) {
|
||||
wx_offset = (ww - ww_surface) / 2;
|
||||
}
|
||||
if (wh > wh_surface) {
|
||||
wy_offset = (wh - wh_surface) / 2;
|
||||
}
|
||||
|
||||
if (vc->gfx.scanout_mode) {
|
||||
if (!vc->gfx.guest_fb.framebuffer) {
|
||||
@@ -71,11 +92,29 @@ void gd_gl_area_draw(VirtualConsole *vc)
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, vc->gfx.guest_fb.framebuffer);
|
||||
/* GtkGLArea sets GL_DRAW_FRAMEBUFFER for us */
|
||||
|
||||
glViewport(0, 0, ww, wh);
|
||||
if (wx_offset > 0) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(0, 0, wx_offset * gs, wh * gs);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glScissor((ww - wx_offset) * gs, 0, wx_offset * gs, wh * gs);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
if (wy_offset > 0) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(0, 0, ww * gs, wy_offset * gs);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glScissor(0, (wh - wy_offset) * gs, ww * gs, wy_offset * gs);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
glViewport(0, 0, pw, ph);
|
||||
y1 = vc->gfx.y0_top ? 0 : vc->gfx.h;
|
||||
y2 = vc->gfx.y0_top ? vc->gfx.h : 0;
|
||||
glBlitFramebuffer(0, y1, vc->gfx.w, y2,
|
||||
0, 0, ww, wh,
|
||||
wx_offset * gs, wy_offset * gs,
|
||||
(ww - wx_offset) * gs, (wh - wy_offset) * gs,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
#ifdef CONFIG_GBM
|
||||
if (dmabuf) {
|
||||
@@ -101,7 +140,7 @@ void gd_gl_area_draw(VirtualConsole *vc)
|
||||
}
|
||||
gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
|
||||
|
||||
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
|
||||
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph);
|
||||
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,16 +387,16 @@ static void *gd_win32_get_hwnd(VirtualConsole *vc)
|
||||
/** DisplayState Callbacks **/
|
||||
|
||||
static void gd_update(DisplayChangeListener *dcl,
|
||||
int x, int y, int w, int h)
|
||||
int fbx, int fby, int fbw, int fbh)
|
||||
{
|
||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||
GdkWindow *win;
|
||||
int x1, x2, y1, y2;
|
||||
int mx, my;
|
||||
int fbw, fbh;
|
||||
int ww, wh;
|
||||
int wx1, wx2, wy1, wy2;
|
||||
int wx_offset, wy_offset;
|
||||
int ww_surface, wh_surface;
|
||||
int ww_widget, wh_widget;
|
||||
|
||||
trace_gd_update(vc->label, x, y, w, h);
|
||||
trace_gd_update(vc->label, fbx, fby, fbw, fbh);
|
||||
|
||||
if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
|
||||
return;
|
||||
@@ -405,35 +405,36 @@ static void gd_update(DisplayChangeListener *dcl,
|
||||
if (vc->gfx.convert) {
|
||||
pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
|
||||
NULL, vc->gfx.convert,
|
||||
x, y, 0, 0, x, y, w, h);
|
||||
fbx, fby, 0, 0, fbx, fby, fbw, fbh);
|
||||
}
|
||||
|
||||
x1 = floor(x * vc->gfx.scale_x);
|
||||
y1 = floor(y * vc->gfx.scale_y);
|
||||
wx1 = floor(fbx * vc->gfx.scale_x);
|
||||
wy1 = floor(fby * vc->gfx.scale_y);
|
||||
|
||||
x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x);
|
||||
y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y);
|
||||
wx2 = ceil(fbx * vc->gfx.scale_x + fbw * vc->gfx.scale_x);
|
||||
wy2 = ceil(fby * vc->gfx.scale_y + fbh * vc->gfx.scale_y);
|
||||
|
||||
fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
|
||||
fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
|
||||
ww_surface = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
|
||||
wh_surface = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
|
||||
|
||||
win = gtk_widget_get_window(vc->gfx.drawing_area);
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
ww = gdk_window_get_width(win);
|
||||
wh = gdk_window_get_height(win);
|
||||
ww_widget = gdk_window_get_width(win);
|
||||
wh_widget = gdk_window_get_height(win);
|
||||
|
||||
mx = my = 0;
|
||||
if (ww > fbw) {
|
||||
mx = (ww - fbw) / 2;
|
||||
wx_offset = wy_offset = 0;
|
||||
if (ww_widget > ww_surface) {
|
||||
wx_offset = (ww_widget - ww_surface) / 2;
|
||||
}
|
||||
if (wh > fbh) {
|
||||
my = (wh - fbh) / 2;
|
||||
if (wh_widget > wh_surface) {
|
||||
wy_offset = (wh_widget - wh_surface) / 2;
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw_area(vc->gfx.drawing_area,
|
||||
mx + x1, my + y1, (x2 - x1), (y2 - y1));
|
||||
wx_offset + wx1, wy_offset + wy1,
|
||||
(wx2 - wx1), (wy2 - wy1));
|
||||
}
|
||||
|
||||
static void gd_refresh(DisplayChangeListener *dcl)
|
||||
@@ -771,8 +772,21 @@ static void gd_resize_event(GtkGLArea *area,
|
||||
gint width, gint height, gpointer *opaque)
|
||||
{
|
||||
VirtualConsole *vc = (void *)opaque;
|
||||
double pw = width, ph = height;
|
||||
double sx = vc->gfx.scale_x, sy = vc->gfx.scale_y;
|
||||
GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(area));
|
||||
const int gs = gdk_window_get_scale_factor(window);
|
||||
|
||||
gd_set_ui_size(vc, width, height);
|
||||
if (!vc->s->free_scale && !vc->s->full_screen) {
|
||||
pw /= sx;
|
||||
ph /= sy;
|
||||
}
|
||||
|
||||
/**
|
||||
* width and height here are in pixel coordinate, so we must divide it
|
||||
* by global window scale (gs)
|
||||
*/
|
||||
gd_set_ui_size(vc, pw / gs, ph / gs);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -800,12 +814,95 @@ void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget)
|
||||
#endif
|
||||
}
|
||||
|
||||
void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh)
|
||||
{
|
||||
if (!vc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vc->s->full_screen) {
|
||||
vc->gfx.scale_x = (double)ww / fbw;
|
||||
vc->gfx.scale_y = (double)wh / fbh;
|
||||
} else if (vc->s->free_scale) {
|
||||
double sx, sy;
|
||||
|
||||
sx = (double)ww / fbw;
|
||||
sy = (double)wh / fbh;
|
||||
|
||||
vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* DOC: Coordinate handling.
|
||||
*
|
||||
* We are coping with sizes and positions in various coordinates and the
|
||||
* handling of these coordinates is somewhat confusing. It would benefit us
|
||||
* all if we define these coordinates explicitly and clearly. Besides, it's
|
||||
* also helpful to follow the same naming convention for variables
|
||||
* representing values in different coordinates.
|
||||
*
|
||||
* I. Definitions
|
||||
*
|
||||
* - (guest) buffer coordinate: this is the coordinates that the guest will
|
||||
* see. The x/y offsets and width/height specified in commands sent by
|
||||
* guest is basically in buffer coordinate.
|
||||
*
|
||||
* - (host) pixel coordinate: this is the coordinate in pixel level on the
|
||||
* host destop. A window/widget of width 300 in pixel coordinate means it
|
||||
* occupies 300 pixels horizontally.
|
||||
*
|
||||
* - (host) logical window coordinate: the existence of global scaling
|
||||
* factor in desktop level makes this kind of coordinate play a role. It
|
||||
* always holds that (logical window size) * (global scale factor) =
|
||||
* (pixel size).
|
||||
*
|
||||
* - global scale factor: this is specified in desktop level and is
|
||||
* typically invariant during the life cycle of the process. Users with
|
||||
* high-DPI monitors might set this scale, for example, to 2, in order to
|
||||
* make the UI look larger.
|
||||
*
|
||||
* - zooming scale: this can be freely controlled by the QEMU user to zoom
|
||||
* in/out the guest content.
|
||||
*
|
||||
* II. Representation
|
||||
*
|
||||
* We'd like to use consistent representation for variables in different
|
||||
* coordinates:
|
||||
* - buffer coordinate: prefix fb
|
||||
* - pixel coordinate: prefix p
|
||||
* - logical window coordinate: prefix w
|
||||
*
|
||||
* For scales:
|
||||
* - global scale factor: prefix gs
|
||||
* - zooming scale: prefix scale/s
|
||||
*
|
||||
* Example: fbw, pw, ww for width in different coordinates
|
||||
*
|
||||
* III. Equation
|
||||
*
|
||||
* - fbw * gs * scale_x = pw
|
||||
* - pw = gs * ww
|
||||
*
|
||||
* Consequently we have
|
||||
*
|
||||
* - fbw * scale_x = ww
|
||||
*
|
||||
* Example: assuming we are running QEMU on a 3840x2160 screen and have set
|
||||
* global scaling factor to 2, if the guest buffer size is 1920x1080 and the
|
||||
* zooming scale is 0.5, then we have:
|
||||
* - fbw = 1920, fbh = 1080
|
||||
* - pw = 1920, ph = 1080
|
||||
* - ww = 960, wh = 540
|
||||
* A bonus of this configuration is that we can achieve pixel to pixel
|
||||
* presentation of the guest content.
|
||||
*/
|
||||
|
||||
static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
|
||||
{
|
||||
VirtualConsole *vc = opaque;
|
||||
GtkDisplayState *s = vc->s;
|
||||
int mx, my;
|
||||
int ww, wh;
|
||||
int wx_offset, wy_offset;
|
||||
int ww_widget, wh_widget, ww_surface, wh_surface;
|
||||
int fbw, fbh;
|
||||
|
||||
#if defined(CONFIG_OPENGL)
|
||||
@@ -839,46 +936,37 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
|
||||
fbw = surface_width(vc->gfx.ds);
|
||||
fbh = surface_height(vc->gfx.ds);
|
||||
|
||||
ww = gdk_window_get_width(gtk_widget_get_window(widget));
|
||||
wh = gdk_window_get_height(gtk_widget_get_window(widget));
|
||||
ww_widget = gdk_window_get_width(gtk_widget_get_window(widget));
|
||||
wh_widget = gdk_window_get_height(gtk_widget_get_window(widget));
|
||||
|
||||
if (s->full_screen) {
|
||||
vc->gfx.scale_x = (double)ww / fbw;
|
||||
vc->gfx.scale_y = (double)wh / fbh;
|
||||
} else if (s->free_scale) {
|
||||
double sx, sy;
|
||||
gd_update_scale(vc, ww_widget, wh_widget, fbw, fbh);
|
||||
|
||||
sx = (double)ww / fbw;
|
||||
sy = (double)wh / fbh;
|
||||
ww_surface = fbw * vc->gfx.scale_x;
|
||||
wh_surface = fbh * vc->gfx.scale_y;
|
||||
|
||||
vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
|
||||
wx_offset = wy_offset = 0;
|
||||
if (ww_widget > ww_surface) {
|
||||
wx_offset = (ww_widget - ww_surface) / 2;
|
||||
}
|
||||
if (wh_widget > wh_surface) {
|
||||
wy_offset = (wh_widget - wh_surface) / 2;
|
||||
}
|
||||
|
||||
fbw *= vc->gfx.scale_x;
|
||||
fbh *= vc->gfx.scale_y;
|
||||
|
||||
mx = my = 0;
|
||||
if (ww > fbw) {
|
||||
mx = (ww - fbw) / 2;
|
||||
}
|
||||
if (wh > fbh) {
|
||||
my = (wh - fbh) / 2;
|
||||
}
|
||||
|
||||
cairo_rectangle(cr, 0, 0, ww, wh);
|
||||
cairo_rectangle(cr, 0, 0, ww_widget, wh_widget);
|
||||
|
||||
/* Optionally cut out the inner area where the pixmap
|
||||
will be drawn. This avoids 'flashing' since we're
|
||||
not double-buffering. Note we're using the undocumented
|
||||
behaviour of drawing the rectangle from right to left
|
||||
to cut out the whole */
|
||||
cairo_rectangle(cr, mx + fbw, my,
|
||||
-1 * fbw, fbh);
|
||||
cairo_rectangle(cr, wx_offset + ww_surface, wy_offset,
|
||||
-1 * ww_surface, wh_surface);
|
||||
cairo_fill(cr);
|
||||
|
||||
cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y);
|
||||
cairo_set_source_surface(cr, vc->gfx.surface,
|
||||
mx / vc->gfx.scale_x, my / vc->gfx.scale_y);
|
||||
wx_offset / vc->gfx.scale_x,
|
||||
wy_offset / vc->gfx.scale_y);
|
||||
cairo_paint(cr);
|
||||
|
||||
return TRUE;
|
||||
@@ -889,19 +977,19 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
|
||||
{
|
||||
VirtualConsole *vc = opaque;
|
||||
GtkDisplayState *s = vc->s;
|
||||
int x, y;
|
||||
int mx, my;
|
||||
int fbh, fbw;
|
||||
int ww, wh;
|
||||
int fbx, fby;
|
||||
int wx_offset, wy_offset;
|
||||
int wh_surface, ww_surface;
|
||||
int ww_widget, wh_widget;
|
||||
|
||||
if (!vc->gfx.ds) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
|
||||
fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
|
||||
ww = gtk_widget_get_allocated_width(widget);
|
||||
wh = gtk_widget_get_allocated_height(widget);
|
||||
ww_surface = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
|
||||
wh_surface = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
|
||||
ww_widget = gtk_widget_get_allocated_width(widget);
|
||||
wh_widget = gtk_widget_get_allocated_height(widget);
|
||||
|
||||
/*
|
||||
* `widget` may not have the same size with the frame buffer.
|
||||
@@ -909,41 +997,42 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
|
||||
* To achieve that, `vc` will be displayed at (mx, my)
|
||||
* so that it is displayed at the center of the widget.
|
||||
*/
|
||||
mx = my = 0;
|
||||
if (ww > fbw) {
|
||||
mx = (ww - fbw) / 2;
|
||||
wx_offset = wy_offset = 0;
|
||||
if (ww_widget > ww_surface) {
|
||||
wx_offset = (ww_widget - ww_surface) / 2;
|
||||
}
|
||||
if (wh > fbh) {
|
||||
my = (wh - fbh) / 2;
|
||||
if (wh_widget > wh_surface) {
|
||||
wy_offset = (wh_widget - wh_surface) / 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* `motion` is reported in `widget` coordinates
|
||||
* so translating it to the coordinates in `vc`.
|
||||
*/
|
||||
x = (motion->x - mx) / vc->gfx.scale_x;
|
||||
y = (motion->y - my) / vc->gfx.scale_y;
|
||||
fbx = (motion->x - wx_offset) / vc->gfx.scale_x;
|
||||
fby = (motion->y - wy_offset) / vc->gfx.scale_y;
|
||||
|
||||
trace_gd_motion_event(ww, wh, gtk_widget_get_scale_factor(widget), x, y);
|
||||
trace_gd_motion_event(ww_widget, wh_widget,
|
||||
gtk_widget_get_scale_factor(widget), fbx, fby);
|
||||
|
||||
if (qemu_input_is_absolute(vc->gfx.dcl.con)) {
|
||||
if (x < 0 || y < 0 ||
|
||||
x >= surface_width(vc->gfx.ds) ||
|
||||
y >= surface_height(vc->gfx.ds)) {
|
||||
if (fbx < 0 || fby < 0 ||
|
||||
fbx >= surface_width(vc->gfx.ds) ||
|
||||
fby >= surface_height(vc->gfx.ds)) {
|
||||
return TRUE;
|
||||
}
|
||||
qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
|
||||
qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, fbx,
|
||||
0, surface_width(vc->gfx.ds));
|
||||
qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
|
||||
qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, fby,
|
||||
0, surface_height(vc->gfx.ds));
|
||||
qemu_input_event_sync();
|
||||
} else if (s->last_set && s->ptr_owner == vc) {
|
||||
qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x);
|
||||
qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y);
|
||||
qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, fbx - s->last_x);
|
||||
qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, fby - s->last_y);
|
||||
qemu_input_event_sync();
|
||||
}
|
||||
s->last_x = x;
|
||||
s->last_y = y;
|
||||
s->last_x = fbx;
|
||||
s->last_y = fby;
|
||||
s->last_set = TRUE;
|
||||
|
||||
if (!qemu_input_is_absolute(vc->gfx.dcl.con) && s->ptr_owner == vc) {
|
||||
@@ -1760,8 +1849,16 @@ static gboolean gd_configure(GtkWidget *widget,
|
||||
GdkEventConfigure *cfg, gpointer opaque)
|
||||
{
|
||||
VirtualConsole *vc = opaque;
|
||||
const double sx = vc->gfx.scale_x, sy = vc->gfx.scale_y;
|
||||
double width = cfg->width, height = cfg->height;
|
||||
|
||||
if (!vc->s->free_scale && !vc->s->full_screen) {
|
||||
width /= sx;
|
||||
height /= sy;
|
||||
}
|
||||
|
||||
gd_set_ui_size(vc, width, height);
|
||||
|
||||
gd_set_ui_size(vc, cfg->width, cfg->height);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -241,7 +241,7 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
|
||||
SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
|
||||
|
||||
SDL_GetWindowSize(scon->real_window, &ww, &wh);
|
||||
egl_fb_setup_default(&scon->win_fb, ww, wh);
|
||||
egl_fb_setup_default(&scon->win_fb, ww, wh, 0, 0);
|
||||
egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
|
||||
|
||||
SDL_GL_SwapWindow(scon->real_window);
|
||||
|
||||
@@ -488,14 +488,14 @@ static void handle_mousemotion(SDL_Event *ev)
|
||||
{
|
||||
int max_x, max_y;
|
||||
struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID);
|
||||
int scr_w, scr_h, surf_w, surf_h, x, y, dx, dy;
|
||||
|
||||
if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
|
||||
if (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) {
|
||||
int scr_w, scr_h;
|
||||
SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
|
||||
max_x = scr_w - 1;
|
||||
max_y = scr_h - 1;
|
||||
if (gui_grab && !gui_fullscreen
|
||||
@@ -509,9 +509,14 @@ static void handle_mousemotion(SDL_Event *ev)
|
||||
sdl_grab_start(scon);
|
||||
}
|
||||
}
|
||||
surf_w = surface_width(scon->surface);
|
||||
surf_h = surface_height(scon->surface);
|
||||
x = (int64_t)ev->motion.x * surf_w / scr_w;
|
||||
y = (int64_t)ev->motion.y * surf_h / scr_h;
|
||||
dx = (int64_t)ev->motion.xrel * surf_w / scr_w;
|
||||
dy = (int64_t)ev->motion.yrel * surf_h / scr_h;
|
||||
if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) {
|
||||
sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel,
|
||||
ev->motion.x, ev->motion.y, ev->motion.state);
|
||||
sdl_send_mouse_event(scon, dx, dy, x, y, ev->motion.state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,12 +525,17 @@ static void handle_mousebutton(SDL_Event *ev)
|
||||
int buttonstate = SDL_GetMouseState(NULL, NULL);
|
||||
SDL_MouseButtonEvent *bev;
|
||||
struct sdl2_console *scon = get_scon_from_window(ev->button.windowID);
|
||||
int scr_w, scr_h, x, y;
|
||||
|
||||
if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bev = &ev->button;
|
||||
SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
|
||||
x = (int64_t)bev->x * surface_width(scon->surface) / scr_w;
|
||||
y = (int64_t)bev->y * surface_height(scon->surface) / scr_h;
|
||||
|
||||
if (!gui_grab && !qemu_input_is_absolute(scon->dcl.con)) {
|
||||
if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) {
|
||||
/* start grabbing all events */
|
||||
@@ -537,7 +547,7 @@ static void handle_mousebutton(SDL_Event *ev)
|
||||
} else {
|
||||
buttonstate &= ~SDL_BUTTON(bev->button);
|
||||
}
|
||||
sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);
|
||||
sdl_send_mouse_event(scon, 0, 0, x, y, buttonstate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+175
-30
@@ -6,10 +6,10 @@
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/units.h"
|
||||
#include "hw/qdev-core.h"
|
||||
#include "migration/blocker.h"
|
||||
#include "ui/clipboard.h"
|
||||
#include "ui/console.h"
|
||||
#include "ui/input.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "qapi/qapi-types-char.h"
|
||||
@@ -32,14 +32,12 @@
|
||||
struct VDAgentChardev {
|
||||
Chardev parent;
|
||||
|
||||
/* TODO: migration isn't yet supported */
|
||||
Error *migration_blocker;
|
||||
|
||||
/* config */
|
||||
bool mouse;
|
||||
bool clipboard;
|
||||
|
||||
/* guest vdagent */
|
||||
bool connected;
|
||||
uint32_t caps;
|
||||
VDIChunkHeader chunk;
|
||||
uint32_t chunksize;
|
||||
@@ -47,7 +45,7 @@ struct VDAgentChardev {
|
||||
uint32_t msgsize;
|
||||
uint8_t *xbuf;
|
||||
uint32_t xoff, xsize;
|
||||
Buffer outbuf;
|
||||
GByteArray *outbuf;
|
||||
|
||||
/* mouse */
|
||||
DeviceState mouse_dev;
|
||||
@@ -142,16 +140,16 @@ static void vdagent_send_buf(VDAgentChardev *vd)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
while (!buffer_empty(&vd->outbuf)) {
|
||||
while (vd->outbuf->len) {
|
||||
len = qemu_chr_be_can_write(CHARDEV(vd));
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
if (len > vd->outbuf.offset) {
|
||||
len = vd->outbuf.offset;
|
||||
if (len > vd->outbuf->len) {
|
||||
len = vd->outbuf->len;
|
||||
}
|
||||
qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len);
|
||||
buffer_advance(&vd->outbuf, len);
|
||||
qemu_chr_be_write(CHARDEV(vd), vd->outbuf->data, len);
|
||||
g_byte_array_remove_range(vd->outbuf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +164,7 @@ static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg)
|
||||
|
||||
msg->protocol = VD_AGENT_PROTOCOL;
|
||||
|
||||
if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) {
|
||||
if (vd->outbuf->len + msgsize > VDAGENT_BUFFER_LIMIT) {
|
||||
error_report("buffer full, dropping message");
|
||||
return;
|
||||
}
|
||||
@@ -177,9 +175,8 @@ static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg)
|
||||
if (chunk.size > 1024) {
|
||||
chunk.size = 1024;
|
||||
}
|
||||
buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size);
|
||||
buffer_append(&vd->outbuf, &chunk, sizeof(chunk));
|
||||
buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size);
|
||||
g_byte_array_append(vd->outbuf, (void *)&chunk, sizeof(chunk));
|
||||
g_byte_array_append(vd->outbuf, msgbuf + msgoff, chunk.size);
|
||||
msgoff += chunk.size;
|
||||
}
|
||||
vdagent_send_buf(vd);
|
||||
@@ -672,10 +669,6 @@ static void vdagent_chr_open(Chardev *chr,
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (migrate_add_blocker(&vd->migration_blocker, errp) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
vd->mouse = VDAGENT_MOUSE_DEFAULT;
|
||||
if (cfg->has_mouse) {
|
||||
vd->mouse = cfg->mouse;
|
||||
@@ -694,6 +687,18 @@ static void vdagent_chr_open(Chardev *chr,
|
||||
*be_opened = true;
|
||||
}
|
||||
|
||||
static void vdagent_clipboard_peer_register(VDAgentChardev *vd)
|
||||
{
|
||||
if (vd->cbpeer.notifier.notify != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
vd->cbpeer.name = "vdagent";
|
||||
vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
|
||||
vd->cbpeer.request = vdagent_clipboard_request;
|
||||
qemu_clipboard_peer_register(&vd->cbpeer);
|
||||
}
|
||||
|
||||
static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
|
||||
{
|
||||
VDAgentAnnounceCapabilities *caps = (void *)msg->data;
|
||||
@@ -720,13 +725,9 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
|
||||
|
||||
memset(vd->last_serial, 0, sizeof(vd->last_serial));
|
||||
|
||||
if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) {
|
||||
if (have_clipboard(vd)) {
|
||||
qemu_clipboard_reset_serial();
|
||||
|
||||
vd->cbpeer.name = "vdagent";
|
||||
vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
|
||||
vd->cbpeer.request = vdagent_clipboard_request;
|
||||
qemu_clipboard_peer_register(&vd->cbpeer);
|
||||
vdagent_clipboard_peer_register(vd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,7 +860,8 @@ static void vdagent_disconnect(VDAgentChardev *vd)
|
||||
{
|
||||
trace_vdagent_disconnect();
|
||||
|
||||
buffer_reset(&vd->outbuf);
|
||||
vd->connected = false;
|
||||
g_byte_array_set_size(vd->outbuf, 0);
|
||||
vdagent_reset_bufs(vd);
|
||||
vd->caps = 0;
|
||||
if (vd->mouse_hs) {
|
||||
@@ -877,6 +879,10 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
|
||||
|
||||
trace_vdagent_fe_open(fe_open);
|
||||
|
||||
if (vd->connected == fe_open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fe_open) {
|
||||
trace_vdagent_close();
|
||||
vdagent_disconnect(vd);
|
||||
@@ -886,6 +892,7 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
|
||||
return;
|
||||
}
|
||||
|
||||
vd->connected = true;
|
||||
vdagent_send_caps(vd, true);
|
||||
}
|
||||
|
||||
@@ -916,25 +923,163 @@ static void vdagent_chr_class_init(ObjectClass *oc, const void *data)
|
||||
cc->chr_accept_input = vdagent_chr_accept_input;
|
||||
}
|
||||
|
||||
static int post_load(void *opaque, int version_id)
|
||||
{
|
||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(opaque);
|
||||
|
||||
if (have_mouse(vd) && vd->mouse_hs) {
|
||||
qemu_input_handler_activate(vd->mouse_hs);
|
||||
}
|
||||
|
||||
if (have_clipboard(vd)) {
|
||||
vdagent_clipboard_peer_register(vd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_chunk = {
|
||||
.name = "vdagent/chunk",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(port, VDIChunkHeader),
|
||||
VMSTATE_UINT32(size, VDIChunkHeader),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_vdba = {
|
||||
.name = "vdagent/bytearray",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(len, GByteArray),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(data, GByteArray, 0, 0, len),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
struct CBInfoArray {
|
||||
uint32_t n;
|
||||
QemuClipboardInfo cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_cbinfo_array = {
|
||||
.name = "cbinfoarray",
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(n, struct CBInfoArray),
|
||||
VMSTATE_STRUCT_VARRAY_UINT32(cbinfo, struct CBInfoArray, n,
|
||||
0, vmstate_cbinfo, QemuClipboardInfo),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static int put_cbinfo(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field, JSONWriter *vmdesc)
|
||||
{
|
||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv);
|
||||
struct CBInfoArray cbinfo = {};
|
||||
int i;
|
||||
|
||||
if (!have_clipboard(vd)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
||||
if (qemu_clipboard_peer_owns(&vd->cbpeer, i)) {
|
||||
cbinfo.cbinfo[cbinfo.n++] = *qemu_clipboard_info(i);
|
||||
}
|
||||
}
|
||||
|
||||
return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc);
|
||||
}
|
||||
|
||||
static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field)
|
||||
{
|
||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv);
|
||||
struct CBInfoArray cbinfo = {};
|
||||
int i, ret;
|
||||
|
||||
if (!have_clipboard(vd)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
vdagent_clipboard_peer_register(vd);
|
||||
|
||||
ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < cbinfo.n; i++) {
|
||||
g_autoptr(QemuClipboardInfo) info =
|
||||
qemu_clipboard_info_new(&vd->cbpeer, cbinfo.cbinfo[i].selection);
|
||||
/* this will steal clipboard data pointer from cbinfo.types */
|
||||
memcpy(info->types, cbinfo.cbinfo[i].types, sizeof(cbinfo.cbinfo[i].types));
|
||||
qemu_clipboard_update(info);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateInfo vmstate_cbinfos = {
|
||||
.name = "vdagent/cbinfos",
|
||||
.get = get_cbinfo,
|
||||
.put = put_cbinfo,
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_vdagent = {
|
||||
.name = "vdagent",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.post_load = post_load,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_BOOL(connected, VDAgentChardev),
|
||||
VMSTATE_UINT32(caps, VDAgentChardev),
|
||||
VMSTATE_STRUCT(chunk, VDAgentChardev, 0, vmstate_chunk, VDIChunkHeader),
|
||||
VMSTATE_UINT32(chunksize, VDAgentChardev),
|
||||
VMSTATE_UINT32(msgsize, VDAgentChardev),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(msgbuf, VDAgentChardev, 0, 0, msgsize),
|
||||
VMSTATE_UINT32(xsize, VDAgentChardev),
|
||||
VMSTATE_UINT32(xoff, VDAgentChardev),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(xbuf, VDAgentChardev, 0, 0, xsize),
|
||||
VMSTATE_STRUCT_POINTER(outbuf, VDAgentChardev, vmstate_vdba, GByteArray),
|
||||
VMSTATE_UINT32(mouse_x, VDAgentChardev),
|
||||
VMSTATE_UINT32(mouse_y, VDAgentChardev),
|
||||
VMSTATE_UINT32(mouse_btn, VDAgentChardev),
|
||||
VMSTATE_UINT32(mouse_display, VDAgentChardev),
|
||||
VMSTATE_UINT32_ARRAY(last_serial, VDAgentChardev,
|
||||
QEMU_CLIPBOARD_SELECTION__COUNT),
|
||||
VMSTATE_UINT32_ARRAY(cbpending, VDAgentChardev,
|
||||
QEMU_CLIPBOARD_SELECTION__COUNT),
|
||||
{
|
||||
.name = "cbinfos",
|
||||
.info = &vmstate_cbinfos,
|
||||
.flags = VMS_SINGLE,
|
||||
},
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void vdagent_chr_init(Object *obj)
|
||||
{
|
||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
|
||||
|
||||
buffer_init(&vd->outbuf, "vdagent-outbuf");
|
||||
error_setg(&vd->migration_blocker,
|
||||
"The vdagent chardev doesn't yet support migration");
|
||||
vd->outbuf = g_byte_array_new();
|
||||
vmstate_register_any(NULL, &vmstate_vdagent, vd);
|
||||
}
|
||||
|
||||
static void vdagent_chr_fini(Object *obj)
|
||||
{
|
||||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
|
||||
|
||||
migrate_del_blocker(&vd->migration_blocker);
|
||||
vdagent_disconnect(vd);
|
||||
if (vd->mouse_hs) {
|
||||
qemu_input_handler_unregister(vd->mouse_hs);
|
||||
}
|
||||
buffer_free(&vd->outbuf);
|
||||
g_clear_pointer(&vd->outbuf, g_byte_array_unref);
|
||||
}
|
||||
|
||||
static const TypeInfo vdagent_chr_type_info = {
|
||||
|
||||
Reference in New Issue
Block a user