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
32CStdGtkWindow::~CStdGtkWindow()
33{
34 Clear();
35}
36
37bool 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
97void 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
120void 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
133GdkFilterReturn 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
140gboolean 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
159GtkWidget *CStdGtkWindow::InitGUI()
160{
161 return window;
162}
163