1 /*
2 Copyright 2011-2016 David Robillard <http://drobilla.net>
3
4 Permission to use, copy, modify, and/or distribute this software for any
5 purpose with or without fee is hereby granted, provided that the above
6 copyright notice and this permission notice appear in all copies.
7
8 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <string.h>
18
19 #include <gdk/gdkx.h>
20 #include <gtk/gtk.h>
21 #include <gtk/gtkx.h>
22
23 #include "./suil_internal.h"
24
25 #include "lv2/options/options.h"
26 #include "lv2/urid/urid.h"
27
28 #define SUIL_TYPE_X11_WRAPPER (suil_x11_wrapper_get_type())
29 #define SUIL_X11_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_X11_WRAPPER, SuilX11Wrapper))
30
31 typedef struct _SuilX11Wrapper SuilX11Wrapper;
32 typedef struct _SuilX11WrapperClass SuilX11WrapperClass;
33
34 struct _SuilX11Wrapper {
35 GtkSocket socket;
36 GtkPlug* plug;
37 SuilWrapper* wrapper;
38 SuilInstance* instance;
39 const LV2UI_Idle_Interface* idle_iface;
40 guint idle_id;
41 guint idle_ms;
42 int req_width;
43 int req_height;
44 };
45
46 struct _SuilX11WrapperClass {
47 GtkSocketClass parent_class;
48 };
49
50 GType suil_x11_wrapper_get_type(void); // Accessor for SUIL_TYPE_X11_WRAPPER
51
G_DEFINE_TYPE(SuilX11Wrapper,suil_x11_wrapper,GTK_TYPE_SOCKET)52 G_DEFINE_TYPE(SuilX11Wrapper, suil_x11_wrapper, GTK_TYPE_SOCKET)
53
54 /**
55 Check if 'swallowed' subwindow is known to the X server.
56
57 Gdk/GTK can mark the window as realized, mapped and visible even though
58 there is no window-ID on the X server for it yet. Then,
59 suil_x11_on_size_allocate() will cause a "BadWinow" X error.
60 */
61 static bool
62 x_window_is_valid(SuilX11Wrapper* socket)
63 {
64 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
65 Window root = 0;
66 Window parent = 0;
67 Window* children = NULL;
68 unsigned childcount = 0;
69
70 XQueryTree(GDK_WINDOW_XDISPLAY(window),
71 GDK_WINDOW_XID(window),
72 &root, &parent, &children, &childcount);
73 for (unsigned i = 0; i < childcount; ++i) {
74 if (children[i] == (Window)socket->instance->ui_widget) {
75 XFree(children);
76 return true;
77 }
78 }
79 XFree(children);
80 return false;
81 }
82
83 static gboolean
on_plug_removed(GtkSocket * sock,gpointer data)84 on_plug_removed(GtkSocket* sock, gpointer data)
85 {
86 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(sock);
87
88 if (self->idle_id) {
89 g_source_remove(self->idle_id);
90 self->idle_id = 0;
91 }
92
93 if (self->instance->handle) {
94 self->instance->descriptor->cleanup(self->instance->handle);
95 self->instance->handle = NULL;
96 }
97
98 self->plug = NULL;
99 return TRUE;
100 }
101
102 static void
suil_x11_wrapper_finalize(GObject * gobject)103 suil_x11_wrapper_finalize(GObject* gobject)
104 {
105 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(gobject);
106
107 self->wrapper->impl = NULL;
108
109 G_OBJECT_CLASS(suil_x11_wrapper_parent_class)->finalize(gobject);
110 }
111
112 static void
suil_x11_wrapper_realize(GtkWidget * w)113 suil_x11_wrapper_realize(GtkWidget* w)
114 {
115 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w);
116 GtkSocket* const socket = GTK_SOCKET(w);
117
118 if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize) {
119 GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize(w);
120 }
121
122 gtk_socket_add_id(socket, gtk_plug_get_id(wrap->plug));
123
124 gtk_widget_realize(GTK_WIDGET(wrap->plug));
125
126 gtk_widget_set_sensitive(GTK_WIDGET(wrap->plug), TRUE);
127 gtk_widget_set_can_focus(GTK_WIDGET(wrap->plug), TRUE);
128 gtk_widget_grab_focus(GTK_WIDGET(wrap->plug));
129 }
130
131 static void
suil_x11_wrapper_show(GtkWidget * w)132 suil_x11_wrapper_show(GtkWidget* w)
133 {
134 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w);
135
136 if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show) {
137 GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show(w);
138 }
139
140 gtk_widget_show(GTK_WIDGET(wrap->plug));
141 }
142
143 static gboolean
forward_key_event(SuilX11Wrapper * socket,GdkEvent * gdk_event)144 forward_key_event(SuilX11Wrapper* socket,
145 GdkEvent* gdk_event)
146 {
147 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
148 GdkScreen* screen = gdk_visual_get_screen(gdk_window_get_visual(window));
149
150 Window target_window;
151 if (gdk_event->any.window == window) {
152 // Event sent up to the plug window, forward it up to the parent
153 GtkWidget* widget = GTK_WIDGET(socket->instance->host_widget);
154 GdkWindow* parent = gtk_widget_get_parent_window(widget);
155 if (parent) {
156 target_window = GDK_WINDOW_XID(parent);
157 } else {
158 return FALSE; // Wrapper is a top-level window, do nothing
159 }
160 } else {
161 // Event sent anywhere else, send to the plugin
162 target_window = (Window)socket->instance->ui_widget;
163 }
164
165 XKeyEvent xev;
166 memset(&xev, 0, sizeof(xev));
167 xev.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
168 xev.root = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
169 xev.window = target_window;
170 xev.subwindow = None;
171 xev.time = gdk_event->key.time;
172 xev.state = gdk_event->key.state;
173 xev.keycode = gdk_event->key.hardware_keycode;
174
175 XSendEvent(GDK_WINDOW_XDISPLAY(window),
176 target_window,
177 False,
178 NoEventMask,
179 (XEvent*)&xev);
180
181 return (gdk_event->any.window != window);
182 }
183
184 static gboolean
idle_size_request(gpointer user_data)185 idle_size_request(gpointer user_data)
186 {
187 GtkWidget* w = GTK_WIDGET(user_data);
188 gtk_widget_queue_resize(w);
189 return FALSE;
190 }
191
192 static void
forward_size_request(SuilX11Wrapper * socket,GtkAllocation * allocation)193 forward_size_request(SuilX11Wrapper* socket,
194 GtkAllocation* allocation)
195 {
196 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
197 if (x_window_is_valid(socket)) {
198 // Calculate allocation size constrained to X11 limits for widget
199 int width = allocation->width;
200 int height = allocation->height;
201 XSizeHints hints;
202 memset(&hints, 0, sizeof(hints));
203 XGetNormalHints(GDK_WINDOW_XDISPLAY(window),
204 (Window)socket->instance->ui_widget,
205 &hints);
206 if (hints.flags & PMaxSize) {
207 width = MIN(width, hints.max_width);
208 height = MIN(height, hints.max_height);
209 }
210 if (hints.flags & PMinSize) {
211 width = MAX(width, hints.min_width);
212 height = MAX(height, hints.min_height);
213 }
214
215 // Resize widget window
216 XResizeWindow(GDK_WINDOW_XDISPLAY(window),
217 (Window)socket->instance->ui_widget,
218 width, height);
219
220 // Get actual widget geometry
221 Window root;
222 int wx, wy;
223 unsigned int ww, wh;
224 unsigned int ignored;
225 XGetGeometry(GDK_WINDOW_XDISPLAY(window),
226 (Window)socket->instance->ui_widget,
227 &root,
228 &wx, &wy, &ww, &wh,
229 &ignored, &ignored);
230
231 // Center widget in allocation
232 wx = (allocation->width - ww) / 2;
233 wy = (allocation->height - wh) / 2;
234 XMoveWindow(GDK_WINDOW_XDISPLAY(window),
235 (Window)socket->instance->ui_widget,
236 wx, wy);
237 } else {
238 /* Child has not been realized, so unable to resize now.
239 Queue an idle resize. */
240 g_idle_add(idle_size_request, socket->plug);
241 }
242 }
243
244 static gboolean
suil_x11_wrapper_key_event(GtkWidget * widget,GdkEventKey * event)245 suil_x11_wrapper_key_event(GtkWidget* widget,
246 GdkEventKey* event)
247 {
248 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
249
250 if (self->plug) {
251 return forward_key_event(self, (GdkEvent*)event);
252 }
253
254 return FALSE;
255 }
256
257 static void
suil_x11_wrapper_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)258 suil_x11_wrapper_get_preferred_width(GtkWidget* widget,
259 gint* minimum_width,
260 gint* natural_width)
261 {
262
263 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
264 if (x_window_is_valid(self)) {
265 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(self->plug));
266 XSizeHints hints;
267 memset(&hints, 0, sizeof(hints));
268 long supplied;
269 XGetWMNormalHints(GDK_WINDOW_XDISPLAY(window),
270 (Window)self->instance->ui_widget,
271 &hints,
272 &supplied);
273 *natural_width = ((hints.flags & PBaseSize) ? hints.base_width
274 : self->req_width);
275 *minimum_width = ((hints.flags & PMinSize) ? hints.min_width
276 : self->req_width);
277 } else {
278 *natural_width = *minimum_width = self->req_width;
279 }
280 }
281
282 static void
suil_x11_wrapper_get_preferred_height(GtkWidget * widget,gint * minimum_height,gint * natural_height)283 suil_x11_wrapper_get_preferred_height(GtkWidget* widget,
284 gint* minimum_height,
285 gint* natural_height)
286 {
287 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
288 if (x_window_is_valid(self)) {
289 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(self->plug));
290 XSizeHints hints;
291 memset(&hints, 0, sizeof(hints));
292 long supplied;
293 XGetWMNormalHints(GDK_WINDOW_XDISPLAY(window),
294 (Window)self->instance->ui_widget,
295 &hints,
296 &supplied);
297 *natural_height = ((hints.flags & PBaseSize) ? hints.base_height
298 : self->req_height);
299 *minimum_height = ((hints.flags & PMinSize) ? hints.min_height
300 : self->req_height);
301 } else {
302 *natural_height = *minimum_height = self->req_height;
303 }
304 }
305
306 static void
suil_x11_on_size_allocate(GtkWidget * widget,GtkAllocation * a)307 suil_x11_on_size_allocate(GtkWidget* widget,
308 GtkAllocation* a)
309 {
310 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
311
312 if (self->plug
313 && gtk_widget_get_realized(widget)
314 && gtk_widget_get_mapped(widget)
315 && gtk_widget_get_visible(widget)) {
316 forward_size_request(self, a);
317 }
318 }
319
320 static void
suil_x11_wrapper_class_init(SuilX11WrapperClass * klass)321 suil_x11_wrapper_class_init(SuilX11WrapperClass* klass)
322 {
323 GObjectClass* const gobject_class = G_OBJECT_CLASS(klass);
324 GtkWidgetClass* const widget_class = GTK_WIDGET_CLASS(klass);
325
326 gobject_class->finalize = suil_x11_wrapper_finalize;
327 widget_class->realize = suil_x11_wrapper_realize;
328 widget_class->show = suil_x11_wrapper_show;
329 widget_class->key_press_event = suil_x11_wrapper_key_event;
330 widget_class->key_release_event = suil_x11_wrapper_key_event;
331 widget_class->get_preferred_width = suil_x11_wrapper_get_preferred_width;
332 widget_class->get_preferred_height = suil_x11_wrapper_get_preferred_height;
333 }
334
335 static void
suil_x11_wrapper_init(SuilX11Wrapper * self)336 suil_x11_wrapper_init(SuilX11Wrapper* self)
337 {
338 self->plug = GTK_PLUG(gtk_plug_new(0));
339 self->wrapper = NULL;
340 self->instance = NULL;
341 self->idle_iface = NULL;
342 self->idle_ms = 1000 / 30; // 30 Hz default
343 self->req_width = 0;
344 self->req_height = 0;
345 }
346
347 static int
wrapper_resize(LV2UI_Feature_Handle handle,int width,int height)348 wrapper_resize(LV2UI_Feature_Handle handle, int width, int height)
349 {
350 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(handle);
351
352 wrap->req_width = width;
353 wrap->req_height = height;
354
355 gtk_widget_queue_resize(GTK_WIDGET(handle));
356 return 0;
357 }
358
359 static gboolean
suil_x11_wrapper_idle(void * data)360 suil_x11_wrapper_idle(void* data)
361 {
362 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(data);
363
364 wrap->idle_iface->idle(wrap->instance->handle);
365
366 return TRUE; // Continue calling
367 }
368
369 static int
wrapper_wrap(SuilWrapper * wrapper,SuilInstance * instance)370 wrapper_wrap(SuilWrapper* wrapper,
371 SuilInstance* instance)
372 {
373 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl);
374
375 instance->host_widget = GTK_WIDGET(wrap);
376 wrap->wrapper = wrapper;
377 wrap->instance = instance;
378
379 const LV2UI_Idle_Interface* idle_iface = NULL;
380 if (instance->descriptor->extension_data) {
381 idle_iface = (const LV2UI_Idle_Interface*)
382 instance->descriptor->extension_data(LV2_UI__idleInterface);
383 }
384 if (idle_iface) {
385 wrap->idle_iface = idle_iface;
386 wrap->idle_id = g_timeout_add(
387 wrap->idle_ms, suil_x11_wrapper_idle, wrap);
388 }
389
390 g_signal_connect(G_OBJECT(wrap),
391 "plug-removed",
392 G_CALLBACK(on_plug_removed),
393 NULL);
394
395 g_signal_connect(G_OBJECT(wrap),
396 "size-allocate",
397 G_CALLBACK(suil_x11_on_size_allocate),
398 NULL);
399
400 return 0;
401 }
402
403 static void
wrapper_free(SuilWrapper * wrapper)404 wrapper_free(SuilWrapper* wrapper)
405 {
406 if (wrapper->impl) {
407 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl);
408 gtk_widget_destroy(GTK_WIDGET(wrap));
409 }
410 }
411
412 SUIL_LIB_EXPORT
413 SuilWrapper*
suil_wrapper_new(SuilHost * host,const char * host_type_uri,const char * ui_type_uri,LV2_Feature *** features,unsigned n_features)414 suil_wrapper_new(SuilHost* host,
415 const char* host_type_uri,
416 const char* ui_type_uri,
417 LV2_Feature*** features,
418 unsigned n_features)
419 {
420 SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper));
421 wrapper->wrap = wrapper_wrap;
422 wrapper->free = wrapper_free;
423
424 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(
425 g_object_new(SUIL_TYPE_X11_WRAPPER, NULL));
426
427 wrapper->impl = wrap;
428 wrapper->resize.handle = wrap;
429 wrapper->resize.ui_resize = wrapper_resize;
430
431 gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE);
432 gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE);
433
434 const intptr_t parent_id = (intptr_t)gtk_plug_get_id(wrap->plug);
435 suil_add_feature(features, &n_features, LV2_UI__parent, (void*)parent_id);
436 suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize);
437 suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL);
438
439 // Scan for URID map and options
440 LV2_URID_Map* map = NULL;
441 LV2_Options_Option* options = NULL;
442 for (LV2_Feature** f = *features; *f && (!map || !options); ++f) {
443 if (!strcmp((*f)->URI, LV2_OPTIONS__options)) {
444 options = (LV2_Options_Option*)(*f)->data;
445 } else if (!strcmp((*f)->URI, LV2_URID__map)) {
446 map = (LV2_URID_Map*)(*f)->data;
447 }
448 }
449
450 if (map && options) {
451 // Set UI update rate if given
452 LV2_URID ui_updateRate = map->map(map->handle, LV2_UI__updateRate);
453 for (LV2_Options_Option* o = options; o->key; ++o) {
454 if (o->key == ui_updateRate) {
455 wrap->idle_ms = 1000.0f / *(const float*)o->value;
456 break;
457 }
458 }
459 }
460
461 return wrapper;
462 }
463