| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2006, Clonk-Karl |
| 6 | * Copyright (c) 2017-2020, The LegacyClonk Team and contributors |
| 7 | * |
| 8 | * Distributed under the terms of the ISC license; see accompanying file |
| 9 | * "COPYING" for details. |
| 10 | * |
| 11 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 12 | * See accompanying file "TRADEMARK" for details. |
| 13 | * |
| 14 | * To redistribute this file separately, substitute the full license texts |
| 15 | * for the above references. |
| 16 | */ |
| 17 | |
| 18 | /* A wrapper class to OS dependent event and window interfaces, GTK+ version */ |
| 19 | |
| 20 | #include <StdGtkWindow.h> |
| 21 | #include <StdApp.h> |
| 22 | |
| 23 | #include "res/lc.xpm" |
| 24 | #include <X11/Xlib.h> |
| 25 | #include <gdk/gdk.h> |
| 26 | #include <gdk/gdkx.h> |
| 27 | #include <gdk/gdkkeysyms.h> |
| 28 | #include <gtk/gtk.h> |
| 29 | |
| 30 | /* CStdGtkWindow */ |
| 31 | |
| 32 | CStdGtkWindow::~CStdGtkWindow() |
| 33 | { |
| 34 | Clear(); |
| 35 | } |
| 36 | |
| 37 | bool CStdGtkWindow::Init(CStdApp *const app, const char *const title, const C4Rect &bounds, CStdWindow *const parent) |
| 38 | { |
| 39 | dpy = app->dpy; |
| 40 | |
| 41 | if (!FindInfo()) return false; |
| 42 | |
| 43 | assert(!window); |
| 44 | |
| 45 | window = gtk_window_new(type: GTK_WINDOW_TOPLEVEL); |
| 46 | |
| 47 | // Override gtk's default to match name/class of the XLib windows |
| 48 | gtk_window_set_wmclass(GTK_WINDOW(window), STD_PRODUCT, STD_PRODUCT); |
| 49 | |
| 50 | handlerDestroy = g_signal_connect(G_OBJECT(window), "destroy" , G_CALLBACK(OnDestroyStatic), this); |
| 51 | g_signal_connect(G_OBJECT(window), "key-press-event" , G_CALLBACK(OnUpdateKeyMask), app); |
| 52 | g_signal_connect(G_OBJECT(window), "key-release-event" , G_CALLBACK(OnUpdateKeyMask), app); |
| 53 | |
| 54 | GtkWidget *render_widget = InitGUI(); |
| 55 | |
| 56 | gtk_widget_set_visual(widget: render_widget, visual: gdk_x11_screen_lookup_visual(screen: gtk_window_get_screen(GTK_WINDOW(window)), xvisualid: static_cast<XVisualInfo *>(Info)->visualid)); |
| 57 | |
| 58 | gtk_widget_show_all(widget: window); |
| 59 | |
| 60 | GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data(data: c4x_xpm); |
| 61 | gtk_window_set_icon(GTK_WINDOW(window), icon); |
| 62 | g_object_unref(object: icon); |
| 63 | |
| 64 | gtk_window_set_title(GTK_WINDOW(window), title); |
| 65 | |
| 66 | auto *gdkWindow = gtk_widget_get_window(widget: window); |
| 67 | |
| 68 | // Wait until window is mapped to get the window's XID |
| 69 | gtk_widget_show_now(widget: window); |
| 70 | wnd = gdk_x11_window_get_xid(window: gdkWindow); |
| 71 | gdk_window_add_filter(window: gdkWindow, function: OnFilter, data: this); |
| 72 | |
| 73 | XWMHints *wm_hint = XGetWMHints(dpy, wnd); |
| 74 | if (!wm_hint) wm_hint = XAllocWMHints(); |
| 75 | Hints = wm_hint; |
| 76 | |
| 77 | if (GTK_IS_LAYOUT(render_widget)) |
| 78 | renderwnd = gdk_x11_window_get_xid(window: gtk_layout_get_bin_window(GTK_LAYOUT(render_widget))); |
| 79 | else |
| 80 | renderwnd = gdk_x11_window_get_xid(window: gtk_widget_get_window(widget: render_widget)); |
| 81 | |
| 82 | if (parent) XSetTransientForHint(dpy, wnd, parent->wnd); |
| 83 | |
| 84 | if (HideCursor()) |
| 85 | { |
| 86 | gdk_window_set_cursor(window: gdkWindow, cursor: nullptr); |
| 87 | } |
| 88 | |
| 89 | // Make sure the window is shown and ready to be rendered into, |
| 90 | // this avoids an async X error. |
| 91 | gdk_display_flush(display: gdk_display_get_default()); |
| 92 | |
| 93 | Active = true; |
| 94 | return true; |
| 95 | } |
| 96 | |
| 97 | void CStdGtkWindow::Clear() |
| 98 | { |
| 99 | if (window != nullptr) |
| 100 | { |
| 101 | g_signal_handler_disconnect(instance: window, handler_id: handlerDestroy); |
| 102 | gtk_widget_destroy(widget: window); |
| 103 | handlerDestroy = 0; |
| 104 | } |
| 105 | |
| 106 | // Avoid that the base class tries to free these |
| 107 | wnd = renderwnd = 0; |
| 108 | |
| 109 | window = nullptr; |
| 110 | Active = false; |
| 111 | |
| 112 | // We must free it here since we do not call CStdWindow::Clear() |
| 113 | if (Info) |
| 114 | { |
| 115 | XFree(Info); |
| 116 | Info = 0; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | void CStdGtkWindow::OnDestroyStatic(GtkWidget *widget, gpointer data) |
| 121 | { |
| 122 | CStdGtkWindow *wnd = static_cast<CStdGtkWindow *>(data); |
| 123 | |
| 124 | g_signal_handler_disconnect(instance: wnd->window, handler_id: wnd->handlerDestroy); |
| 125 | wnd->handlerDestroy = 0; |
| 126 | wnd->window = nullptr; |
| 127 | wnd->Active = false; |
| 128 | wnd->wnd = wnd->renderwnd = 0; |
| 129 | |
| 130 | wnd->Close(); |
| 131 | } |
| 132 | |
| 133 | GdkFilterReturn CStdGtkWindow::OnFilter(GdkXEvent *xevent, GdkEvent *event, gpointer user_data) |
| 134 | { |
| 135 | // Handle raw X message, then let GTK+ process it |
| 136 | static_cast<CStdGtkWindow *>(user_data)->HandleMessage(*reinterpret_cast<XEvent *>(xevent)); |
| 137 | return GDK_FILTER_CONTINUE; |
| 138 | } |
| 139 | |
| 140 | gboolean CStdGtkWindow::OnUpdateKeyMask(GtkWidget *widget, GdkEventKey *event, gpointer user_data) |
| 141 | { |
| 142 | // Update mask so that Application.IsShiftDown, |
| 143 | // Application.IsControlDown etc. work. |
| 144 | unsigned int mask = 0; |
| 145 | if (event->state & GDK_SHIFT_MASK) mask |= MK_SHIFT; |
| 146 | if (event->state & GDK_CONTROL_MASK) mask |= MK_CONTROL; |
| 147 | if (event->state & GDK_MOD1_MASK) mask |= (1 << 3); |
| 148 | |
| 149 | // For keypress/relases, event->state contains the state _before_ |
| 150 | // the event, but we need to store the current state. |
| 151 | if (event->keyval == GDK_KEY_Shift_L || event->keyval == GDK_KEY_Shift_R) mask ^= MK_SHIFT; |
| 152 | if (event->keyval == GDK_KEY_Control_L || event->keyval == GDK_KEY_Control_R) mask ^= MK_CONTROL; |
| 153 | if (event->keyval == GDK_KEY_Alt_L || event->keyval == GDK_KEY_Alt_R) mask ^= (1 << 3); |
| 154 | |
| 155 | static_cast<CStdApp *>(user_data)->KeyMask = mask; |
| 156 | return FALSE; |
| 157 | } |
| 158 | |
| 159 | GtkWidget *CStdGtkWindow::InitGUI() |
| 160 | { |
| 161 | return window; |
| 162 | } |
| 163 | |