blob: 59bda83da657a1f36746f7c583aa9875d43745d2 [file] [log] [blame]
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001/*
2 * GTK UI
3 *
4 * Copyright IBM, Corp. 2012
5 *
6 * Authors:
7 * Anthony Liguori <aliguori@us.ibm.com>
8 *
Thomas Huthac383782019-02-21 07:51:42 +01009 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060013 *
Thomas Huthac383782019-02-21 07:51:42 +010014 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <http://www.gnu.org/licenses/>.
21 *
22 * Portions from gtk-vnc (originally licensed under the LGPL v2+):
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060023 *
24 * GTK VNC Widget
25 *
26 * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
27 * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060028 */
29
Anthony Liguori834574e2013-02-20 07:43:24 -060030#define GETTEXT_PACKAGE "qemu"
31#define LOCALEDIR "po"
32
Peter Maydelle16f4c82016-01-29 17:49:51 +000033#include "qemu/osdep.h"
Markus Armbrustere688df62018-02-01 12:18:31 +010034#include "qapi/error.h"
Kevin Wolffa4dcf52020-01-29 11:22:37 +010035#include "qapi/qapi-commands-control.h"
Philippe Mathieu-Daudé90f8c0f2020-10-12 14:15:33 +020036#include "qapi/qapi-commands-machine.h"
Markus Armbruster112ed242018-02-26 17:13:27 -060037#include "qapi/qapi-commands-misc.h"
Veronia Bahaaf348b6d2016-03-20 19:16:19 +020038#include "qemu/cutils.h"
Thomas Huth5feed382023-02-10 12:19:31 +010039#include "qemu/error-report.h"
Vivek Kasireddy65b847d2021-09-14 14:18:35 -070040#include "qemu/main-loop.h"
Phil Dennis-Jordanf5ab12c2024-10-24 12:27:59 +020041#include "qemu-main.h"
Kevin Wolfc95e3082013-02-22 21:08:51 +010042
Gerd Hoffmanndc7ff342015-03-04 15:37:27 +010043#include "ui/console.h"
44#include "ui/gtk.h"
Volker Rümelinbd593d22020-05-16 09:20:05 +020045#ifdef G_OS_WIN32
46#include <gdk/gdkwin32.h>
47#endif
48#include "ui/win32-kbd-hook.h"
Kevin Wolfc95e3082013-02-22 21:08:51 +010049
Anthony Liguori834574e2013-02-20 07:43:24 -060050#include <glib/gi18n.h>
Stefan Weil3f58ead2013-02-22 07:28:01 +010051#include <locale.h>
Stefan Weilbbbf9bf2014-02-19 07:04:34 +010052#if defined(CONFIG_VTE)
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060053#include <vte/vte.h>
Stefan Weilbbbf9bf2014-02-19 07:04:34 +010054#endif
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060055#include <math.h>
56
Stefan Weilef0dd982013-11-10 16:24:02 +010057#include "trace.h"
Gerd Hoffmannaf98ba92013-11-28 11:40:27 +010058#include "ui/input.h"
Philippe Mathieu-Daudé32cad1f2024-12-03 15:20:13 +010059#include "system/runstate.h"
60#include "system/system.h"
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060061#include "keymaps.h"
Marc-André Lureau8228e352017-01-26 17:19:46 +040062#include "chardev/char.h"
Gerd Hoffmann6a24ced2014-05-05 14:36:56 +020063#include "qom/object.h"
Anthony Liguoria4ccabc2013-02-20 07:43:20 -060064
Gerd Hoffmann82fc1802014-05-16 12:26:12 +020065#define VC_WINDOW_X_MIN 320
66#define VC_WINDOW_Y_MIN 240
67#define VC_TERM_X_MIN 80
68#define VC_TERM_Y_MIN 25
69#define VC_SCALE_MIN 0.25
70#define VC_SCALE_STEP 0.25
Anthony Liguorid861def2013-02-20 07:43:21 -060071
Daniel P. Berrange2ec78702018-01-17 16:47:15 +000072#ifdef GDK_WINDOWING_X11
Michael S. Tsirkin0041e9a2018-05-03 22:51:00 +030073#include "x_keymap.h"
Daniel P. Berrange2ec78702018-01-17 16:47:15 +000074
75/* Gtk2 compat */
76#ifndef GDK_IS_X11_DISPLAY
77#define GDK_IS_X11_DISPLAY(dpy) (dpy != NULL)
78#endif
79#endif
80
81
82#ifdef GDK_WINDOWING_WAYLAND
83/* Gtk2 compat */
84#ifndef GDK_IS_WAYLAND_DISPLAY
85#define GDK_IS_WAYLAND_DISPLAY(dpy) (dpy != NULL)
86#endif
87#endif
88
89
90#ifdef GDK_WINDOWING_WIN32
91/* Gtk2 compat */
92#ifndef GDK_IS_WIN32_DISPLAY
93#define GDK_IS_WIN32_DISPLAY(dpy) (dpy != NULL)
94#endif
95#endif
96
97
98#ifdef GDK_WINDOWING_BROADWAY
99/* Gtk2 compat */
100#ifndef GDK_IS_BROADWAY_DISPLAY
101#define GDK_IS_BROADWAY_DISPLAY(dpy) (dpy != NULL)
102#endif
103#endif
104
105
106#ifdef GDK_WINDOWING_QUARTZ
107/* Gtk2 compat */
108#ifndef GDK_IS_QUARTZ_DISPLAY
109#define GDK_IS_QUARTZ_DISPLAY(dpy) (dpy != NULL)
110#endif
111#endif
112
113
Stefan Weilbbbf9bf2014-02-19 07:04:34 +0100114#if !defined(CONFIG_VTE)
115# define VTE_CHECK_VERSION(a, b, c) 0
116#endif
Daniel P. Berrangecba68832013-02-25 15:20:34 +0000117
Jan Kiszkab1e749c2013-07-22 09:04:32 +0200118#define HOTKEY_MODIFIERS (GDK_CONTROL_MASK | GDK_MOD1_MASK)
Jan Kiszkab1e749c2013-07-22 09:04:32 +0200119
Daniel P. Berrange2ec78702018-01-17 16:47:15 +0000120static const guint16 *keycode_map;
121static size_t keycode_maplen;
122
Eduardo Habkostdb1015e2020-09-03 16:43:22 -0400123struct VCChardev {
Marc-André Lureau0ec7b3e2016-12-07 16:20:22 +0300124 Chardev parent;
Marc-André Lureau41ac54b2016-10-21 23:44:44 +0300125 VirtualConsole *console;
126 bool echo;
Eduardo Habkostdb1015e2020-09-03 16:43:22 -0400127};
128typedef struct VCChardev VCChardev;
Marc-André Lureau41ac54b2016-10-21 23:44:44 +0300129
Marc-André Lureau777357d2016-12-07 18:39:10 +0300130#define TYPE_CHARDEV_VC "chardev-vc"
Eduardo Habkost8110fa12020-08-31 17:07:33 -0400131DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
132 TYPE_CHARDEV_VC)
Marc-André Lureau777357d2016-12-07 18:39:10 +0300133
Sergio Lopez5a4cb612023-05-26 13:29:25 +0200134static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX];
135
Gerd Hoffmann11c82b52018-03-06 10:09:46 +0100136bool gtk_use_gl_area;
137
Gerd Hoffmannd531dee2015-09-09 10:12:20 +0200138static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
Gerd Hoffmann2884cf52014-05-06 11:45:30 +0200139static void gd_ungrab_pointer(GtkDisplayState *s);
Gerd Hoffmannd531dee2015-09-09 10:12:20 +0200140static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
Gerd Hoffmannaa4f4052015-09-09 09:57:01 +0200141static void gd_ungrab_keyboard(GtkDisplayState *s);
Gerd Hoffmann2884cf52014-05-06 11:45:30 +0200142
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600143/** Utility Functions **/
144
Gerd Hoffmann271a25c2014-04-30 13:53:16 +0200145static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
146{
147 VirtualConsole *vc;
148 gint i;
149
150 for (i = 0; i < s->nb_vcs; i++) {
151 vc = &s->vc[i];
152 if (gtk_check_menu_item_get_active
153 (GTK_CHECK_MENU_ITEM(vc->menu_item))) {
154 return vc;
155 }
156 }
157 return NULL;
158}
159
160static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
161{
162 VirtualConsole *vc;
163 gint i, p;
164
165 for (i = 0; i < s->nb_vcs; i++) {
166 vc = &s->vc[i];
167 p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
168 if (p == page) {
169 return vc;
170 }
171 }
172 return NULL;
173}
174
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200175static VirtualConsole *gd_vc_find_current(GtkDisplayState *s)
176{
177 gint page;
178
179 page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook));
180 return gd_vc_find_by_page(s, page);
181}
182
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600183static bool gd_is_grab_active(GtkDisplayState *s)
184{
185 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
186}
187
188static bool gd_grab_on_hover(GtkDisplayState *s)
189{
190 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
191}
192
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200193static void gd_update_cursor(VirtualConsole *vc)
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600194{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200195 GtkDisplayState *s = vc->s;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600196 GdkWindow *window;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600197
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +0200198 if (vc->type != GD_VC_GFX ||
199 !qemu_console_is_graphic(vc->gfx.dcl.con)) {
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200200 return;
201 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600202
Hervé Poussineau4cdfc932015-03-24 20:08:48 +0100203 if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
204 return;
205 }
206
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200207 window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area));
Akihiko Odaki0337e412023-09-21 17:29:34 +0900208 if (s->full_screen || qemu_input_is_absolute(vc->gfx.dcl.con) || s->ptr_owner == vc) {
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600209 gdk_window_set_cursor(window, s->null_cursor);
210 } else {
211 gdk_window_set_cursor(window, NULL);
212 }
213}
214
215static void gd_update_caption(GtkDisplayState *s)
216{
217 const char *status = "";
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +0200218 gchar *prefix;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600219 gchar *title;
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600220 const char *grab = "";
Jan Kiszka30e8f222013-02-22 20:53:33 +0100221 bool is_paused = !runstate_is_running();
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +0200222 int i;
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600223
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +0200224 if (qemu_name) {
225 prefix = g_strdup_printf("QEMU (%s)", qemu_name);
226 } else {
227 prefix = g_strdup_printf("QEMU");
228 }
229
230 if (s->ptr_owner != NULL &&
231 s->ptr_owner->window == NULL) {
Aurelien Jarnod8da9ee2013-04-01 19:12:02 +0200232 grab = _(" - Press Ctrl+Alt+G to release grab");
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600233 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600234
Jan Kiszka30e8f222013-02-22 20:53:33 +0100235 if (is_paused) {
Aurelien Jarnod8da9ee2013-04-01 19:12:02 +0200236 status = _(" [Paused]");
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600237 }
Jan Kiszka30e8f222013-02-22 20:53:33 +0100238 s->external_pause_update = true;
239 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->pause_item),
240 is_paused);
241 s->external_pause_update = false;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600242
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +0200243 title = g_strdup_printf("%s%s%s", prefix, status, grab);
244 gtk_window_set_title(GTK_WINDOW(s->window), title);
245 g_free(title);
246
247 for (i = 0; i < s->nb_vcs; i++) {
248 VirtualConsole *vc = &s->vc[i];
249
250 if (!vc->window) {
251 continue;
252 }
253 title = g_strdup_printf("%s: %s%s%s", prefix, vc->label,
254 vc == s->kbd_owner ? " +kbd" : "",
255 vc == s->ptr_owner ? " +ptr" : "");
256 gtk_window_set_title(GTK_WINDOW(vc->window), title);
257 g_free(title);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600258 }
259
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +0200260 g_free(prefix);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600261}
262
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200263static void gd_update_geometry_hints(VirtualConsole *vc)
264{
265 GtkDisplayState *s = vc->s;
266 GdkWindowHints mask = 0;
267 GdkGeometry geo = {};
268 GtkWidget *geo_widget = NULL;
269 GtkWindow *geo_window;
270
271 if (vc->type == GD_VC_GFX) {
Gerd Hoffmannf98f43e2015-02-27 14:36:09 +0100272 if (!vc->gfx.ds) {
273 return;
274 }
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200275 if (s->free_scale) {
276 geo.min_width = surface_width(vc->gfx.ds) * VC_SCALE_MIN;
277 geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN;
278 mask |= GDK_HINT_MIN_SIZE;
279 } else {
280 geo.min_width = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
281 geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
282 mask |= GDK_HINT_MIN_SIZE;
283 }
284 geo_widget = vc->gfx.drawing_area;
285 gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height);
286
287#if defined(CONFIG_VTE)
288 } else if (vc->type == GD_VC_VTE) {
289 VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
Alberto Garcia6978dc42016-05-13 11:20:54 +0300290 GtkBorder padding = { 0 };
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200291
Cole Robinson84e2dc42016-05-06 14:03:13 -0400292#if VTE_CHECK_VERSION(0, 37, 0)
Cole Robinson84e2dc42016-05-06 14:03:13 -0400293 gtk_style_context_get_padding(
294 gtk_widget_get_style_context(vc->vte.terminal),
295 gtk_widget_get_state_flags(vc->vte.terminal),
296 &padding);
Cole Robinson84e2dc42016-05-06 14:03:13 -0400297#else
Alberto Garcia6978dc42016-05-13 11:20:54 +0300298 {
299 GtkBorder *ib = NULL;
300 gtk_widget_style_get(vc->vte.terminal, "inner-border", &ib, NULL);
301 if (ib) {
302 padding = *ib;
303 gtk_border_free(ib);
304 }
305 }
Cole Robinson84e2dc42016-05-06 14:03:13 -0400306#endif
307
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200308 geo.width_inc = vte_terminal_get_char_width(term);
309 geo.height_inc = vte_terminal_get_char_height(term);
310 mask |= GDK_HINT_RESIZE_INC;
311 geo.base_width = geo.width_inc;
312 geo.base_height = geo.height_inc;
313 mask |= GDK_HINT_BASE_SIZE;
314 geo.min_width = geo.width_inc * VC_TERM_X_MIN;
315 geo.min_height = geo.height_inc * VC_TERM_Y_MIN;
316 mask |= GDK_HINT_MIN_SIZE;
Cole Robinson84e2dc42016-05-06 14:03:13 -0400317
Alberto Garcia6978dc42016-05-13 11:20:54 +0300318 geo.base_width += padding.left + padding.right;
319 geo.base_height += padding.top + padding.bottom;
320 geo.min_width += padding.left + padding.right;
321 geo.min_height += padding.top + padding.bottom;
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200322 geo_widget = vc->vte.terminal;
323#endif
324 }
325
326 geo_window = GTK_WINDOW(vc->window ? vc->window : s->window);
327 gtk_window_set_geometry_hints(geo_window, geo_widget, &geo, mask);
328}
329
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100330void gd_update_windowsize(VirtualConsole *vc)
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600331{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200332 GtkDisplayState *s = vc->s;
333
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200334 gd_update_geometry_hints(vc);
Gerd Hoffmannd3ef5752014-05-05 14:55:18 +0200335
Gerd Hoffmann82fc1802014-05-16 12:26:12 +0200336 if (vc->type == GD_VC_GFX && !s->full_screen && !s->free_scale) {
337 gtk_window_resize(GTK_WINDOW(vc->window ? vc->window : s->window),
338 VC_WINDOW_X_MIN, VC_WINDOW_Y_MIN);
Gerd Hoffmannaa0a55d2014-05-06 10:20:53 +0200339 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600340}
341
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200342static void gd_update_full_redraw(VirtualConsole *vc)
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100343{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200344 GtkWidget *area = vc->gfx.drawing_area;
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100345 int ww, wh;
Daniel P. Berrangé89d85cd2018-08-22 14:15:52 +0100346 ww = gdk_window_get_width(gtk_widget_get_window(area));
347 wh = gdk_window_get_height(gtk_widget_get_window(area));
Paolo Bonzini5cb69562021-01-07 13:46:32 +0100348#if defined(CONFIG_OPENGL)
Gerd Hoffmann11c82b52018-03-06 10:09:46 +0100349 if (vc->gfx.gls && gtk_use_gl_area) {
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200350 gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
351 return;
352 }
353#endif
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200354 gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100355}
356
Jan Kiszka6db253c2013-03-24 19:10:02 +0100357static void gtk_release_modifiers(GtkDisplayState *s)
358{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200359 VirtualConsole *vc = gd_vc_find_current(s);
Jan Kiszka6db253c2013-03-24 19:10:02 +0100360
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +0200361 if (vc->type != GD_VC_GFX ||
362 !qemu_console_is_graphic(vc->gfx.dcl.con)) {
Jan Kiszka6db253c2013-03-24 19:10:02 +0100363 return;
364 }
Gerd Hoffmann0c0d4272019-01-22 10:28:11 +0100365 qkbd_state_lift_all_keys(vc->gfx.kbd);
Jan Kiszka6db253c2013-03-24 19:10:02 +0100366}
367
Gerd Hoffmann316cb062014-10-23 17:21:00 +0200368static void gd_widget_reparent(GtkWidget *from, GtkWidget *to,
369 GtkWidget *widget)
370{
371 g_object_ref(G_OBJECT(widget));
372 gtk_container_remove(GTK_CONTAINER(from), widget);
373 gtk_container_add(GTK_CONTAINER(to), widget);
374 g_object_unref(G_OBJECT(widget));
375}
376
Volker Rümelinbd593d22020-05-16 09:20:05 +0200377static void *gd_win32_get_hwnd(VirtualConsole *vc)
378{
379#ifdef G_OS_WIN32
380 return gdk_win32_window_get_impl_hwnd(
381 gtk_widget_get_window(vc->window ? vc->window : vc->s->window));
382#else
383 return NULL;
384#endif
385}
386
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100387/** DisplayState Callbacks **/
388
389static void gd_update(DisplayChangeListener *dcl,
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100390 int x, int y, int w, int h)
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100391{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200392 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +0200393 GdkWindow *win;
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100394 int x1, x2, y1, y2;
395 int mx, my;
396 int fbw, fbh;
397 int ww, wh;
398
Gerd Hoffmann74444bc2014-05-06 10:27:54 +0200399 trace_gd_update(vc->label, x, y, w, h);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100400
Hervé Poussineau4cdfc932015-03-24 20:08:48 +0100401 if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
402 return;
403 }
404
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200405 if (vc->gfx.convert) {
406 pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
407 NULL, vc->gfx.convert,
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200408 x, y, 0, 0, x, y, w, h);
409 }
410
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200411 x1 = floor(x * vc->gfx.scale_x);
412 y1 = floor(y * vc->gfx.scale_y);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100413
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200414 x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x);
415 y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100416
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200417 fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
418 fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100419
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +0200420 win = gtk_widget_get_window(vc->gfx.drawing_area);
421 if (!win) {
422 return;
423 }
Daniel P. Berrangé89d85cd2018-08-22 14:15:52 +0100424 ww = gdk_window_get_width(win);
425 wh = gdk_window_get_height(win);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100426
427 mx = my = 0;
428 if (ww > fbw) {
429 mx = (ww - fbw) / 2;
430 }
431 if (wh > fbh) {
432 my = (wh - fbh) / 2;
433 }
434
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200435 gtk_widget_queue_draw_area(vc->gfx.drawing_area,
436 mx + x1, my + y1, (x2 - x1), (y2 - y1));
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100437}
438
Gerd Hoffmannbc2ed972013-03-01 13:03:04 +0100439static void gd_refresh(DisplayChangeListener *dcl)
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100440{
Gerd Hoffmann284d1c62013-03-15 15:45:54 +0100441 graphic_hw_update(dcl->con);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100442}
443
Cole Robinsonbb732ee2016-05-06 14:03:14 -0400444static GdkDevice *gd_get_pointer(GdkDisplay *dpy)
445{
Cole Robinsonbb732ee2016-05-06 14:03:14 -0400446 return gdk_seat_get_pointer(gdk_display_get_default_seat(dpy));
Cole Robinsonbb732ee2016-05-06 14:03:14 -0400447}
448
Igor Mitsyankob0871432013-05-10 18:59:45 +0400449static void gd_mouse_set(DisplayChangeListener *dcl,
Akihiko Odakia418e7a2024-07-15 14:25:43 +0900450 int x, int y, bool visible)
Igor Mitsyankob0871432013-05-10 18:59:45 +0400451{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200452 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
Igor Mitsyankob0871432013-05-10 18:59:45 +0400453 GdkDisplay *dpy;
Igor Mitsyankob0871432013-05-10 18:59:45 +0400454 gint x_root, y_root;
455
Marc-André Lureau281a77d2023-03-20 17:26:24 +0400456 if (!gtk_widget_get_realized(vc->gfx.drawing_area) ||
Akihiko Odaki0337e412023-09-21 17:29:34 +0900457 qemu_input_is_absolute(dcl->con)) {
Cole Robinson2bda6602014-03-13 15:30:24 -0400458 return;
459 }
460
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200461 dpy = gtk_widget_get_display(vc->gfx.drawing_area);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200462 gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
Igor Mitsyankob0871432013-05-10 18:59:45 +0400463 x, y, &x_root, &y_root);
Cole Robinsonbb732ee2016-05-06 14:03:14 -0400464 gdk_device_warp(gd_get_pointer(dpy),
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200465 gtk_widget_get_screen(vc->gfx.drawing_area),
Cole Robinson298526f2014-03-13 15:30:23 -0400466 x_root, y_root);
Gerd Hoffmann1271f7f2014-07-01 19:12:45 +0200467 vc->s->last_x = x;
468 vc->s->last_y = y;
Igor Mitsyankob0871432013-05-10 18:59:45 +0400469}
Gerd Hoffmann9697f5d2013-03-20 09:11:41 +0100470
471static void gd_cursor_define(DisplayChangeListener *dcl,
472 QEMUCursor *c)
473{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200474 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
Gerd Hoffmann9697f5d2013-03-20 09:11:41 +0100475 GdkPixbuf *pixbuf;
476 GdkCursor *cursor;
477
Hervé Poussineau4cdfc932015-03-24 20:08:48 +0100478 if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
479 return;
480 }
481
Gerd Hoffmann9697f5d2013-03-20 09:11:41 +0100482 pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data),
483 GDK_COLORSPACE_RGB, true, 8,
484 c->width, c->height, c->width * 4,
485 NULL, NULL);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200486 cursor = gdk_cursor_new_from_pixbuf
487 (gtk_widget_get_display(vc->gfx.drawing_area),
488 pixbuf, c->hot_x, c->hot_y);
489 gdk_window_set_cursor(gtk_widget_get_window(vc->gfx.drawing_area), cursor);
Gerd Hoffmann9697f5d2013-03-20 09:11:41 +0100490 g_object_unref(pixbuf);
Stefan Weil030b4b72013-06-16 16:13:07 +0200491 g_object_unref(cursor);
Gerd Hoffmann9697f5d2013-03-20 09:11:41 +0100492}
493
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100494static void gd_switch(DisplayChangeListener *dcl,
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100495 DisplaySurface *surface)
496{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200497 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100498 bool resized = true;
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100499
Akihiko Odakie251b582021-03-08 23:07:13 +0900500 trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100501
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200502 if (vc->gfx.surface) {
503 cairo_surface_destroy(vc->gfx.surface);
Gerd Hoffmannf98f43e2015-02-27 14:36:09 +0100504 vc->gfx.surface = NULL;
505 }
506 if (vc->gfx.convert) {
507 pixman_image_unref(vc->gfx.convert);
508 vc->gfx.convert = NULL;
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100509 }
510
Akihiko Odakie251b582021-03-08 23:07:13 +0900511 if (vc->gfx.ds &&
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200512 surface_width(vc->gfx.ds) == surface_width(surface) &&
513 surface_height(vc->gfx.ds) == surface_height(surface)) {
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100514 resized = false;
515 }
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200516 vc->gfx.ds = surface;
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200517
Marc-André Lureauff174c62023-08-30 13:38:21 +0400518 if (surface_format(surface) == PIXMAN_x8r8g8b8) {
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200519 /*
520 * PIXMAN_x8r8g8b8 == CAIRO_FORMAT_RGB24
521 *
522 * No need to convert, use surface directly. Should be the
523 * common case as this is qemu_default_pixelformat(32) too.
524 */
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200525 vc->gfx.surface = cairo_image_surface_create_for_data
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200526 (surface_data(surface),
527 CAIRO_FORMAT_RGB24,
528 surface_width(surface),
529 surface_height(surface),
530 surface_stride(surface));
531 } else {
532 /* Must convert surface, use pixman to do it. */
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200533 vc->gfx.convert = pixman_image_create_bits(PIXMAN_x8r8g8b8,
534 surface_width(surface),
535 surface_height(surface),
536 NULL, 0);
537 vc->gfx.surface = cairo_image_surface_create_for_data
538 ((void *)pixman_image_get_data(vc->gfx.convert),
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200539 CAIRO_FORMAT_RGB24,
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200540 pixman_image_get_width(vc->gfx.convert),
541 pixman_image_get_height(vc->gfx.convert),
542 pixman_image_get_stride(vc->gfx.convert));
543 pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
544 NULL, vc->gfx.convert,
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200545 0, 0, 0, 0, 0, 0,
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200546 pixman_image_get_width(vc->gfx.convert),
547 pixman_image_get_height(vc->gfx.convert));
Gerd Hoffmannf0875532013-06-25 10:48:54 +0200548 }
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100549
550 if (resized) {
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200551 gd_update_windowsize(vc);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100552 } else {
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200553 gd_update_full_redraw(vc);
Gerd Hoffmann9d9801c2013-02-28 16:10:02 +0100554 }
555}
556
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100557static const DisplayChangeListenerOps dcl_ops = {
558 .dpy_name = "gtk",
559 .dpy_gfx_update = gd_update,
560 .dpy_gfx_switch = gd_switch,
561 .dpy_gfx_check_format = qemu_pixman_check_format,
562 .dpy_refresh = gd_refresh,
563 .dpy_mouse_set = gd_mouse_set,
564 .dpy_cursor_define = gd_cursor_define,
565};
566
567
568#if defined(CONFIG_OPENGL)
569
Marc-André Lureau52a37e22021-02-04 14:52:27 +0400570static bool gd_has_dmabuf(DisplayChangeListener *dcl)
571{
572 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
573
Marc-André Lureau26065192021-02-04 14:52:28 +0400574 if (gtk_use_gl_area && !gtk_widget_get_realized(vc->gfx.drawing_area)) {
575 /* FIXME: Assume it will work, actual check done after realize */
576 /* fixing this would require delaying listener registration */
577 return true;
578 }
579
Marc-André Lureau52a37e22021-02-04 14:52:27 +0400580 return vc->gfx.has_dmabuf;
581}
582
Vivek Kasireddy89faed62021-09-14 14:18:33 -0700583static void gd_gl_release_dmabuf(DisplayChangeListener *dcl,
584 QemuDmaBuf *dmabuf)
585{
586#ifdef CONFIG_GBM
Dongwon Kim2fc28072023-06-26 17:53:16 -0700587 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
588
Vivek Kasireddy89faed62021-09-14 14:18:33 -0700589 egl_dmabuf_release_texture(dmabuf);
Dongwon Kim2fc28072023-06-26 17:53:16 -0700590 if (vc->gfx.guest_fb.dmabuf == dmabuf) {
591 vc->gfx.guest_fb.dmabuf = NULL;
592 }
Vivek Kasireddy89faed62021-09-14 14:18:33 -0700593#endif
594}
595
Vivek Kasireddy65b847d2021-09-14 14:18:35 -0700596void gd_hw_gl_flushed(void *vcon)
597{
598 VirtualConsole *vc = vcon;
599 QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
Dongwon Kim6779a302024-05-08 10:54:00 -0700600 int fence_fd;
Vivek Kasireddy65b847d2021-09-14 14:18:35 -0700601
Dongwon Kimfa642682024-05-08 10:54:01 -0700602 fence_fd = qemu_dmabuf_get_fence_fd(dmabuf);
603 if (fence_fd >= 0) {
Dongwon Kim6779a302024-05-08 10:54:00 -0700604 qemu_set_fd_handler(fence_fd, NULL, NULL, NULL);
605 close(fence_fd);
Dongwon Kimfa642682024-05-08 10:54:01 -0700606 qemu_dmabuf_set_fence_fd(dmabuf, -1);
Dongwon Kime4e62512024-05-08 10:53:58 -0700607 graphic_hw_gl_block(vc->gfx.dcl.con, false);
608 }
Vivek Kasireddy65b847d2021-09-14 14:18:35 -0700609}
610
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100611/** DisplayState Callbacks (opengl version) **/
612
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200613static const DisplayChangeListenerOps dcl_gl_area_ops = {
614 .dpy_name = "gtk-egl",
615 .dpy_gfx_update = gd_gl_area_update,
616 .dpy_gfx_switch = gd_gl_area_switch,
617 .dpy_gfx_check_format = console_gl_check_format,
618 .dpy_refresh = gd_gl_area_refresh,
619 .dpy_mouse_set = gd_mouse_set,
620 .dpy_cursor_define = gd_cursor_define,
621
Gerd Hoffmannf4c36bd2017-02-21 10:37:16 +0100622 .dpy_gl_scanout_texture = gd_gl_area_scanout_texture,
Marc-André Lureau568b12f2021-02-04 14:52:19 +0400623 .dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200624 .dpy_gl_update = gd_gl_area_scanout_flush,
Marc-André Lureau26065192021-02-04 14:52:28 +0400625 .dpy_gl_scanout_dmabuf = gd_gl_area_scanout_dmabuf,
Vivek Kasireddy89faed62021-09-14 14:18:33 -0700626 .dpy_gl_release_dmabuf = gd_gl_release_dmabuf,
Marc-André Lureau26065192021-02-04 14:52:28 +0400627 .dpy_has_dmabuf = gd_has_dmabuf,
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200628};
629
Marc-André Lureaua62c4a12022-02-16 19:33:37 +0400630static bool
631gd_gl_area_is_compatible_dcl(DisplayGLCtx *dgc,
632 DisplayChangeListener *dcl)
633{
634 return dcl->ops == &dcl_gl_area_ops;
635}
636
Marc-André Lureau5e79d512021-10-09 23:48:46 +0400637static const DisplayGLCtxOps gl_area_ctx_ops = {
Marc-André Lureaua62c4a12022-02-16 19:33:37 +0400638 .dpy_gl_ctx_is_compatible_dcl = gd_gl_area_is_compatible_dcl,
Marc-André Lureau5e79d512021-10-09 23:48:46 +0400639 .dpy_gl_ctx_create = gd_gl_area_create_context,
640 .dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
641 .dpy_gl_ctx_make_current = gd_gl_area_make_current,
642};
Akihiko Odakibc6a3562021-02-23 15:03:07 +0900643
Marc-André Lureau5e79d512021-10-09 23:48:46 +0400644#ifdef CONFIG_X11
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100645static const DisplayChangeListenerOps dcl_egl_ops = {
646 .dpy_name = "gtk-egl",
647 .dpy_gfx_update = gd_egl_update,
648 .dpy_gfx_switch = gd_egl_switch,
649 .dpy_gfx_check_format = console_gl_check_format,
650 .dpy_refresh = gd_egl_refresh,
651 .dpy_mouse_set = gd_mouse_set,
652 .dpy_cursor_define = gd_cursor_define,
Gerd Hoffmann4782aeb2015-05-08 11:30:51 +0200653
Gerd Hoffmann543a7a12017-02-21 10:37:21 +0100654 .dpy_gl_scanout_disable = gd_egl_scanout_disable,
Gerd Hoffmannf4c36bd2017-02-21 10:37:16 +0100655 .dpy_gl_scanout_texture = gd_egl_scanout_texture,
Gerd Hoffmann70763fe2018-03-06 10:09:50 +0100656 .dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
Gerd Hoffmannf1bd3132018-03-06 10:09:51 +0100657 .dpy_gl_cursor_dmabuf = gd_egl_cursor_dmabuf,
658 .dpy_gl_cursor_position = gd_egl_cursor_position,
Vivek Kasireddyab971f82021-09-14 14:18:36 -0700659 .dpy_gl_update = gd_egl_flush,
Vivek Kasireddy89faed62021-09-14 14:18:33 -0700660 .dpy_gl_release_dmabuf = gd_gl_release_dmabuf,
Marc-André Lureau52a37e22021-02-04 14:52:27 +0400661 .dpy_has_dmabuf = gd_has_dmabuf,
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100662};
663
Marc-André Lureaua62c4a12022-02-16 19:33:37 +0400664static bool
665gd_egl_is_compatible_dcl(DisplayGLCtx *dgc,
666 DisplayChangeListener *dcl)
667{
668 return dcl->ops == &dcl_egl_ops;
669}
670
Marc-André Lureau5e79d512021-10-09 23:48:46 +0400671static const DisplayGLCtxOps egl_ctx_ops = {
Marc-André Lureaua62c4a12022-02-16 19:33:37 +0400672 .dpy_gl_ctx_is_compatible_dcl = gd_egl_is_compatible_dcl,
Marc-André Lureau5e79d512021-10-09 23:48:46 +0400673 .dpy_gl_ctx_create = gd_egl_create_context,
674 .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
675 .dpy_gl_ctx_make_current = gd_egl_make_current,
676};
Akihiko Odakibc6a3562021-02-23 15:03:07 +0900677#endif
678
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200679#endif /* CONFIG_OPENGL */
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100680
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600681/** QEMU Events **/
682
Philippe Mathieu-Daudé538f0492021-01-11 16:20:20 +0100683static void gd_change_runstate(void *opaque, bool running, RunState state)
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600684{
685 GtkDisplayState *s = opaque;
686
687 gd_update_caption(s);
688}
689
690static void gd_mouse_mode_change(Notifier *notify, void *data)
691{
Takashi Iwai800b0e82014-04-08 11:26:45 +0200692 GtkDisplayState *s;
Gerd Hoffmann99623c92014-05-06 11:48:28 +0200693 int i;
Takashi Iwai800b0e82014-04-08 11:26:45 +0200694
695 s = container_of(notify, GtkDisplayState, mouse_mode_notifier);
696 /* release the grab at switching to absolute mode */
Akihiko Odaki0337e412023-09-21 17:29:34 +0900697 if (s->ptr_owner && qemu_input_is_absolute(s->ptr_owner->gfx.dcl.con)) {
Akihiko Odaki8af5f822022-10-08 23:01:16 +0900698 if (!s->ptr_owner->window) {
699 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
700 FALSE);
701 } else {
702 gd_ungrab_pointer(s);
703 }
Takashi Iwai800b0e82014-04-08 11:26:45 +0200704 }
Gerd Hoffmann99623c92014-05-06 11:48:28 +0200705 for (i = 0; i < s->nb_vcs; i++) {
706 VirtualConsole *vc = &s->vc[i];
707 gd_update_cursor(vc);
708 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600709}
710
711/** GTK Events **/
712
713static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
714 void *opaque)
715{
716 GtkDisplayState *s = opaque;
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +0100717 bool allow_close = true;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600718
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +0100719 if (s->opts->has_window_close && !s->opts->window_close) {
720 allow_close = false;
721 }
722
723 if (allow_close) {
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600724 qmp_quit(NULL);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600725 }
726
727 return TRUE;
728}
729
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900730static void gd_set_ui_refresh_rate(VirtualConsole *vc, int refresh_rate)
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200731{
732 QemuUIInfo info;
733
Marc-André Lureau9bd4d3d2023-09-15 15:28:31 +0400734 if (!dpy_ui_info_supported(vc->gfx.dcl.con)) {
735 return;
736 }
737
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900738 info = *dpy_get_ui_info(vc->gfx.dcl.con);
739 info.refresh_rate = refresh_rate;
740 dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
741}
742
743static void gd_set_ui_size(VirtualConsole *vc, gint width, gint height)
744{
745 QemuUIInfo info;
746
Marc-André Lureau9bd4d3d2023-09-15 15:28:31 +0400747 if (!dpy_ui_info_supported(vc->gfx.dcl.con)) {
748 return;
749 }
750
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900751 info = *dpy_get_ui_info(vc->gfx.dcl.con);
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200752 info.width = width;
753 info.height = height;
Marc-André Lureauca19ef52021-04-13 20:39:11 +0400754 dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200755}
756
Paolo Bonzini5cb69562021-01-07 13:46:32 +0100757#if defined(CONFIG_OPENGL)
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200758
759static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context,
760 void *opaque)
761{
762 VirtualConsole *vc = opaque;
763
764 if (vc->gfx.gls) {
765 gd_gl_area_draw(vc);
766 }
767 return TRUE;
768}
769
770static void gd_resize_event(GtkGLArea *area,
771 gint width, gint height, gpointer *opaque)
772{
773 VirtualConsole *vc = (void *)opaque;
774
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900775 gd_set_ui_size(vc, width, height);
Gerd Hoffmann925a0402015-05-26 12:26:21 +0200776}
777
778#endif
779
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900780void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget)
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200781{
782#ifdef GDK_VERSION_3_22
Volker Rümelin0431e362020-12-13 17:57:23 +0100783 GdkWindow *win = gtk_widget_get_window(widget);
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900784 int refresh_rate;
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200785
786 if (win) {
Volker Rümelin0431e362020-12-13 17:57:23 +0100787 GdkDisplay *dpy = gtk_widget_get_display(widget);
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200788 GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900789 refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */
790 } else {
791 refresh_rate = 0;
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200792 }
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900793
794 gd_set_ui_refresh_rate(vc, refresh_rate);
795
796 /* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
797 vc->gfx.dcl.update_interval = refresh_rate ?
798 MIN(1000 * 1000 / refresh_rate, GUI_REFRESH_INTERVAL_DEFAULT) :
799 GUI_REFRESH_INTERVAL_DEFAULT;
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200800#endif
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200801}
802
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600803static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
804{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200805 VirtualConsole *vc = opaque;
806 GtkDisplayState *s = vc->s;
Anthony Liguoric6158482013-02-20 07:43:23 -0600807 int mx, my;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600808 int ww, wh;
809 int fbw, fbh;
810
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100811#if defined(CONFIG_OPENGL)
812 if (vc->gfx.gls) {
Gerd Hoffmann11c82b52018-03-06 10:09:46 +0100813 if (gtk_use_gl_area) {
814 /* invoke render callback please */
815 return FALSE;
816 } else {
Akihiko Odakibc6a3562021-02-23 15:03:07 +0900817#ifdef CONFIG_X11
Gerd Hoffmann11c82b52018-03-06 10:09:46 +0100818 gd_egl_draw(vc);
819 return TRUE;
Akihiko Odakibc6a3562021-02-23 15:03:07 +0900820#else
821 abort();
822#endif
Gerd Hoffmann11c82b52018-03-06 10:09:46 +0100823 }
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +0100824 }
825#endif
826
Anthony Liguoric6158482013-02-20 07:43:23 -0600827 if (!gtk_widget_get_realized(widget)) {
828 return FALSE;
829 }
Gerd Hoffmannf98f43e2015-02-27 14:36:09 +0100830 if (!vc->gfx.ds) {
831 return FALSE;
832 }
Dongwon Kim7cf87252021-11-03 23:51:51 -0700833 if (!vc->gfx.surface) {
834 return FALSE;
835 }
Anthony Liguoric6158482013-02-20 07:43:23 -0600836
Akihiko Odakiaeffd072022-02-26 20:55:15 +0900837 gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : s->window);
Philippe Mathieu-Daudédc264352020-08-17 19:23:31 +0200838
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200839 fbw = surface_width(vc->gfx.ds);
840 fbh = surface_height(vc->gfx.ds);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600841
Daniel P. Berrangé89d85cd2018-08-22 14:15:52 +0100842 ww = gdk_window_get_width(gtk_widget_get_window(widget));
843 wh = gdk_window_get_height(gtk_widget_get_window(widget));
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600844
Anthony Liguoric6158482013-02-20 07:43:23 -0600845 if (s->full_screen) {
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200846 vc->gfx.scale_x = (double)ww / fbw;
847 vc->gfx.scale_y = (double)wh / fbh;
Anthony Liguoric6158482013-02-20 07:43:23 -0600848 } else if (s->free_scale) {
849 double sx, sy;
850
851 sx = (double)ww / fbw;
852 sy = (double)wh / fbh;
853
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200854 vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600855 }
856
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200857 fbw *= vc->gfx.scale_x;
858 fbh *= vc->gfx.scale_y;
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600859
Anthony Liguoric6158482013-02-20 07:43:23 -0600860 mx = my = 0;
861 if (ww > fbw) {
862 mx = (ww - fbw) / 2;
863 }
864 if (wh > fbh) {
865 my = (wh - fbh) / 2;
866 }
867
868 cairo_rectangle(cr, 0, 0, ww, wh);
869
870 /* Optionally cut out the inner area where the pixmap
871 will be drawn. This avoids 'flashing' since we're
872 not double-buffering. Note we're using the undocumented
873 behaviour of drawing the rectangle from right to left
874 to cut out the whole */
875 cairo_rectangle(cr, mx + fbw, my,
876 -1 * fbw, fbh);
877 cairo_fill(cr);
878
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200879 cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y);
880 cairo_set_source_surface(cr, vc->gfx.surface,
881 mx / vc->gfx.scale_x, my / vc->gfx.scale_y);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600882 cairo_paint(cr);
883
884 return TRUE;
885}
886
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600887static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
888 void *opaque)
889{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200890 VirtualConsole *vc = opaque;
891 GtkDisplayState *s = vc->s;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600892 int x, y;
Anthony Liguoric6158482013-02-20 07:43:23 -0600893 int mx, my;
894 int fbh, fbw;
hikalium37e91412024-05-12 20:14:35 +0900895 int ww, wh;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600896
Gerd Hoffmannf98f43e2015-02-27 14:36:09 +0100897 if (!vc->gfx.ds) {
898 return TRUE;
899 }
900
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200901 fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
902 fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
Erico Nunes2f316632023-03-20 17:08:55 +0100903 ww = gtk_widget_get_allocated_width(widget);
904 wh = gtk_widget_get_allocated_height(widget);
Anthony Liguoric6158482013-02-20 07:43:23 -0600905
hikalium37e91412024-05-12 20:14:35 +0900906 /*
907 * `widget` may not have the same size with the frame buffer.
908 * In such cases, some paddings are needed around the `vc`.
909 * To achieve that, `vc` will be displayed at (mx, my)
910 * so that it is displayed at the center of the widget.
911 */
Anthony Liguoric6158482013-02-20 07:43:23 -0600912 mx = my = 0;
913 if (ww > fbw) {
914 mx = (ww - fbw) / 2;
915 }
916 if (wh > fbh) {
917 my = (wh - fbh) / 2;
918 }
919
hikalium37e91412024-05-12 20:14:35 +0900920 /*
921 * `motion` is reported in `widget` coordinates
922 * so translating it to the coordinates in `vc`.
923 */
924 x = (motion->x - mx) / vc->gfx.scale_x;
925 y = (motion->y - my) / vc->gfx.scale_y;
Anthony Liguoric6158482013-02-20 07:43:23 -0600926
hikalium36b8e6b2024-05-12 20:14:34 +0900927 trace_gd_motion_event(ww, wh, gtk_widget_get_scale_factor(widget), x, y);
928
Akihiko Odaki0337e412023-09-21 17:29:34 +0900929 if (qemu_input_is_absolute(vc->gfx.dcl.con)) {
Takashi Iwaie61031c2014-04-04 12:41:22 +0200930 if (x < 0 || y < 0 ||
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200931 x >= surface_width(vc->gfx.ds) ||
932 y >= surface_height(vc->gfx.ds)) {
Takashi Iwaie61031c2014-04-04 12:41:22 +0200933 return TRUE;
934 }
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200935 qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
Philippe Voinov9cfa7ab2017-05-05 15:39:52 +0200936 0, surface_width(vc->gfx.ds));
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200937 qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
Philippe Voinov9cfa7ab2017-05-05 15:39:52 +0200938 0, surface_height(vc->gfx.ds));
Gerd Hoffmann192f81b2013-11-28 12:06:04 +0100939 qemu_input_event_sync();
Gerd Hoffmann2884cf52014-05-06 11:45:30 +0200940 } else if (s->last_set && s->ptr_owner == vc) {
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200941 qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x);
942 qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y);
Gerd Hoffmann192f81b2013-11-28 12:06:04 +0100943 qemu_input_event_sync();
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600944 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600945 s->last_x = x;
946 s->last_y = y;
Takashi Iwaie61031c2014-04-04 12:41:22 +0200947 s->last_set = TRUE;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600948
Akihiko Odaki0337e412023-09-21 17:29:34 +0900949 if (!qemu_input_is_absolute(vc->gfx.dcl.con) && s->ptr_owner == vc) {
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200950 GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
Volker Rümelin7b23d122020-05-16 09:20:14 +0200951 GdkDisplay *dpy = gtk_widget_get_display(widget);
952 GdkWindow *win = gtk_widget_get_window(widget);
953 GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
954 GdkRectangle geometry;
Alberto Garcia76d8f932016-10-26 18:21:08 +0300955
Markus Armbrustere33e66b2023-09-21 14:13:08 +0200956 int xr = (int)motion->x_root;
957 int yr = (int)motion->y_root;
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600958
Volker Rümelin7b23d122020-05-16 09:20:14 +0200959 gdk_monitor_get_geometry(monitor, &geometry);
Alberto Garcia76d8f932016-10-26 18:21:08 +0300960
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600961 /* In relative mode check to see if client pointer hit
Dennis Wölfingcd6c7682021-07-20 16:39:41 +0200962 * one of the monitor edges, and if so move it back to the
963 * center of the monitor. This is important because the pointer
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600964 * in the server doesn't correspond 1-for-1, and so
965 * may still be only half way across the screen. Without
966 * this warp, the server pointer would thus appear to hit
967 * an invisible wall */
Markus Armbrustere33e66b2023-09-21 14:13:08 +0200968 if (xr <= geometry.x || xr - geometry.x >= geometry.width - 1 ||
969 yr <= geometry.y || yr - geometry.y >= geometry.height - 1) {
Daniel P. Berrange8906de72013-02-25 15:20:40 +0000970 GdkDevice *dev = gdk_event_get_device((GdkEvent *)motion);
Markus Armbrustere33e66b2023-09-21 14:13:08 +0200971 xr = geometry.x + geometry.width / 2;
972 yr = geometry.y + geometry.height / 2;
Dennis Wölfingcd6c7682021-07-20 16:39:41 +0200973
Markus Armbrustere33e66b2023-09-21 14:13:08 +0200974 gdk_device_warp(dev, screen, xr, yr);
Takashi Iwaie61031c2014-04-04 12:41:22 +0200975 s->last_set = FALSE;
Anthony Liguori5104a1f2013-02-20 07:43:22 -0600976 return FALSE;
977 }
978 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600979 return TRUE;
980}
981
982static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
983 void *opaque)
984{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +0200985 VirtualConsole *vc = opaque;
986 GtkDisplayState *s = vc->s;
Gerd Hoffmann192f81b2013-11-28 12:06:04 +0100987 InputButton btn;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -0600988
Takashi Iwai800b0e82014-04-08 11:26:45 +0200989 /* implicitly grab the input at the first click in the relative mode */
990 if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
Akihiko Odaki0337e412023-09-21 17:29:34 +0900991 !qemu_input_is_absolute(vc->gfx.dcl.con) && s->ptr_owner != vc) {
Gerd Hoffmann2884cf52014-05-06 11:45:30 +0200992 if (!vc->window) {
993 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
994 TRUE);
995 } else {
Gerd Hoffmannd531dee2015-09-09 10:12:20 +0200996 gd_grab_pointer(vc, "relative-mode-click");
Gerd Hoffmann2884cf52014-05-06 11:45:30 +0200997 }
Takashi Iwai800b0e82014-04-08 11:26:45 +0200998 return TRUE;
999 }
1000
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001001 if (button->button == 1) {
Gerd Hoffmann192f81b2013-11-28 12:06:04 +01001002 btn = INPUT_BUTTON_LEFT;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001003 } else if (button->button == 2) {
Gerd Hoffmann192f81b2013-11-28 12:06:04 +01001004 btn = INPUT_BUTTON_MIDDLE;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001005 } else if (button->button == 3) {
Gerd Hoffmann192f81b2013-11-28 12:06:04 +01001006 btn = INPUT_BUTTON_RIGHT;
Fabian Lesniak1266b682016-12-06 20:00:07 +01001007 } else if (button->button == 8) {
1008 btn = INPUT_BUTTON_SIDE;
1009 } else if (button->button == 9) {
1010 btn = INPUT_BUTTON_EXTRA;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001011 } else {
Gerd Hoffmann192f81b2013-11-28 12:06:04 +01001012 return TRUE;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001013 }
1014
K. Lange2297db82022-03-05 19:45:21 +09001015 if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) {
1016 return TRUE;
1017 }
1018
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001019 qemu_input_queue_btn(vc->gfx.dcl.con, btn,
1020 button->type == GDK_BUTTON_PRESS);
Gerd Hoffmann192f81b2013-11-28 12:06:04 +01001021 qemu_input_event_sync();
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001022 return TRUE;
1023}
1024
Jan Kiszkad58b9122014-03-11 17:26:44 +01001025static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
1026 void *opaque)
1027{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001028 VirtualConsole *vc = opaque;
Dmitry Petrov13cb3602022-01-08 16:39:45 +01001029 InputButton btn_vertical;
1030 InputButton btn_horizontal;
1031 bool has_vertical = false;
1032 bool has_horizontal = false;
Jan Kiszkad58b9122014-03-11 17:26:44 +01001033
1034 if (scroll->direction == GDK_SCROLL_UP) {
Dmitry Petrov13cb3602022-01-08 16:39:45 +01001035 btn_vertical = INPUT_BUTTON_WHEEL_UP;
1036 has_vertical = true;
Jan Kiszkad58b9122014-03-11 17:26:44 +01001037 } else if (scroll->direction == GDK_SCROLL_DOWN) {
Dmitry Petrov13cb3602022-01-08 16:39:45 +01001038 btn_vertical = INPUT_BUTTON_WHEEL_DOWN;
1039 has_vertical = true;
1040 } else if (scroll->direction == GDK_SCROLL_LEFT) {
1041 btn_horizontal = INPUT_BUTTON_WHEEL_LEFT;
1042 has_horizontal = true;
1043 } else if (scroll->direction == GDK_SCROLL_RIGHT) {
1044 btn_horizontal = INPUT_BUTTON_WHEEL_RIGHT;
1045 has_horizontal = true;
OGAWA Hirofumie229d1e2017-01-05 05:41:16 +09001046 } else if (scroll->direction == GDK_SCROLL_SMOOTH) {
1047 gdouble delta_x, delta_y;
1048 if (!gdk_event_get_scroll_deltas((GdkEvent *)scroll,
1049 &delta_x, &delta_y)) {
1050 return TRUE;
1051 }
Dmitry Petrov13cb3602022-01-08 16:39:45 +01001052
1053 if (delta_y > 0) {
1054 btn_vertical = INPUT_BUTTON_WHEEL_DOWN;
1055 has_vertical = true;
1056 } else if (delta_y < 0) {
1057 btn_vertical = INPUT_BUTTON_WHEEL_UP;
1058 has_vertical = true;
1059 } else if (delta_x > 0) {
1060 btn_horizontal = INPUT_BUTTON_WHEEL_RIGHT;
1061 has_horizontal = true;
1062 } else if (delta_x < 0) {
1063 btn_horizontal = INPUT_BUTTON_WHEEL_LEFT;
1064 has_horizontal = true;
OGAWA Hirofumie229d1e2017-01-05 05:41:16 +09001065 } else {
Dmitry Petrov13cb3602022-01-08 16:39:45 +01001066 return TRUE;
OGAWA Hirofumie229d1e2017-01-05 05:41:16 +09001067 }
Jan Kiszkad58b9122014-03-11 17:26:44 +01001068 } else {
1069 return TRUE;
1070 }
1071
Dmitry Petrov13cb3602022-01-08 16:39:45 +01001072 if (has_vertical) {
1073 qemu_input_queue_btn(vc->gfx.dcl.con, btn_vertical, true);
1074 qemu_input_event_sync();
1075 qemu_input_queue_btn(vc->gfx.dcl.con, btn_vertical, false);
1076 qemu_input_event_sync();
1077 }
1078
1079 if (has_horizontal) {
1080 qemu_input_queue_btn(vc->gfx.dcl.con, btn_horizontal, true);
1081 qemu_input_event_sync();
1082 qemu_input_queue_btn(vc->gfx.dcl.con, btn_horizontal, false);
1083 qemu_input_event_sync();
1084 }
1085
Jan Kiszkad58b9122014-03-11 17:26:44 +01001086 return TRUE;
1087}
1088
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001089
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001090static gboolean gd_touch_event(GtkWidget *widget, GdkEventTouch *touch,
1091 void *opaque)
1092{
1093 VirtualConsole *vc = opaque;
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001094 uint64_t num_slot = GPOINTER_TO_UINT(touch->sequence);
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001095 int type = -1;
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001096
1097 switch (touch->type) {
1098 case GDK_TOUCH_BEGIN:
1099 type = INPUT_MULTI_TOUCH_TYPE_BEGIN;
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001100 break;
1101 case GDK_TOUCH_UPDATE:
1102 type = INPUT_MULTI_TOUCH_TYPE_UPDATE;
1103 break;
1104 case GDK_TOUCH_END:
1105 case GDK_TOUCH_CANCEL:
1106 type = INPUT_MULTI_TOUCH_TYPE_END;
1107 break;
1108 default:
1109 warn_report("gtk: unexpected touch event type\n");
Bilal Elmoussaouib6596782023-06-19 11:53:36 +02001110 return FALSE;
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001111 }
1112
Bilal Elmoussaouib6596782023-06-19 11:53:36 +02001113 console_handle_touch_event(vc->gfx.dcl.con, touch_slots,
1114 num_slot, surface_width(vc->gfx.ds),
1115 surface_height(vc->gfx.ds), touch->x,
1116 touch->y, type, &error_warn);
Sergio Lopez5a4cb612023-05-26 13:29:25 +02001117 return TRUE;
1118}
1119
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001120static const guint16 *gd_get_keymap(size_t *maplen)
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001121{
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001122 GdkDisplay *dpy = gdk_display_get_default();
1123
1124#ifdef GDK_WINDOWING_X11
1125 if (GDK_IS_X11_DISPLAY(dpy)) {
1126 trace_gd_keymap_windowing("x11");
1127 return qemu_xkeymap_mapping_table(
1128 gdk_x11_display_get_xdisplay(dpy), maplen);
1129 }
1130#endif
1131
1132#ifdef GDK_WINDOWING_WAYLAND
1133 if (GDK_IS_WAYLAND_DISPLAY(dpy)) {
1134 trace_gd_keymap_windowing("wayland");
1135 *maplen = qemu_input_map_xorgevdev_to_qcode_len;
1136 return qemu_input_map_xorgevdev_to_qcode;
1137 }
1138#endif
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001139
Gerd Hoffmann0a337ed2014-05-28 22:33:06 +02001140#ifdef GDK_WINDOWING_WIN32
1141 if (GDK_IS_WIN32_DISPLAY(dpy)) {
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001142 trace_gd_keymap_windowing("win32");
Volker Rümelin14541922020-05-16 09:20:13 +02001143 *maplen = qemu_input_map_atset1_to_qcode_len;
1144 return qemu_input_map_atset1_to_qcode;
Stefan Weil2777ccc2013-12-07 16:25:17 +01001145 }
Gerd Hoffmann0a337ed2014-05-28 22:33:06 +02001146#endif
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001147
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001148#ifdef GDK_WINDOWING_QUARTZ
1149 if (GDK_IS_QUARTZ_DISPLAY(dpy)) {
1150 trace_gd_keymap_windowing("quartz");
1151 *maplen = qemu_input_map_osx_to_qcode_len;
1152 return qemu_input_map_osx_to_qcode;
1153 }
Gerd Hoffmann0a337ed2014-05-28 22:33:06 +02001154#endif
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001155
1156#ifdef GDK_WINDOWING_BROADWAY
1157 if (GDK_IS_BROADWAY_DISPLAY(dpy)) {
1158 trace_gd_keymap_windowing("broadway");
1159 g_warning("experimental: using broadway, x11 virtual keysym\n"
1160 "mapping - with very limited support. See also\n"
1161 "https://bugzilla.gnome.org/show_bug.cgi?id=700105");
1162 *maplen = qemu_input_map_x11_to_qcode_len;
1163 return qemu_input_map_x11_to_qcode;
1164 }
Daniel P. Berrangea8ffb372016-12-01 09:41:17 +00001165#endif
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001166
1167 g_warning("Unsupported GDK Windowing platform.\n"
1168 "Disabling extended keycode tables.\n"
1169 "Please report to qemu-devel@nongnu.org\n"
1170 "including the following information:\n"
1171 "\n"
1172 " - Operating system\n"
1173 " - GDK Windowing system build\n");
1174 return NULL;
1175}
1176
1177
1178static int gd_map_keycode(int scancode)
1179{
1180 if (!keycode_map) {
1181 return 0;
1182 }
1183 if (scancode > keycode_maplen) {
1184 return 0;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001185 }
1186
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00001187 return keycode_map[scancode];
Gerd Hoffmann932f2d72014-06-03 09:18:23 +02001188}
1189
Volker Rümelin14541922020-05-16 09:20:13 +02001190static int gd_get_keycode(GdkEventKey *key)
1191{
Volker Rümelin7b23d122020-05-16 09:20:14 +02001192#ifdef G_OS_WIN32
Volker Rümelin14541922020-05-16 09:20:13 +02001193 int scancode = gdk_event_get_scancode((GdkEvent *)key);
1194
1195 /* translate Windows native scancodes to atset1 keycodes */
1196 switch (scancode & (KF_EXTENDED | 0xff)) {
1197 case 0x145: /* NUMLOCK */
1198 return scancode & 0xff;
1199 }
1200
1201 return scancode & KF_EXTENDED ?
1202 0xe000 | (scancode & 0xff) : scancode & 0xff;
1203
1204#else
1205 return key->hardware_keycode;
1206#endif
1207}
1208
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001209static gboolean gd_text_key_down(GtkWidget *widget,
1210 GdkEventKey *key, void *opaque)
1211{
1212 VirtualConsole *vc = opaque;
Marc-André Lureau9db018a2023-08-30 13:38:18 +04001213 QemuTextConsole *con = QEMU_TEXT_CONSOLE(vc->gfx.dcl.con);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001214
Thomas Huth85617882016-10-27 14:17:27 +02001215 if (key->keyval == GDK_KEY_Delete) {
Marc-André Lureaucc6ba2c2023-08-30 13:38:20 +04001216 qemu_text_console_put_qcode(con, Q_KEY_CODE_DELETE, false);
Thomas Huth85617882016-10-27 14:17:27 +02001217 } else if (key->length) {
Marc-André Lureaucc6ba2c2023-08-30 13:38:20 +04001218 qemu_text_console_put_string(con, key->string, key->length);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001219 } else {
Volker Rümelin14541922020-05-16 09:20:13 +02001220 int qcode = gd_map_keycode(gd_get_keycode(key));
Marc-André Lureaucc6ba2c2023-08-30 13:38:20 +04001221 qemu_text_console_put_qcode(con, qcode, false);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001222 }
1223 return TRUE;
1224}
1225
Gerd Hoffmann932f2d72014-06-03 09:18:23 +02001226static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
1227{
1228 VirtualConsole *vc = opaque;
Volker Rümelin14541922020-05-16 09:20:13 +02001229 int keycode, qcode;
Gerd Hoffmann932f2d72014-06-03 09:18:23 +02001230
Volker Rümelind3953bf2020-05-16 09:20:12 +02001231#ifdef G_OS_WIN32
Daniel P. Berrange08774f62018-01-17 16:47:17 +00001232 /* on windows, we ought to ignore the reserved key event? */
1233 if (key->hardware_keycode == 0xff)
1234 return false;
Volker Rümelind3953bf2020-05-16 09:20:12 +02001235
1236 if (!vc->s->kbd_owner) {
1237 if (key->hardware_keycode == VK_LWIN ||
1238 key->hardware_keycode == VK_RWIN) {
1239 return FALSE;
1240 }
1241 }
Daniel P. Berrange08774f62018-01-17 16:47:17 +00001242#endif
1243
Daniel P. Berrange8026a812018-01-17 16:47:16 +00001244 if (key->keyval == GDK_KEY_Pause
1245#ifdef G_OS_WIN32
1246 /* for some reason GDK does not fill keyval for VK_PAUSE
1247 * See https://bugzilla.gnome.org/show_bug.cgi?id=769214
1248 */
1249 || key->hardware_keycode == VK_PAUSE
1250#endif
1251 ) {
Gerd Hoffmann0c0d4272019-01-22 10:28:11 +01001252 qkbd_state_key_event(vc->gfx.kbd, Q_KEY_CODE_PAUSE,
1253 key->type == GDK_KEY_PRESS);
Martin Decky5c960522014-09-16 16:04:40 +02001254 return TRUE;
1255 }
1256
Volker Rümelin14541922020-05-16 09:20:13 +02001257 keycode = gd_get_keycode(key);
1258 qcode = gd_map_keycode(keycode);
Gerd Hoffmann932f2d72014-06-03 09:18:23 +02001259
Volker Rümelin14541922020-05-16 09:20:13 +02001260 trace_gd_key_event(vc->label, keycode, qcode,
Stefan Weilef0dd982013-11-10 16:24:02 +01001261 (key->type == GDK_KEY_PRESS) ? "down" : "up");
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001262
Gerd Hoffmann0c0d4272019-01-22 10:28:11 +01001263 qkbd_state_key_event(vc->gfx.kbd, qcode,
1264 key->type == GDK_KEY_PRESS);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001265
1266 return TRUE;
1267}
1268
Volker Rümelin0c4b1a72020-05-16 09:20:06 +02001269static gboolean gd_grab_broken_event(GtkWidget *widget,
1270 GdkEventGrabBroken *event, void *opaque)
1271{
1272#ifdef CONFIG_WIN32
1273 /*
1274 * On Windows the Ctrl-Alt-Del key combination can't be grabbed. This
1275 * key combination leaves all three keys in a stuck condition. We use
1276 * the grab-broken-event to release all keys.
1277 */
1278 if (event->keyboard) {
1279 VirtualConsole *vc = opaque;
1280 GtkDisplayState *s = vc->s;
1281
1282 gtk_release_modifiers(s);
1283 }
1284#endif
1285 return TRUE;
1286}
1287
Takashi Iwai0d0e0442014-04-04 12:41:21 +02001288static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque)
1289{
1290 if (event->type == GDK_MOTION_NOTIFY) {
1291 return gd_motion_event(widget, &event->motion, opaque);
1292 }
1293 return FALSE;
1294}
1295
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001296/** Window Menu Actions **/
1297
Jan Kiszka30e8f222013-02-22 20:53:33 +01001298static void gd_menu_pause(GtkMenuItem *item, void *opaque)
1299{
1300 GtkDisplayState *s = opaque;
1301
1302 if (s->external_pause_update) {
1303 return;
1304 }
1305 if (runstate_is_running()) {
1306 qmp_stop(NULL);
1307 } else {
1308 qmp_cont(NULL);
1309 }
1310}
1311
1312static void gd_menu_reset(GtkMenuItem *item, void *opaque)
1313{
1314 qmp_system_reset(NULL);
1315}
1316
1317static void gd_menu_powerdown(GtkMenuItem *item, void *opaque)
1318{
1319 qmp_system_powerdown(NULL);
1320}
1321
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001322static void gd_menu_quit(GtkMenuItem *item, void *opaque)
1323{
1324 qmp_quit(NULL);
1325}
1326
1327static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
1328{
1329 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001330 VirtualConsole *vc = gd_vc_find_by_menu(s);
John Snowe72b59f2014-07-08 14:28:57 -04001331 GtkNotebook *nb = GTK_NOTEBOOK(s->notebook);
Gerd Hoffmann832189c2014-04-30 12:56:53 +02001332 gint page;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001333
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001334 gtk_release_modifiers(s);
1335 if (vc) {
John Snowe72b59f2014-07-08 14:28:57 -04001336 page = gtk_notebook_page_num(nb, vc->tab_item);
1337 gtk_notebook_set_current_page(nb, page);
Jan Kiszka9d677e12015-04-26 21:04:20 +02001338 gtk_widget_grab_focus(vc->focus);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001339 }
1340}
1341
Cole Robinson277836c2014-10-30 15:34:34 -04001342static void gd_accel_switch_vc(void *opaque)
1343{
1344 VirtualConsole *vc = opaque;
Jan Kiszka1a017162015-04-26 21:04:21 +02001345
Cole Robinson277836c2014-10-30 15:34:34 -04001346 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
1347}
1348
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001349static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
1350{
1351 GtkDisplayState *s = opaque;
Gerd Hoffmannfa7a1e52014-05-22 08:19:48 +02001352 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001353
1354 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
1355 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
1356 } else {
1357 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1358 }
Gerd Hoffmannfa7a1e52014-05-22 08:19:48 +02001359 gd_update_windowsize(vc);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001360}
1361
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001362static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
1363 void *opaque)
1364{
1365 VirtualConsole *vc = opaque;
1366 GtkDisplayState *s = vc->s;
1367
1368 gtk_widget_set_sensitive(vc->menu_item, true);
Gerd Hoffmann316cb062014-10-23 17:21:00 +02001369 gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001370 gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
1371 vc->tab_item, vc->label);
1372 gtk_widget_destroy(vc->window);
1373 vc->window = NULL;
Dongwon Kim1ab26282021-11-03 23:51:48 -07001374#if defined(CONFIG_OPENGL)
1375 if (vc->gfx.esurface) {
1376 eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
1377 vc->gfx.esurface = NULL;
1378 }
1379 if (vc->gfx.ectx) {
1380 eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
1381 vc->gfx.ectx = NULL;
1382 }
1383#endif
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001384 return TRUE;
1385}
1386
Gerd Hoffmann0c77a372014-05-06 12:51:00 +02001387static gboolean gd_win_grab(void *opaque)
1388{
1389 VirtualConsole *vc = opaque;
1390
1391 fprintf(stderr, "%s: %s\n", __func__, vc->label);
1392 if (vc->s->ptr_owner) {
1393 gd_ungrab_pointer(vc->s);
1394 } else {
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001395 gd_grab_pointer(vc, "user-request-detached-tab");
Gerd Hoffmann0c77a372014-05-06 12:51:00 +02001396 }
Gerd Hoffmann0c77a372014-05-06 12:51:00 +02001397 return TRUE;
1398}
1399
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001400static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
1401{
1402 GtkDisplayState *s = opaque;
1403 VirtualConsole *vc = gd_vc_find_current(s);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001404
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001405 if (vc->type == GD_VC_GFX &&
1406 qemu_console_is_graphic(vc->gfx.dcl.con)) {
Gerd Hoffmannaa0a55d2014-05-06 10:20:53 +02001407 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1408 FALSE);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001409 }
1410 if (!vc->window) {
1411 gtk_widget_set_sensitive(vc->menu_item, false);
1412 vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
Dongwon Kim1ab26282021-11-03 23:51:48 -07001413#if defined(CONFIG_OPENGL)
1414 if (vc->gfx.esurface) {
1415 eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
1416 vc->gfx.esurface = NULL;
1417 }
Sergey Mironovfb935692023-10-12 13:44:48 +03001418 if (vc->gfx.ectx) {
Dongwon Kim1ab26282021-11-03 23:51:48 -07001419 eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
1420 vc->gfx.ectx = NULL;
1421 }
1422#endif
Gerd Hoffmann316cb062014-10-23 17:21:00 +02001423 gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001424
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001425 g_signal_connect(vc->window, "delete-event",
1426 G_CALLBACK(gd_tab_window_close), vc);
1427 gtk_widget_show_all(vc->window);
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +02001428
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001429 if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
1430 GtkAccelGroup *ag = gtk_accel_group_new();
1431 gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
Gerd Hoffmann0c77a372014-05-06 12:51:00 +02001432
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001433 GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
1434 vc, NULL);
1435 gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
1436 }
Gerd Hoffmann0c77a372014-05-06 12:51:00 +02001437
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001438 gd_update_geometry_hints(vc);
Gerd Hoffmann4eeaa3a2014-05-06 11:20:17 +02001439 gd_update_caption(s);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001440 }
1441}
1442
Peter Wu1d187742018-05-11 01:07:38 +02001443static void gd_menu_show_menubar(GtkMenuItem *item, void *opaque)
1444{
1445 GtkDisplayState *s = opaque;
1446 VirtualConsole *vc = gd_vc_find_current(s);
1447
1448 if (s->full_screen) {
1449 return;
1450 }
1451
1452 if (gtk_check_menu_item_get_active(
1453 GTK_CHECK_MENU_ITEM(s->show_menubar_item))) {
1454 gtk_widget_show(s->menu_bar);
1455 } else {
1456 gtk_widget_hide(s->menu_bar);
1457 }
1458 gd_update_windowsize(vc);
1459}
1460
1461static void gd_accel_show_menubar(void *opaque)
1462{
1463 GtkDisplayState *s = opaque;
1464 gtk_menu_item_activate(GTK_MENU_ITEM(s->show_menubar_item));
1465}
1466
Anthony Liguoric6158482013-02-20 07:43:23 -06001467static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
1468{
1469 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001470 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguoric6158482013-02-20 07:43:23 -06001471
Stefan Weil10409282013-02-22 20:33:34 +01001472 if (!s->full_screen) {
Anthony Liguoric6158482013-02-20 07:43:23 -06001473 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
Cole Robinsonb0f31822014-10-30 15:34:35 -04001474 gtk_widget_hide(s->menu_bar);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001475 if (vc->type == GD_VC_GFX) {
1476 gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
Anthony Liguoric6158482013-02-20 07:43:23 -06001477 }
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001478 gtk_window_fullscreen(GTK_WINDOW(s->window));
Anthony Liguoric6158482013-02-20 07:43:23 -06001479 s->full_screen = TRUE;
1480 } else {
1481 gtk_window_unfullscreen(GTK_WINDOW(s->window));
1482 gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
Peter Wu1d187742018-05-11 01:07:38 +02001483 if (gtk_check_menu_item_get_active(
1484 GTK_CHECK_MENU_ITEM(s->show_menubar_item))) {
1485 gtk_widget_show(s->menu_bar);
1486 }
Anthony Liguoric6158482013-02-20 07:43:23 -06001487 s->full_screen = FALSE;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001488 if (vc->type == GD_VC_GFX) {
1489 vc->gfx.scale_x = 1.0;
1490 vc->gfx.scale_y = 1.0;
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001491 gd_update_windowsize(vc);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001492 }
Anthony Liguoric6158482013-02-20 07:43:23 -06001493 }
1494
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001495 gd_update_cursor(vc);
Anthony Liguoric6158482013-02-20 07:43:23 -06001496}
1497
Cole Robinson95414912014-10-30 15:34:33 -04001498static void gd_accel_full_screen(void *opaque)
1499{
1500 GtkDisplayState *s = opaque;
1501 gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
1502}
1503
Anthony Liguoric6158482013-02-20 07:43:23 -06001504static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
1505{
1506 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001507 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguoric6158482013-02-20 07:43:23 -06001508
1509 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
1510 FALSE);
1511
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001512 vc->gfx.scale_x += VC_SCALE_STEP;
1513 vc->gfx.scale_y += VC_SCALE_STEP;
Anthony Liguoric6158482013-02-20 07:43:23 -06001514
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001515 gd_update_windowsize(vc);
Anthony Liguoric6158482013-02-20 07:43:23 -06001516}
1517
Ziyue Yang66f6b822017-01-31 09:32:15 +08001518static void gd_accel_zoom_in(void *opaque)
1519{
1520 GtkDisplayState *s = opaque;
1521 gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_in_item));
1522}
1523
Anthony Liguoric6158482013-02-20 07:43:23 -06001524static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
1525{
1526 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001527 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguoric6158482013-02-20 07:43:23 -06001528
1529 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
1530 FALSE);
1531
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001532 vc->gfx.scale_x -= VC_SCALE_STEP;
1533 vc->gfx.scale_y -= VC_SCALE_STEP;
Anthony Liguoric6158482013-02-20 07:43:23 -06001534
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001535 vc->gfx.scale_x = MAX(vc->gfx.scale_x, VC_SCALE_MIN);
1536 vc->gfx.scale_y = MAX(vc->gfx.scale_y, VC_SCALE_MIN);
Anthony Liguoric6158482013-02-20 07:43:23 -06001537
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001538 gd_update_windowsize(vc);
Anthony Liguoric6158482013-02-20 07:43:23 -06001539}
1540
1541static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
1542{
1543 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001544 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguoric6158482013-02-20 07:43:23 -06001545
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001546 vc->gfx.scale_x = 1.0;
1547 vc->gfx.scale_y = 1.0;
Anthony Liguoric6158482013-02-20 07:43:23 -06001548
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001549 gd_update_windowsize(vc);
Anthony Liguoric6158482013-02-20 07:43:23 -06001550}
1551
1552static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
1553{
1554 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001555 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguoric6158482013-02-20 07:43:23 -06001556
1557 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
1558 s->free_scale = TRUE;
1559 } else {
1560 s->free_scale = FALSE;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001561 vc->gfx.scale_x = 1.0;
1562 vc->gfx.scale_y = 1.0;
Anthony Liguoric6158482013-02-20 07:43:23 -06001563 }
1564
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001565 gd_update_windowsize(vc);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001566 gd_update_full_redraw(vc);
Anthony Liguoric6158482013-02-20 07:43:23 -06001567}
1568
Gerd Hoffmanna69fc692016-05-12 09:29:06 +02001569static void gd_grab_update(VirtualConsole *vc, bool kbd, bool ptr)
1570{
1571 GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1572 GdkSeat *seat = gdk_display_get_default_seat(display);
1573 GdkWindow *window = gtk_widget_get_window(vc->gfx.drawing_area);
1574 GdkSeatCapabilities caps = 0;
1575 GdkCursor *cursor = NULL;
1576
1577 if (kbd) {
1578 caps |= GDK_SEAT_CAPABILITY_KEYBOARD;
1579 }
1580 if (ptr) {
1581 caps |= GDK_SEAT_CAPABILITY_ALL_POINTING;
1582 cursor = vc->s->null_cursor;
1583 }
1584
1585 if (caps) {
1586 gdk_seat_grab(seat, window, caps, false, cursor,
1587 NULL, NULL, NULL);
1588 } else {
1589 gdk_seat_ungrab(seat);
1590 }
1591}
Gerd Hoffmannf50def92014-05-19 11:52:18 +02001592
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001593static void gd_grab_keyboard(VirtualConsole *vc, const char *reason)
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001594{
Gerd Hoffmannaa4f4052015-09-09 09:57:01 +02001595 if (vc->s->kbd_owner) {
1596 if (vc->s->kbd_owner == vc) {
1597 return;
1598 } else {
1599 gd_ungrab_keyboard(vc->s);
1600 }
1601 }
1602
Volker Rümelinbd593d22020-05-16 09:20:05 +02001603 win32_kbd_set_grab(true);
Gerd Hoffmanna69fc692016-05-12 09:29:06 +02001604 gd_grab_update(vc, true, vc->s->ptr_owner == vc);
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001605 vc->s->kbd_owner = vc;
Gerd Hoffmann695cc592015-09-09 10:03:59 +02001606 gd_update_caption(vc->s);
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001607 trace_gd_grab(vc->label, "kbd", reason);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001608}
1609
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001610static void gd_ungrab_keyboard(GtkDisplayState *s)
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001611{
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001612 VirtualConsole *vc = s->kbd_owner;
1613
1614 if (vc == NULL) {
1615 return;
1616 }
1617 s->kbd_owner = NULL;
1618
Volker Rümelinbd593d22020-05-16 09:20:05 +02001619 win32_kbd_set_grab(false);
Gerd Hoffmanna69fc692016-05-12 09:29:06 +02001620 gd_grab_update(vc, false, vc->s->ptr_owner == vc);
Gerd Hoffmann695cc592015-09-09 10:03:59 +02001621 gd_update_caption(s);
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001622 trace_gd_ungrab(vc->label, "kbd");
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001623}
1624
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001625static void gd_grab_pointer(VirtualConsole *vc, const char *reason)
Daniel P. Berrange2a054852013-02-25 15:20:37 +00001626{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001627 GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
Gerd Hoffmannaa4f4052015-09-09 09:57:01 +02001628
1629 if (vc->s->ptr_owner) {
1630 if (vc->s->ptr_owner == vc) {
1631 return;
1632 } else {
1633 gd_ungrab_pointer(vc->s);
1634 }
1635 }
1636
Gerd Hoffmanna69fc692016-05-12 09:29:06 +02001637 gd_grab_update(vc, vc->s->kbd_owner == vc, true);
1638 gdk_device_get_position(gd_get_pointer(display),
1639 NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001640 vc->s->ptr_owner = vc;
Gerd Hoffmann695cc592015-09-09 10:03:59 +02001641 gd_update_caption(vc->s);
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001642 trace_gd_grab(vc->label, "ptr", reason);
Daniel P. Berrange2a054852013-02-25 15:20:37 +00001643}
1644
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001645static void gd_ungrab_pointer(GtkDisplayState *s)
Daniel P. Berrange2a054852013-02-25 15:20:37 +00001646{
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001647 VirtualConsole *vc = s->ptr_owner;
Gerd Hoffmann41cc5232016-05-20 11:49:08 +02001648 GdkDisplay *display;
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001649
1650 if (vc == NULL) {
1651 return;
1652 }
1653 s->ptr_owner = NULL;
1654
Gerd Hoffmann41cc5232016-05-20 11:49:08 +02001655 display = gtk_widget_get_display(vc->gfx.drawing_area);
Gerd Hoffmanna69fc692016-05-12 09:29:06 +02001656 gd_grab_update(vc, vc->s->kbd_owner == vc, false);
1657 gdk_device_warp(gd_get_pointer(display),
1658 gtk_widget_get_screen(vc->gfx.drawing_area),
1659 vc->s->grab_x_root, vc->s->grab_y_root);
Gerd Hoffmann695cc592015-09-09 10:03:59 +02001660 gd_update_caption(s);
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001661 trace_gd_ungrab(vc->label, "ptr");
Daniel P. Berrange2a054852013-02-25 15:20:37 +00001662}
1663
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001664static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
1665{
1666 GtkDisplayState *s = opaque;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001667 VirtualConsole *vc = gd_vc_find_current(s);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001668
1669 if (gd_is_grab_active(s)) {
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001670 gd_grab_keyboard(vc, "user-request-main-window");
1671 gd_grab_pointer(vc, "user-request-main-window");
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001672 } else {
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001673 gd_ungrab_keyboard(s);
1674 gd_ungrab_pointer(s);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001675 }
1676
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001677 gd_update_cursor(vc);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001678}
1679
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001680static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
1681 gpointer data)
1682{
1683 GtkDisplayState *s = data;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001684 VirtualConsole *vc;
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001685 gboolean on_vga;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001686
1687 if (!gtk_widget_get_realized(s->notebook)) {
1688 return;
1689 }
1690
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001691 vc = gd_vc_find_by_page(s, arg2);
1692 if (!vc) {
1693 return;
1694 }
1695 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item),
1696 TRUE);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02001697 on_vga = (vc->type == GD_VC_GFX &&
1698 qemu_console_is_graphic(vc->gfx.dcl.con));
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001699 if (!on_vga) {
1700 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1701 FALSE);
Anthony Liguoric6158482013-02-20 07:43:23 -06001702 } else if (s->full_screen) {
1703 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1704 TRUE);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001705 }
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001706 gtk_widget_set_sensitive(s->grab_item, on_vga);
Stefan Hajnoczia0815632016-12-14 14:25:18 +00001707#ifdef CONFIG_VTE
1708 gtk_widget_set_sensitive(s->copy_item, vc->type == GD_VC_VTE);
1709#endif
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001710
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001711 gd_update_windowsize(vc);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001712 gd_update_cursor(vc);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001713}
1714
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001715static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
1716 gpointer opaque)
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001717{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001718 VirtualConsole *vc = opaque;
1719 GtkDisplayState *s = vc->s;
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001720
Gerd Hoffmann2884cf52014-05-06 11:45:30 +02001721 if (gd_grab_on_hover(s)) {
Gerd Hoffmannd531dee2015-09-09 10:12:20 +02001722 gd_grab_keyboard(vc, "grab-on-hover");
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001723 }
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001724 return TRUE;
1725}
1726
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001727static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
1728 gpointer opaque)
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001729{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001730 VirtualConsole *vc = opaque;
1731 GtkDisplayState *s = vc->s;
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001732
Gerd Hoffmann2884cf52014-05-06 11:45:30 +02001733 if (gd_grab_on_hover(s)) {
Gerd Hoffmann4c638e22014-05-06 11:01:31 +02001734 gd_ungrab_keyboard(s);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001735 }
Anthony Liguori5104a1f2013-02-20 07:43:22 -06001736 return TRUE;
1737}
1738
Volker Rümelinbd593d22020-05-16 09:20:05 +02001739static gboolean gd_focus_in_event(GtkWidget *widget,
1740 GdkEventFocus *event, gpointer opaque)
1741{
1742 VirtualConsole *vc = opaque;
1743
1744 win32_kbd_set_window(gd_win32_get_hwnd(vc));
1745 return TRUE;
1746}
1747
Jan Kiszka6db253c2013-03-24 19:10:02 +01001748static gboolean gd_focus_out_event(GtkWidget *widget,
Volker Rümelinbd593d22020-05-16 09:20:05 +02001749 GdkEventFocus *event, gpointer opaque)
Jan Kiszka6db253c2013-03-24 19:10:02 +01001750{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001751 VirtualConsole *vc = opaque;
1752 GtkDisplayState *s = vc->s;
Jan Kiszka6db253c2013-03-24 19:10:02 +01001753
Volker Rümelinbd593d22020-05-16 09:20:05 +02001754 win32_kbd_set_window(NULL);
Jan Kiszka6db253c2013-03-24 19:10:02 +01001755 gtk_release_modifiers(s);
Jan Kiszka6db253c2013-03-24 19:10:02 +01001756 return TRUE;
1757}
1758
Gerd Hoffmann1301e512015-03-13 12:47:00 +01001759static gboolean gd_configure(GtkWidget *widget,
1760 GdkEventConfigure *cfg, gpointer opaque)
1761{
1762 VirtualConsole *vc = opaque;
Gerd Hoffmann1301e512015-03-13 12:47:00 +01001763
Akihiko Odakiaeffd072022-02-26 20:55:15 +09001764 gd_set_ui_size(vc, cfg->width, cfg->height);
Gerd Hoffmann1301e512015-03-13 12:47:00 +01001765 return FALSE;
1766}
1767
Anthony Liguorid861def2013-02-20 07:43:21 -06001768/** Virtual Console Callbacks **/
1769
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001770static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001771 int idx, GSList *group, GtkWidget *view_menu)
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001772{
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001773 vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
Cole Robinson277836c2014-10-30 15:34:34 -04001774 gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
1775 HOTKEY_MODIFIERS, 0,
1776 g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
Cole Robinson277836c2014-10-30 15:34:34 -04001777 gtk_accel_label_set_accel(
1778 GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
1779 GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001780
1781 g_signal_connect(vc->menu_item, "activate",
1782 G_CALLBACK(gd_menu_switch_vc), s);
1783 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
1784
Simran Singhalb3ac2b92020-04-01 22:23:14 +05301785 return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001786}
1787
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02001788#if defined(CONFIG_VTE)
Michael S. Tsirkin44b31e02016-04-17 23:25:54 +03001789static void gd_menu_copy(GtkMenuItem *item, void *opaque)
1790{
1791 GtkDisplayState *s = opaque;
1792 VirtualConsole *vc = gd_vc_find_current(s);
1793
Anthony PERARD82a4f1a2017-10-10 11:24:18 +01001794#if VTE_CHECK_VERSION(0, 50, 0)
1795 vte_terminal_copy_clipboard_format(VTE_TERMINAL(vc->vte.terminal),
1796 VTE_FORMAT_TEXT);
1797#else
Michael S. Tsirkin44b31e02016-04-17 23:25:54 +03001798 vte_terminal_copy_clipboard(VTE_TERMINAL(vc->vte.terminal));
Anthony PERARD82a4f1a2017-10-10 11:24:18 +01001799#endif
Michael S. Tsirkin44b31e02016-04-17 23:25:54 +03001800}
1801
Cole Robinson0fb20d12014-04-29 15:24:32 -04001802static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque)
1803{
1804 VirtualConsole *vc = opaque;
1805
1806 if (gtk_adjustment_get_upper(adjustment) >
1807 gtk_adjustment_get_page_size(adjustment)) {
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001808 gtk_widget_show(vc->vte.scrollbar);
Cole Robinson0fb20d12014-04-29 15:24:32 -04001809 } else {
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001810 gtk_widget_hide(vc->vte.scrollbar);
Cole Robinson0fb20d12014-04-29 15:24:32 -04001811 }
1812}
1813
Volker Rümelin584af1f2021-07-25 18:50:39 +02001814static void gd_vc_send_chars(VirtualConsole *vc)
1815{
1816 uint32_t len, avail;
1817
1818 len = qemu_chr_be_can_write(vc->vte.chr);
1819 avail = fifo8_num_used(&vc->vte.out_fifo);
Volker Rümelin7bce3302021-08-10 08:32:57 +02001820 while (len > 0 && avail > 0) {
Volker Rümelin584af1f2021-07-25 18:50:39 +02001821 const uint8_t *buf;
1822 uint32_t size;
1823
Philippe Mathieu-Daudé06252bf2024-07-22 13:25:01 +02001824 buf = fifo8_pop_bufptr(&vc->vte.out_fifo, MIN(len, avail), &size);
Volker Rümelincf6280b2022-10-22 16:12:04 +02001825 qemu_chr_be_write(vc->vte.chr, buf, size);
Volker Rümelin7bce3302021-08-10 08:32:57 +02001826 len = qemu_chr_be_can_write(vc->vte.chr);
1827 avail -= size;
Volker Rümelin584af1f2021-07-25 18:50:39 +02001828 }
1829}
1830
Marc-André Lureau0ec7b3e2016-12-07 16:20:22 +03001831static int gd_vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
Anthony Liguorid861def2013-02-20 07:43:21 -06001832{
Marc-André Lureau777357d2016-12-07 18:39:10 +03001833 VCChardev *vcd = VC_CHARDEV(chr);
Marc-André Lureau41ac54b2016-10-21 23:44:44 +03001834 VirtualConsole *vc = vcd->console;
Anthony Liguorid861def2013-02-20 07:43:21 -06001835
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001836 vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
Cole Robinsond4370742014-04-24 13:35:54 -04001837 return len;
Anthony Liguorid861def2013-02-20 07:43:21 -06001838}
1839
Volker Rümelin584af1f2021-07-25 18:50:39 +02001840static void gd_vc_chr_accept_input(Chardev *chr)
1841{
1842 VCChardev *vcd = VC_CHARDEV(chr);
1843 VirtualConsole *vc = vcd->console;
1844
Marc-André Lureau49152ac2023-02-20 11:22:51 +04001845 if (vc) {
1846 gd_vc_send_chars(vc);
1847 }
Volker Rümelin584af1f2021-07-25 18:50:39 +02001848}
1849
Marc-André Lureau0ec7b3e2016-12-07 16:20:22 +03001850static void gd_vc_chr_set_echo(Chardev *chr, bool echo)
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001851{
Marc-André Lureau777357d2016-12-07 18:39:10 +03001852 VCChardev *vcd = VC_CHARDEV(chr);
Marc-André Lureau41ac54b2016-10-21 23:44:44 +03001853 VirtualConsole *vc = vcd->console;
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001854
Marc-André Lureau41ac54b2016-10-21 23:44:44 +03001855 if (vc) {
1856 vc->vte.echo = echo;
1857 } else {
1858 vcd->echo = echo;
1859 }
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001860}
1861
Anthony Liguorid861def2013-02-20 07:43:21 -06001862static int nb_vcs;
Marc-André Lureau0ec7b3e2016-12-07 16:20:22 +03001863static Chardev *vcs[MAX_VCS];
Marc-André Lureau777357d2016-12-07 18:39:10 +03001864static void gd_vc_open(Chardev *chr,
1865 ChardevBackend *backend,
1866 bool *be_opened,
1867 Error **errp)
Anthony Liguorid861def2013-02-20 07:43:21 -06001868{
Marc-André Lureauc952b712016-12-07 13:55:11 +03001869 if (nb_vcs == MAX_VCS) {
1870 error_setg(errp, "Maximum number of consoles reached");
Marc-André Lureau777357d2016-12-07 18:39:10 +03001871 return;
Daniel P. Berrange919e11f2016-01-21 11:56:26 +00001872 }
1873
Anthony Liguorid861def2013-02-20 07:43:21 -06001874 vcs[nb_vcs++] = chr;
1875
Marc-André Lureau6f974c82016-12-07 15:13:50 +03001876 /* console/chardev init sometimes completes elsewhere in a 2nd
1877 * stage, so defer OPENED events until they are fully initialized
1878 */
1879 *be_opened = false;
Anthony Liguorid861def2013-02-20 07:43:21 -06001880}
1881
Marc-André Lureau777357d2016-12-07 18:39:10 +03001882static void char_gd_vc_class_init(ObjectClass *oc, void *data)
1883{
1884 ChardevClass *cc = CHARDEV_CLASS(oc);
1885
1886 cc->open = gd_vc_open;
1887 cc->chr_write = gd_vc_chr_write;
Volker Rümelin584af1f2021-07-25 18:50:39 +02001888 cc->chr_accept_input = gd_vc_chr_accept_input;
Marc-André Lureau777357d2016-12-07 18:39:10 +03001889 cc->chr_set_echo = gd_vc_chr_set_echo;
1890}
1891
1892static const TypeInfo char_gd_vc_type_info = {
1893 .name = TYPE_CHARDEV_VC,
1894 .parent = TYPE_CHARDEV,
Marc-André Lureau6f974c82016-12-07 15:13:50 +03001895 .instance_size = sizeof(VCChardev),
Marc-André Lureau777357d2016-12-07 18:39:10 +03001896 .class_init = char_gd_vc_class_init,
1897};
1898
Cole Robinsond4370742014-04-24 13:35:54 -04001899static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
1900 gpointer user_data)
Anthony Liguorid861def2013-02-20 07:43:21 -06001901{
Cole Robinsond4370742014-04-24 13:35:54 -04001902 VirtualConsole *vc = user_data;
Volker Rümelin584af1f2021-07-25 18:50:39 +02001903 uint32_t free;
Anthony Liguorid861def2013-02-20 07:43:21 -06001904
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001905 if (vc->vte.echo) {
1906 VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
1907 int i;
1908 for (i = 0; i < size; i++) {
1909 uint8_t c = text[i];
1910 if (c >= 128 || isprint(c)) {
1911 /* 8-bit characters are considered printable. */
1912 vte_terminal_feed(term, &text[i], 1);
1913 } else if (c == '\r' || c == '\n') {
1914 vte_terminal_feed(term, "\r\n", 2);
1915 } else {
1916 char ctrl[2] = { '^', 0};
1917 ctrl[1] = text[i] ^ 64;
1918 vte_terminal_feed(term, ctrl, 2);
1919 }
1920 }
1921 }
1922
Volker Rümelin584af1f2021-07-25 18:50:39 +02001923 free = fifo8_num_free(&vc->vte.out_fifo);
1924 fifo8_push_all(&vc->vte.out_fifo, (uint8_t *)text, MIN(free, size));
1925 gd_vc_send_chars(vc);
Zack Marvel8eb13bb2021-02-21 10:06:13 -07001926
Anthony Liguorid861def2013-02-20 07:43:21 -06001927 return TRUE;
1928}
1929
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001930static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
Marc-André Lureau0ec7b3e2016-12-07 16:20:22 +03001931 Chardev *chr, int idx,
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001932 GSList *group, GtkWidget *view_menu)
Anthony Liguorid861def2013-02-20 07:43:21 -06001933{
Anthony Liguorid861def2013-02-20 07:43:21 -06001934 char buffer[32];
Cole Robinson0fb20d12014-04-29 15:24:32 -04001935 GtkWidget *box;
1936 GtkWidget *scrollbar;
1937 GtkAdjustment *vadjustment;
Marc-André Lureau777357d2016-12-07 18:39:10 +03001938 VCChardev *vcd = VC_CHARDEV(chr);
Anthony Liguorid861def2013-02-20 07:43:21 -06001939
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001940 vc->s = s;
Marc-André Lureau41ac54b2016-10-21 23:44:44 +03001941 vc->vte.echo = vcd->echo;
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001942 vc->vte.chr = chr;
Volker Rümelin584af1f2021-07-25 18:50:39 +02001943 fifo8_create(&vc->vte.out_fifo, 4096);
Marc-André Lureau41ac54b2016-10-21 23:44:44 +03001944 vcd->console = vc;
Anthony Liguorid861def2013-02-20 07:43:21 -06001945
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001946 snprintf(buffer, sizeof(buffer), "vc%d", idx);
Philippe Mathieu-Daudé9038ac02025-01-30 11:57:43 +01001947 vc->label = g_strdup(vc->vte.chr->label ? : buffer);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001948 group = gd_vc_menu_init(s, vc, idx, group, view_menu);
Anthony Liguorid861def2013-02-20 07:43:21 -06001949
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001950 vc->vte.terminal = vte_terminal_new();
1951 g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
Anthony Liguorid861def2013-02-20 07:43:21 -06001952
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001953 /* The documentation says that the default is UTF-8, but actually it is
Kevin Wolf64159942018-10-11 17:30:39 +02001954 * 7-bit ASCII at least in VTE 0.38. The function is deprecated since
1955 * VTE 0.54 (only UTF-8 is supported now). */
1956#if !VTE_CHECK_VERSION(0, 54, 0)
Olaf Hering4d594232016-06-08 21:43:52 +00001957#if VTE_CHECK_VERSION(0, 38, 0)
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001958 vte_terminal_set_encoding(VTE_TERMINAL(vc->vte.terminal), "UTF-8", NULL);
1959#else
1960 vte_terminal_set_encoding(VTE_TERMINAL(vc->vte.terminal), "UTF-8");
1961#endif
Kevin Wolf64159942018-10-11 17:30:39 +02001962#endif
Paolo Bonzinifba958c2015-12-17 13:47:02 +01001963
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001964 vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1);
Gerd Hoffmann82fc1802014-05-16 12:26:12 +02001965 vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal),
1966 VC_TERM_X_MIN, VC_TERM_Y_MIN);
Anthony Liguorid861def2013-02-20 07:43:21 -06001967
Daniel P. Berrangé89d85cd2018-08-22 14:15:52 +01001968#if VTE_CHECK_VERSION(0, 28, 0)
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001969 vadjustment = gtk_scrollable_get_vadjustment
1970 (GTK_SCROLLABLE(vc->vte.terminal));
Cole Robinson0fb20d12014-04-29 15:24:32 -04001971#else
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001972 vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
Cole Robinson0fb20d12014-04-29 15:24:32 -04001973#endif
Anthony Liguorid861def2013-02-20 07:43:21 -06001974
Cole Robinson0fb20d12014-04-29 15:24:32 -04001975 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1976 scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
Cole Robinson0fb20d12014-04-29 15:24:32 -04001977
Jan Kiszka63ad9322018-02-17 12:26:49 +01001978 gtk_box_pack_end(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);
1979 gtk_box_pack_end(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
Cole Robinson0fb20d12014-04-29 15:24:32 -04001980
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001981 vc->vte.box = box;
1982 vc->vte.scrollbar = scrollbar;
Cole Robinson0fb20d12014-04-29 15:24:32 -04001983
1984 g_signal_connect(vadjustment, "changed",
1985 G_CALLBACK(gd_vc_adjustment_changed), vc);
1986
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02001987 vc->type = GD_VC_VTE;
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001988 vc->tab_item = box;
Jan Kiszka9d677e12015-04-26 21:04:20 +02001989 vc->focus = vc->vte.terminal;
Gerd Hoffmann271a25c2014-04-30 13:53:16 +02001990 gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item,
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02001991 gtk_label_new(vc->label));
Anthony Liguorid861def2013-02-20 07:43:21 -06001992
Marc-André Lureau63618132016-12-14 14:23:02 +03001993 qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
Anthony Liguorid861def2013-02-20 07:43:21 -06001994
Anthony Liguorid861def2013-02-20 07:43:21 -06001995 return group;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06001996}
1997
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02001998static void gd_vcs_init(GtkDisplayState *s, GSList *group,
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02001999 GtkWidget *view_menu)
2000{
2001 int i;
2002
2003 for (i = 0; i < nb_vcs; i++) {
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02002004 VirtualConsole *vc = &s->vc[s->nb_vcs];
2005 group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002006 s->nb_vcs++;
2007 }
2008}
2009#endif /* CONFIG_VTE */
2010
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002011/** Window Creation **/
2012
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002013static void gd_connect_vc_gfx_signals(VirtualConsole *vc)
2014{
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002015 g_signal_connect(vc->gfx.drawing_area, "draw",
2016 G_CALLBACK(gd_draw_event), vc);
Paolo Bonzini5cb69562021-01-07 13:46:32 +01002017#if defined(CONFIG_OPENGL)
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002018 if (gtk_use_gl_area) {
Gerd Hoffmann925a0402015-05-26 12:26:21 +02002019 /* wire up GtkGlArea events */
2020 g_signal_connect(vc->gfx.drawing_area, "render",
2021 G_CALLBACK(gd_render_event), vc);
2022 g_signal_connect(vc->gfx.drawing_area, "resize",
2023 G_CALLBACK(gd_resize_event), vc);
2024 }
2025#endif
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02002026 if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
2027 g_signal_connect(vc->gfx.drawing_area, "event",
2028 G_CALLBACK(gd_event), vc);
2029 g_signal_connect(vc->gfx.drawing_area, "button-press-event",
2030 G_CALLBACK(gd_button_event), vc);
2031 g_signal_connect(vc->gfx.drawing_area, "button-release-event",
2032 G_CALLBACK(gd_button_event), vc);
2033 g_signal_connect(vc->gfx.drawing_area, "scroll-event",
2034 G_CALLBACK(gd_scroll_event), vc);
2035 g_signal_connect(vc->gfx.drawing_area, "key-press-event",
2036 G_CALLBACK(gd_key_event), vc);
2037 g_signal_connect(vc->gfx.drawing_area, "key-release-event",
2038 G_CALLBACK(gd_key_event), vc);
Sergio Lopez5a4cb612023-05-26 13:29:25 +02002039 g_signal_connect(vc->gfx.drawing_area, "touch-event",
2040 G_CALLBACK(gd_touch_event), vc);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002041
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02002042 g_signal_connect(vc->gfx.drawing_area, "enter-notify-event",
2043 G_CALLBACK(gd_enter_event), vc);
2044 g_signal_connect(vc->gfx.drawing_area, "leave-notify-event",
2045 G_CALLBACK(gd_leave_event), vc);
Volker Rümelinbd593d22020-05-16 09:20:05 +02002046 g_signal_connect(vc->gfx.drawing_area, "focus-in-event",
2047 G_CALLBACK(gd_focus_in_event), vc);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02002048 g_signal_connect(vc->gfx.drawing_area, "focus-out-event",
2049 G_CALLBACK(gd_focus_out_event), vc);
Gerd Hoffmann1301e512015-03-13 12:47:00 +01002050 g_signal_connect(vc->gfx.drawing_area, "configure-event",
2051 G_CALLBACK(gd_configure), vc);
Volker Rümelin0c4b1a72020-05-16 09:20:06 +02002052 g_signal_connect(vc->gfx.drawing_area, "grab-broken-event",
2053 G_CALLBACK(gd_grab_broken_event), vc);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02002054 } else {
2055 g_signal_connect(vc->gfx.drawing_area, "key-press-event",
2056 G_CALLBACK(gd_text_key_down), vc);
2057 }
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002058}
2059
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002060static void gd_connect_signals(GtkDisplayState *s)
2061{
2062 g_signal_connect(s->show_tabs_item, "activate",
2063 G_CALLBACK(gd_menu_show_tabs), s);
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02002064 g_signal_connect(s->untabify_item, "activate",
2065 G_CALLBACK(gd_menu_untabify), s);
Peter Wu1d187742018-05-11 01:07:38 +02002066 g_signal_connect(s->show_menubar_item, "activate",
2067 G_CALLBACK(gd_menu_show_menubar), s);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002068
2069 g_signal_connect(s->window, "delete-event",
2070 G_CALLBACK(gd_window_close), s);
2071
Jan Kiszka30e8f222013-02-22 20:53:33 +01002072 g_signal_connect(s->pause_item, "activate",
2073 G_CALLBACK(gd_menu_pause), s);
2074 g_signal_connect(s->reset_item, "activate",
2075 G_CALLBACK(gd_menu_reset), s);
2076 g_signal_connect(s->powerdown_item, "activate",
2077 G_CALLBACK(gd_menu_powerdown), s);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002078 g_signal_connect(s->quit_item, "activate",
2079 G_CALLBACK(gd_menu_quit), s);
Michael S. Tsirkin44b31e02016-04-17 23:25:54 +03002080#if defined(CONFIG_VTE)
2081 g_signal_connect(s->copy_item, "activate",
2082 G_CALLBACK(gd_menu_copy), s);
2083#endif
Anthony Liguoric6158482013-02-20 07:43:23 -06002084 g_signal_connect(s->full_screen_item, "activate",
2085 G_CALLBACK(gd_menu_full_screen), s);
2086 g_signal_connect(s->zoom_in_item, "activate",
2087 G_CALLBACK(gd_menu_zoom_in), s);
2088 g_signal_connect(s->zoom_out_item, "activate",
2089 G_CALLBACK(gd_menu_zoom_out), s);
2090 g_signal_connect(s->zoom_fixed_item, "activate",
2091 G_CALLBACK(gd_menu_zoom_fixed), s);
2092 g_signal_connect(s->zoom_fit_item, "activate",
2093 G_CALLBACK(gd_menu_zoom_fit), s);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06002094 g_signal_connect(s->grab_item, "activate",
2095 G_CALLBACK(gd_menu_grab_input), s);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002096 g_signal_connect(s->notebook, "switch-page",
2097 G_CALLBACK(gd_change_page), s);
2098}
2099
Cole Robinson400519d2014-10-30 15:34:32 -04002100static GtkWidget *gd_create_menu_machine(GtkDisplayState *s)
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002101{
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002102 GtkWidget *machine_menu;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002103 GtkWidget *separator;
2104
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002105 machine_menu = gtk_menu_new();
Cole Robinson400519d2014-10-30 15:34:32 -04002106 gtk_menu_set_accel_group(GTK_MENU(machine_menu), s->accel_group);
Jan Kiszka30e8f222013-02-22 20:53:33 +01002107
2108 s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause"));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002109 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item);
Jan Kiszka30e8f222013-02-22 20:53:33 +01002110
2111 separator = gtk_separator_menu_item_new();
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002112 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
Jan Kiszka30e8f222013-02-22 20:53:33 +01002113
Cole Robinson9068f202014-03-17 16:06:24 -04002114 s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset"));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002115 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item);
Jan Kiszka30e8f222013-02-22 20:53:33 +01002116
Cole Robinson9068f202014-03-17 16:06:24 -04002117 s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down"));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002118 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item);
Jan Kiszka30e8f222013-02-22 20:53:33 +01002119
2120 separator = gtk_separator_menu_item_new();
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002121 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002122
Cole Robinson3d914482014-03-17 16:06:26 -04002123 s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit"));
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002124 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
Jan Kiszka30e8f222013-02-22 20:53:33 +01002125 "<QEMU>/Machine/Quit");
Cole Robinson3d914482014-03-17 16:06:26 -04002126 gtk_accel_map_add_entry("<QEMU>/Machine/Quit",
Cole Robinsondb1da1f2014-03-17 16:06:27 -04002127 GDK_KEY_q, HOTKEY_MODIFIERS);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002128 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002129
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002130 return machine_menu;
2131}
2132
Marc-André Lureau26065192021-02-04 14:52:28 +04002133#if defined(CONFIG_OPENGL)
2134static void gl_area_realize(GtkGLArea *area, VirtualConsole *vc)
2135{
2136 gtk_gl_area_make_current(area);
2137 qemu_egl_display = eglGetCurrentDisplay();
2138 vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
2139 if (!vc->gfx.has_dmabuf) {
2140 error_report("GtkGLArea console lacks DMABUF support.");
2141 }
2142}
2143#endif
2144
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002145static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02002146 QemuConsole *con, int idx,
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002147 GSList *group, GtkWidget *view_menu)
2148{
Paolo Bonzini1d454c32018-10-03 14:11:38 +02002149 bool zoom_to_fit = false;
Sergio Lopez5a4cb612023-05-26 13:29:25 +02002150 int i;
Nikola Pavlicac4c00922020-01-08 13:13:42 +01002151
Gerd Hoffmann779ce882015-02-17 10:41:08 +01002152 vc->label = qemu_console_get_label(con);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002153 vc->s = s;
2154 vc->gfx.scale_x = 1.0;
2155 vc->gfx.scale_y = 1.0;
2156
Gerd Hoffmann925a0402015-05-26 12:26:21 +02002157#if defined(CONFIG_OPENGL)
2158 if (display_opengl) {
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002159 if (gtk_use_gl_area) {
2160 vc->gfx.drawing_area = gtk_gl_area_new();
Marc-André Lureau26065192021-02-04 14:52:28 +04002161 g_signal_connect(vc->gfx.drawing_area, "realize",
2162 G_CALLBACK(gl_area_realize), vc);
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002163 vc->gfx.dcl.ops = &dcl_gl_area_ops;
Marc-André Lureau5e79d512021-10-09 23:48:46 +04002164 vc->gfx.dgc.ops = &gl_area_ctx_ops;
Marc-André Lureauf988e3c2021-02-04 14:52:17 +04002165 } else {
Akihiko Odakibc6a3562021-02-23 15:03:07 +09002166#ifdef CONFIG_X11
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002167 vc->gfx.drawing_area = gtk_drawing_area_new();
2168 /*
2169 * gtk_widget_set_double_buffered() was deprecated in 3.14.
2170 * It is required for opengl rendering on X11 though. A
2171 * proper replacement (native opengl support) is only
2172 * available in 3.16+. Silence the warning if possible.
2173 */
Gerd Hoffmann925a0402015-05-26 12:26:21 +02002174#pragma GCC diagnostic push
2175#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002176 gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
Gerd Hoffmann925a0402015-05-26 12:26:21 +02002177#pragma GCC diagnostic pop
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002178 vc->gfx.dcl.ops = &dcl_egl_ops;
Marc-André Lureau5e79d512021-10-09 23:48:46 +04002179 vc->gfx.dgc.ops = &egl_ctx_ops;
Marc-André Lureau52a37e22021-02-04 14:52:27 +04002180 vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
Akihiko Odakibc6a3562021-02-23 15:03:07 +09002181#else
2182 abort();
2183#endif
Gerd Hoffmann11c82b52018-03-06 10:09:46 +01002184 }
Gerd Hoffmann925a0402015-05-26 12:26:21 +02002185 } else
2186#endif
2187 {
2188 vc->gfx.drawing_area = gtk_drawing_area_new();
2189 vc->gfx.dcl.ops = &dcl_ops;
2190 }
2191
2192
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002193 gtk_widget_add_events(vc->gfx.drawing_area,
2194 GDK_POINTER_MOTION_MASK |
2195 GDK_BUTTON_PRESS_MASK |
2196 GDK_BUTTON_RELEASE_MASK |
2197 GDK_BUTTON_MOTION_MASK |
Sergio Lopez5a4cb612023-05-26 13:29:25 +02002198 GDK_TOUCH_MASK |
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002199 GDK_ENTER_NOTIFY_MASK |
2200 GDK_LEAVE_NOTIFY_MASK |
2201 GDK_SCROLL_MASK |
Sergio Lopezd89bf1d2019-02-04 13:08:23 +01002202 GDK_SMOOTH_SCROLL_MASK |
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002203 GDK_KEY_PRESS_MASK);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002204 gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);
2205
2206 vc->type = GD_VC_GFX;
2207 vc->tab_item = vc->gfx.drawing_area;
Jan Kiszka9d677e12015-04-26 21:04:20 +02002208 vc->focus = vc->gfx.drawing_area;
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002209 gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02002210 vc->tab_item, gtk_label_new(vc->label));
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002211
Gerd Hoffmann0c0d4272019-01-22 10:28:11 +01002212 vc->gfx.kbd = qkbd_state_init(con);
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002213 vc->gfx.dcl.con = con;
Nikola Pavlicac4c00922020-01-08 13:13:42 +01002214
Marc-André Lureauac32b2f2021-01-25 15:10:36 +04002215 if (display_opengl) {
Marc-André Lureau5e79d512021-10-09 23:48:46 +04002216 qemu_console_set_display_gl_ctx(con, &vc->gfx.dgc);
Marc-André Lureauac32b2f2021-01-25 15:10:36 +04002217 }
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002218 register_displaychangelistener(&vc->gfx.dcl);
2219
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02002220 gd_connect_vc_gfx_signals(vc);
2221 group = gd_vc_menu_init(s, vc, idx, group, view_menu);
2222
Gerd Hoffmann1301e512015-03-13 12:47:00 +01002223 if (dpy_ui_info_supported(vc->gfx.dcl.con)) {
Gerd Hoffmanne8b13862018-08-27 11:56:20 +02002224 zoom_to_fit = true;
2225 }
2226 if (s->opts->u.gtk.has_zoom_to_fit) {
2227 zoom_to_fit = s->opts->u.gtk.zoom_to_fit;
2228 }
2229 if (zoom_to_fit) {
Gerd Hoffmann1301e512015-03-13 12:47:00 +01002230 gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
Gerd Hoffmann1d73cd72015-09-09 10:34:41 +02002231 s->free_scale = true;
Gerd Hoffmann1301e512015-03-13 12:47:00 +01002232 }
2233
Sergio Lopez5a4cb612023-05-26 13:29:25 +02002234 for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
2235 struct touch_slot *slot = &touch_slots[i];
2236 slot->tracking_id = -1;
2237 }
2238
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002239 return group;
2240}
2241
Bryce Millsdbccb1a2022-10-11 13:58:21 +00002242static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002243{
2244 GSList *group = NULL;
2245 GtkWidget *view_menu;
2246 GtkWidget *separator;
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02002247 QemuConsole *con;
2248 int vc;
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002249
2250 view_menu = gtk_menu_new();
Cole Robinson400519d2014-10-30 15:34:32 -04002251 gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002252
Cole Robinson3d914482014-03-17 16:06:26 -04002253 s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
Cole Robinson95414912014-10-30 15:34:33 -04002254
Michael S. Tsirkin44b31e02016-04-17 23:25:54 +03002255#if defined(CONFIG_VTE)
2256 s->copy_item = gtk_menu_item_new_with_mnemonic(_("_Copy"));
2257 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->copy_item);
2258#endif
2259
Cole Robinson95414912014-10-30 15:34:33 -04002260 gtk_accel_group_connect(s->accel_group, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
2261 g_cclosure_new_swap(G_CALLBACK(gd_accel_full_screen), s, NULL));
Cole Robinson95414912014-10-30 15:34:33 -04002262 gtk_accel_label_set_accel(
2263 GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->full_screen_item))),
2264 GDK_KEY_f, HOTKEY_MODIFIERS);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002265 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item);
Anthony Liguoric6158482013-02-20 07:43:23 -06002266
2267 separator = gtk_separator_menu_item_new();
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002268 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
Anthony Liguoric6158482013-02-20 07:43:23 -06002269
Cole Robinson3d914482014-03-17 16:06:26 -04002270 s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In"));
Anthony Liguoric6158482013-02-20 07:43:23 -06002271 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
2272 "<QEMU>/View/Zoom In");
Jan Kiszkab1e749c2013-07-22 09:04:32 +02002273 gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus,
2274 HOTKEY_MODIFIERS);
Ziyue Yang66f6b822017-01-31 09:32:15 +08002275 gtk_accel_group_connect(s->accel_group, GDK_KEY_equal, HOTKEY_MODIFIERS, 0,
2276 g_cclosure_new_swap(G_CALLBACK(gd_accel_zoom_in), s, NULL));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002277 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item);
Anthony Liguoric6158482013-02-20 07:43:23 -06002278
Cole Robinson3d914482014-03-17 16:06:26 -04002279 s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out"));
Anthony Liguoric6158482013-02-20 07:43:23 -06002280 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
2281 "<QEMU>/View/Zoom Out");
Jan Kiszkab1e749c2013-07-22 09:04:32 +02002282 gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus,
2283 HOTKEY_MODIFIERS);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002284 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item);
Anthony Liguoric6158482013-02-20 07:43:23 -06002285
Cole Robinson3d914482014-03-17 16:06:26 -04002286 s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit"));
Anthony Liguoric6158482013-02-20 07:43:23 -06002287 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
2288 "<QEMU>/View/Zoom Fixed");
Jan Kiszkab1e749c2013-07-22 09:04:32 +02002289 gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0,
2290 HOTKEY_MODIFIERS);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002291 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item);
Anthony Liguoric6158482013-02-20 07:43:23 -06002292
Anthony Liguori834574e2013-02-20 07:43:24 -06002293 s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002294 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item);
Anthony Liguoric6158482013-02-20 07:43:23 -06002295
2296 separator = gtk_separator_menu_item_new();
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002297 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
Anthony Liguoric6158482013-02-20 07:43:23 -06002298
Anthony Liguori834574e2013-02-20 07:43:24 -06002299 s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover"));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002300 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_on_hover_item);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06002301
Anthony Liguori834574e2013-02-20 07:43:24 -06002302 s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
Anthony Liguori5104a1f2013-02-20 07:43:22 -06002303 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
2304 "<QEMU>/View/Grab Input");
Jan Kiszkab1e749c2013-07-22 09:04:32 +02002305 gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
2306 HOTKEY_MODIFIERS);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002307 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
Anthony Liguori5104a1f2013-02-20 07:43:22 -06002308
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002309 separator = gtk_separator_menu_item_new();
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002310 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002311
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002312 /* gfx */
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02002313 for (vc = 0;; vc++) {
2314 con = qemu_console_lookup_by_index(vc);
Gerd Hoffmannf8c223f2014-05-22 11:08:54 +02002315 if (!con) {
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02002316 break;
2317 }
2318 group = gd_vc_gfx_init(s, &s->vc[vc], con,
2319 vc, group, view_menu);
2320 s->nb_vcs++;
2321 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002322
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002323#if defined(CONFIG_VTE)
Gerd Hoffmanne3500d12014-04-30 16:30:07 +02002324 /* vte */
Gerd Hoffmanned1132e2014-04-30 16:39:18 +02002325 gd_vcs_init(s, group, view_menu);
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002326#endif
Anthony Liguorid861def2013-02-20 07:43:21 -06002327
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002328 separator = gtk_separator_menu_item_new();
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002329 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002330
Anthony Liguori834574e2013-02-20 07:43:24 -06002331 s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002332 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002333
Gerd Hoffmanncdeb7092014-05-05 16:23:33 +02002334 s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab"));
2335 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item);
2336
Peter Wu1d187742018-05-11 01:07:38 +02002337 s->show_menubar_item = gtk_check_menu_item_new_with_mnemonic(
2338 _("Show Menubar"));
2339 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->show_menubar_item),
Bryce Millsdbccb1a2022-10-11 13:58:21 +00002340 !opts->u.gtk.has_show_menubar ||
2341 opts->u.gtk.show_menubar);
Peter Wu1d187742018-05-11 01:07:38 +02002342 gtk_accel_group_connect(s->accel_group, GDK_KEY_m, HOTKEY_MODIFIERS, 0,
2343 g_cclosure_new_swap(G_CALLBACK(gd_accel_show_menubar), s, NULL));
Peter Wu1d187742018-05-11 01:07:38 +02002344 gtk_accel_label_set_accel(
2345 GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))),
2346 GDK_KEY_m, HOTKEY_MODIFIERS);
Peter Wu1d187742018-05-11 01:07:38 +02002347 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item);
2348
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002349 return view_menu;
2350}
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002351
Bryce Millsdbccb1a2022-10-11 13:58:21 +00002352static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002353{
Peter Wu677b4902018-05-11 01:07:39 +02002354 GtkSettings *settings;
2355
Cole Robinson400519d2014-10-30 15:34:32 -04002356 s->accel_group = gtk_accel_group_new();
2357 s->machine_menu = gd_create_menu_machine(s);
Bryce Millsdbccb1a2022-10-11 13:58:21 +00002358 s->view_menu = gd_create_menu_view(s, opts);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002359
2360 s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
Jan Kiszka30e8f222013-02-22 20:53:33 +01002361 gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
2362 s->machine_menu);
2363 gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->machine_menu_item);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002364
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002365 s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002366 gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
2367 gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
Anthony Liguoribf9b2552013-04-26 08:48:46 -05002368
Cole Robinson400519d2014-10-30 15:34:32 -04002369 g_object_set_data(G_OBJECT(s->window), "accel_group", s->accel_group);
2370 gtk_window_add_accel_group(GTK_WINDOW(s->window), s->accel_group);
Peter Wu677b4902018-05-11 01:07:39 +02002371
2372 /* Disable the default "F10" menu shortcut. */
2373 settings = gtk_widget_get_settings(s->window);
2374 g_object_set(G_OBJECT(settings), "gtk-menu-bar-accel", "", NULL);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002375}
2376
Bruce Rogers3158a342014-04-30 20:29:42 -06002377
Gerd Hoffmann060ab762015-06-05 13:07:58 +02002378static gboolean gtkinit;
2379
Gerd Hoffmanndb715892018-03-01 11:05:35 +01002380static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002381{
Hervé Poussineau3d4da9d2017-01-01 10:39:45 +01002382 VirtualConsole *vc;
2383
Dmitry Frolove38f4e92023-08-25 14:58:19 +03002384 GtkDisplayState *s;
Max Reitz63c67b62015-05-20 15:35:31 +02002385 GdkDisplay *window_display;
Daniel P. Berrangéa8260d32019-01-10 12:00:45 +00002386 GtkIconTheme *theme;
Paolo Bonzini77d910f2020-08-18 11:59:27 +02002387 char *dir;
Marc-André Lureau565f85a2023-10-17 15:16:42 +04002388 int idx;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002389
Gerd Hoffmann060ab762015-06-05 13:07:58 +02002390 if (!gtkinit) {
2391 fprintf(stderr, "gtk initialization failed\n");
2392 exit(1);
2393 }
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +01002394 assert(opts->type == DISPLAY_TYPE_GTK);
Dmitry Frolove38f4e92023-08-25 14:58:19 +03002395 s = g_malloc0(sizeof(*s));
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +01002396 s->opts = opts;
Gerd Hoffmann060ab762015-06-05 13:07:58 +02002397
Daniel P. Berrangéa8260d32019-01-10 12:00:45 +00002398 theme = gtk_icon_theme_get_default();
Paolo Bonzini77d910f2020-08-18 11:59:27 +02002399 dir = get_relocated_path(CONFIG_QEMU_ICONDIR);
2400 gtk_icon_theme_prepend_search_path(theme, dir);
2401 g_free(dir);
Daniel P. Berrangé67ea9542019-01-10 12:00:46 +00002402 g_set_prgname("qemu");
Daniel P. Berrangéa8260d32019-01-10 12:00:45 +00002403
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002404 s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
Daniel P. Berrange51572ab2013-02-25 15:20:38 +00002405 s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002406 s->notebook = gtk_notebook_new();
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002407 s->menu_bar = gtk_menu_bar_new();
2408
Anthony Liguoric6158482013-02-20 07:43:23 -06002409 s->free_scale = FALSE;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002410
Kevin Wolf27b224a2017-01-31 11:09:45 +01002411 /* Mostly LC_MESSAGES only. See early_gtk_display_init() for details. For
2412 * LC_CTYPE, we need to make sure that non-ASCII characters are considered
2413 * printable, but without changing any of the character classes to make
2414 * sure that we don't accidentally break implicit assumptions. */
Alberto Garcia2cb5d2a2015-09-10 18:19:32 +03002415 setlocale(LC_MESSAGES, "");
Kevin Wolf27b224a2017-01-31 11:09:45 +01002416 setlocale(LC_CTYPE, "C.UTF-8");
Paolo Bonzini77d910f2020-08-18 11:59:27 +02002417 dir = get_relocated_path(CONFIG_QEMU_LOCALEDIR);
2418 bindtextdomain("qemu", dir);
2419 g_free(dir);
yanminhuic55c9742019-11-16 11:10:37 +08002420 bind_textdomain_codeset("qemu", "UTF-8");
Anthony Liguori834574e2013-02-20 07:43:24 -06002421 textdomain("qemu");
2422
Max Reitz63c67b62015-05-20 15:35:31 +02002423 window_display = gtk_widget_get_display(s->window);
Gerd Hoffmann9cfca0b2020-01-31 11:54:48 +01002424 if (s->opts->has_show_cursor && s->opts->show_cursor) {
2425 s->null_cursor = NULL; /* default pointer */
2426 } else {
2427 s->null_cursor = gdk_cursor_new_for_display(window_display,
2428 GDK_BLANK_CURSOR);
2429 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002430
2431 s->mouse_mode_notifier.notify = gd_mouse_mode_change;
2432 qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
2433 qemu_add_vm_change_state_handler(gd_change_runstate, s);
2434
Daniel P. Berrangéa8260d32019-01-10 12:00:45 +00002435 gtk_window_set_icon_name(GTK_WINDOW(s->window), "qemu");
Stefan Weild819cdc2013-03-30 15:21:40 +01002436
Bryce Millsdbccb1a2022-10-11 13:58:21 +00002437 gd_create_menus(s, opts);
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002438
2439 gd_connect_signals(s);
2440
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002441 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
2442 gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
2443
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002444 gd_update_caption(s);
2445
2446 gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
2447 gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);
2448
2449 gtk_container_add(GTK_CONTAINER(s->window), s->vbox);
2450
2451 gtk_widget_show_all(s->window);
Marc-André Lureau565f85a2023-10-17 15:16:42 +04002452
2453 for (idx = 0;; idx++) {
2454 QemuConsole *con = qemu_console_lookup_by_index(idx);
2455 if (!con) {
2456 break;
2457 }
2458 gtk_widget_realize(s->vc[idx].gfx.drawing_area);
2459 }
2460
Bryce Millsdbccb1a2022-10-11 13:58:21 +00002461 if (opts->u.gtk.has_show_menubar &&
2462 !opts->u.gtk.show_menubar) {
2463 gtk_widget_hide(s->menu_bar);
2464 }
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002465
Hervé Poussineau3d4da9d2017-01-01 10:39:45 +01002466 vc = gd_vc_find_current(s);
2467 gtk_widget_set_sensitive(s->view_menu, vc != NULL);
Stefan Hajnoczia0815632016-12-14 14:25:18 +00002468#ifdef CONFIG_VTE
2469 gtk_widget_set_sensitive(s->copy_item,
Hervé Poussineau3d4da9d2017-01-01 10:39:45 +01002470 vc && vc->type == GD_VC_VTE);
Stefan Hajnoczia0815632016-12-14 14:25:18 +00002471#endif
2472
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +01002473 if (opts->has_full_screen &&
2474 opts->full_screen) {
Peter Wu787ba4f2013-06-10 20:04:43 +02002475 gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
2476 }
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +01002477 if (opts->u.gtk.has_grab_on_hover &&
2478 opts->u.gtk.grab_on_hover) {
Jan Kiszka881249c2014-03-12 08:33:50 +01002479 gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
2480 }
Felix xq Queißnerc34a9332022-07-12 15:37:53 +02002481 if (opts->u.gtk.has_show_tabs &&
2482 opts->u.gtk.show_tabs) {
2483 gtk_menu_item_activate(GTK_MENU_ITEM(s->show_tabs_item));
2484 }
Claudio Fontana29e0bff2022-11-21 14:55:38 +01002485#ifdef CONFIG_GTK_CLIPBOARD
Gerd Hoffmannd11ebe22021-05-19 07:39:40 +02002486 gd_clipboard_init(s);
Claudio Fontana29e0bff2022-11-21 14:55:38 +01002487#endif /* CONFIG_GTK_CLIPBOARD */
Phil Dennis-Jordanf5ab12c2024-10-24 12:27:59 +02002488
2489 /* GTK's event polling must happen on the main thread. */
2490 qemu_main = NULL;
Anthony Liguoria4ccabc2013-02-20 07:43:20 -06002491}
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002492
Gerd Hoffmanndb715892018-03-01 11:05:35 +01002493static void early_gtk_display_init(DisplayOptions *opts)
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002494{
Alberto Garcia2cb5d2a2015-09-10 18:19:32 +03002495 /* The QEMU code relies on the assumption that it's always run in
2496 * the C locale. Therefore it is not prepared to deal with
2497 * operations that produce different results depending on the
2498 * locale, such as printf's formatting of decimal numbers, and
2499 * possibly others.
2500 *
2501 * Since GTK+ calls setlocale() by default -importing the locale
2502 * settings from the environment- we must prevent it from doing so
2503 * using gtk_disable_setlocale().
2504 *
2505 * QEMU's GTK+ UI, however, _does_ have translations for some of
2506 * the menu items. As a trade-off between a functionally correct
2507 * QEMU and a fully internationalized UI we support importing
2508 * LC_MESSAGES from the environment (see the setlocale() call
2509 * earlier in this file). This allows us to display translated
2510 * messages leaving everything else untouched.
2511 */
2512 gtk_disable_setlocale();
Gerd Hoffmann060ab762015-06-05 13:07:58 +02002513 gtkinit = gtk_init_check(NULL, NULL);
2514 if (!gtkinit) {
2515 /* don't exit yet, that'll break -help */
2516 return;
2517 }
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +01002518
Gerd Hoffmann0c8d7062018-02-02 12:10:14 +01002519 assert(opts->type == DISPLAY_TYPE_GTK);
Markus Armbruster154fd4d2024-09-04 13:18:25 +02002520 if (opts->has_gl && opts->gl != DISPLAY_GL_MODE_OFF) {
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +01002521#if defined(CONFIG_OPENGL)
Paolo Bonzini5cb69562021-01-07 13:46:32 +01002522#if defined(GDK_WINDOWING_WAYLAND)
Gerd Hoffmann4c702802018-03-06 10:09:49 +01002523 if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
2524 gtk_use_gl_area = true;
2525 gtk_gl_area_init();
Tomeu Vizoso4f4cb822018-05-07 15:42:37 +02002526 } else
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +01002527#endif
Marc-André Lureau57430aa2023-05-15 17:25:27 +04002528#if defined(GDK_WINDOWING_WIN32)
2529 if (GDK_IS_WIN32_DISPLAY(gdk_display_get_default())) {
2530 gtk_use_gl_area = true;
2531 gtk_gl_area_init();
2532 } else
2533#endif
Gerd Hoffmann4c702802018-03-06 10:09:49 +01002534 {
Akihiko Odakibc6a3562021-02-23 15:03:07 +09002535#ifdef CONFIG_X11
Markus Armbruster154fd4d2024-09-04 13:18:25 +02002536 DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAY_GL_MODE_ON;
Gerd Hoffmann54d208f2018-06-18 13:21:41 +02002537 gtk_egl_init(mode);
Akihiko Odakibc6a3562021-02-23 15:03:07 +09002538#endif
Gerd Hoffmann4c702802018-03-06 10:09:49 +01002539 }
Gerd Hoffmann925a0402015-05-26 12:26:21 +02002540#endif
Gerd Hoffmann97edf3b2015-01-20 12:43:28 +01002541 }
2542
Daniel P. Berrange2ec78702018-01-17 16:47:15 +00002543 keycode_map = gd_get_keymap(&keycode_maplen);
2544
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002545#if defined(CONFIG_VTE)
Zhao Liue5d60d92024-10-29 16:59:32 +08002546 type_register_static(&char_gd_vc_type_info);
Gerd Hoffmannee5f31e2014-04-29 15:39:37 +02002547#endif
2548}
Gerd Hoffmanndb715892018-03-01 11:05:35 +01002549
2550static QemuDisplay qemu_display_gtk = {
2551 .type = DISPLAY_TYPE_GTK,
2552 .early_init = early_gtk_display_init,
2553 .init = gtk_display_init,
Marc-André Lureaub7f1bb32023-11-17 14:55:07 +04002554 .vc = "vc",
Gerd Hoffmanndb715892018-03-01 11:05:35 +01002555};
2556
2557static void register_gtk(void)
2558{
2559 qemu_display_register(&qemu_display_gtk);
2560}
2561
2562type_init(register_gtk);
Gerd Hoffmannb36ae1c2021-06-24 12:38:13 +02002563
2564#ifdef CONFIG_OPENGL
2565module_dep("ui-opengl");
2566#endif