aboutsummaryrefslogtreecommitdiff
path: root/ui/gtk.c
diff options
context:
space:
mode:
Diffstat (limited to 'ui/gtk.c')
-rw-r--r--ui/gtk.c245
1 files changed, 171 insertions, 74 deletions
diff --git a/ui/gtk.c b/ui/gtk.c
index 982037b..8c4a94c 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -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;
}
-
- 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;
+ if (wh_widget > wh_surface) {
+ wy_offset = (wh_widget - wh_surface) / 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;
}