1 /*
2 Copyright 2011-2020 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 "zrythm-config.h"
18
19 #ifndef HAVE_SUIL
20
21 #ifdef HAVE_X11
22
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include <X11/Xatom.h>
29
30 #include "suil/suil.h"
31
32 #include <string.h>
33
34 #include <gdk/gdkx.h>
35 #include <gtk/gtk.h>
36 #include <gtk/gtkx.h>
37
38 #include "lv2/options/options.h"
39 #include "lv2/urid/urid.h"
40
41 #define SUIL_X11_WRAPPER_TYPE (suil_x11_wrapper_get_type())
42 #define Z_SUIL_X11_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_X11_WRAPPER_TYPE, SuilX11Wrapper))
43
44 typedef struct {
45 bool is_set;
46 int width;
47 int height;
48 } SuilX11SizeHints;
49
50 typedef struct {
51 GtkSocket socket;
52 GtkPlug* plug;
53 SuilWrapper* wrapper;
54 SuilInstance* instance;
55 const LV2UI_Idle_Interface* idle_iface;
56 guint idle_id;
57 guint idle_ms;
58 guint idle_size_request_id;
59 SuilX11SizeHints max_size;
60 SuilX11SizeHints custom_size;
61 SuilX11SizeHints base_size;
62 SuilX11SizeHints min_size;
63 bool query_wm;
64 } SuilX11Wrapper;
65
66 typedef struct {
67 GtkSocketClass parent_class;
68 } SuilX11WrapperClass;
69
70 GType
71 suil_x11_wrapper_get_type(void); // Accessor for SUIL_TYPE_X11_WRAPPER
72
73 #define SUIL_TYPE_X11_WRAPPER (suil_x11_wrapper_get_type())
74 #define SUIL_X11_WRAPPER(obj) \
75 (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_X11_WRAPPER, SuilX11Wrapper))
76
G_DEFINE_TYPE(SuilX11Wrapper,suil_x11_wrapper,GTK_TYPE_SOCKET)77 G_DEFINE_TYPE(SuilX11Wrapper, suil_x11_wrapper, GTK_TYPE_SOCKET)
78
79 /**
80 Check if 'swallowed' subwindow is known to the X server.
81
82 Gdk/GTK can mark the window as realized, mapped and visible even though
83 there is no window-ID on the X server for it yet. Then,
84 suil_x11_on_size_allocate() will cause a "BadWinow" X error.
85 */
86 static bool
87 x_window_is_valid(SuilX11Wrapper* socket)
88 {
89 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
90 Window root = 0;
91 Window parent = 0;
92 Window* children = NULL;
93 unsigned childcount = 0;
94
95 XQueryTree(GDK_WINDOW_XDISPLAY(window),
96 GDK_WINDOW_XID(window),
97 &root,
98 &parent,
99 &children,
100 &childcount);
101 for (unsigned i = 0; i < childcount; ++i) {
102 if (children[i] == (Window)socket->instance->ui_widget) {
103 XFree(children);
104 return true;
105 }
106 }
107
108 XFree(children);
109 return false;
110 }
111
112 static Window
get_parent_window(Display * display,Window child)113 get_parent_window(Display* display, Window child)
114 {
115 Window root = 0;
116 Window parent = 0;
117 Window* children = NULL;
118 unsigned count = 0;
119
120 if (child) {
121 if (XQueryTree(display, child, &root, &parent, &children, &count)) {
122 if (children) {
123 XFree(children);
124 }
125 }
126 }
127
128 return (parent == root) ? 0 : parent;
129 }
130
131 static gboolean
on_plug_removed(GtkSocket * sock,gpointer data)132 on_plug_removed(GtkSocket* sock, gpointer data)
133 {
134 (void)data;
135
136 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(sock);
137
138 if (self->idle_id) {
139 g_source_remove(self->idle_id);
140 self->idle_id = 0;
141 }
142
143 if (self->idle_size_request_id) {
144 g_source_remove(self->idle_size_request_id);
145 self->idle_size_request_id = 0;
146 }
147
148 if (self->instance->handle) {
149 self->instance->descriptor->cleanup(self->instance->handle);
150 self->instance->handle = NULL;
151 }
152
153 self->plug = NULL;
154 return TRUE;
155 }
156
157 static void
suil_x11_wrapper_finalize(GObject * gobject)158 suil_x11_wrapper_finalize(GObject* gobject)
159 {
160 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(gobject);
161
162 self->wrapper->impl = NULL;
163
164 G_OBJECT_CLASS(suil_x11_wrapper_parent_class)->finalize(gobject);
165 }
166
167 static void
suil_x11_wrapper_realize(GtkWidget * w)168 suil_x11_wrapper_realize(GtkWidget* w)
169 {
170 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w);
171 GtkSocket* const socket = GTK_SOCKET(w);
172
173 if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize) {
174 GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize(w);
175 }
176
177 gtk_socket_add_id(socket, gtk_plug_get_id(wrap->plug));
178
179 gtk_widget_realize(GTK_WIDGET(wrap->plug));
180
181 gtk_widget_set_sensitive(GTK_WIDGET(wrap->plug), TRUE);
182 gtk_widget_set_can_focus(GTK_WIDGET(wrap->plug), TRUE);
183 gtk_widget_grab_focus(GTK_WIDGET(wrap->plug));
184
185 // Setup drag/drop proxy from parent/grandparent window
186 GdkWindow* gwindow = gtk_widget_get_window(GTK_WIDGET(wrap->plug));
187 Window xwindow = GDK_WINDOW_XID(gwindow);
188 Atom xdnd_proxy_atom = gdk_x11_get_xatom_by_name("XdndProxy");
189 Window plugin = (Window)wrap->instance->ui_widget;
190
191 while (xwindow) {
192 XChangeProperty(GDK_WINDOW_XDISPLAY(gwindow),
193 xwindow,
194 xdnd_proxy_atom,
195 XA_WINDOW,
196 32,
197 PropModeReplace,
198 (unsigned char*)&plugin,
199 1);
200
201 xwindow = get_parent_window(GDK_WINDOW_XDISPLAY(gwindow), xwindow);
202 }
203 }
204
205 static void
suil_x11_wrapper_show(GtkWidget * w)206 suil_x11_wrapper_show(GtkWidget* w)
207 {
208 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w);
209
210 if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show) {
211 GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show(w);
212 }
213
214 gtk_widget_show(GTK_WIDGET(wrap->plug));
215 }
216
217 static gboolean
forward_key_event(SuilX11Wrapper * socket,GdkEvent * gdk_event)218 forward_key_event(SuilX11Wrapper* socket, GdkEvent* gdk_event)
219 {
220 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
221 GdkScreen* screen = gdk_visual_get_screen(gdk_window_get_visual(window));
222
223 Window target_window = 0;
224 if (gdk_event->any.window == window) {
225 // Event sent up to the plug window, forward it up to the parent
226 GtkWidget* widget = GTK_WIDGET(socket->instance->host_widget);
227 GdkWindow* parent = gtk_widget_get_parent_window(widget);
228 if (parent) {
229 target_window = GDK_WINDOW_XID(parent);
230 } else {
231 return FALSE; // Wrapper is a top-level window, do nothing
232 }
233 } else {
234 // Event sent anywhere else, send to the plugin
235 target_window = (Window)socket->instance->ui_widget;
236 }
237
238 XKeyEvent xev;
239 memset(&xev, 0, sizeof(xev));
240 xev.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
241 xev.root = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
242 xev.window = target_window;
243 xev.subwindow = None;
244 xev.time = gdk_event->key.time;
245 xev.state = gdk_event->key.state;
246 xev.keycode = gdk_event->key.hardware_keycode;
247
248 XSendEvent(GDK_WINDOW_XDISPLAY(window),
249 target_window,
250 False,
251 NoEventMask,
252 (XEvent*)&xev);
253
254 return (gdk_event->any.window != window);
255 }
256
257 static gboolean
idle_size_request(gpointer user_data)258 idle_size_request(gpointer user_data)
259 {
260 SuilX11Wrapper* socket = (SuilX11Wrapper *) user_data;
261 GtkWidget* w = GTK_WIDGET(socket->plug);
262 gtk_widget_queue_resize(w);
263 socket->idle_size_request_id = 0;
264 return FALSE;
265 }
266
267 /// Read XSizeHints and store the values for later use
268 static void
query_wm_hints(SuilX11Wrapper * wrap)269 query_wm_hints(SuilX11Wrapper* wrap)
270 {
271 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(wrap->plug));
272 XSizeHints hints = {0};
273 long supplied = 0;
274
275 XGetWMNormalHints(GDK_WINDOW_XDISPLAY(window),
276 (Window)wrap->instance->ui_widget,
277 &hints,
278 &supplied);
279
280 if (hints.flags & PMaxSize) {
281 wrap->max_size.width = hints.max_width;
282 wrap->max_size.height = hints.max_height;
283 wrap->max_size.is_set = true;
284 }
285
286 if (hints.flags & PBaseSize) {
287 wrap->base_size.width = hints.base_width;
288 wrap->base_size.height = hints.base_height;
289 wrap->base_size.is_set = true;
290 }
291
292 if (hints.flags & PMinSize) {
293 wrap->min_size.width = hints.min_width;
294 wrap->min_size.height = hints.min_height;
295 wrap->min_size.is_set = true;
296 }
297
298 wrap->query_wm = false;
299 }
300
301 static void
forward_size_request(SuilX11Wrapper * socket,GtkAllocation * allocation)302 forward_size_request(SuilX11Wrapper* socket, GtkAllocation* allocation)
303 {
304 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
305 if (x_window_is_valid(socket)) {
306 // Calculate allocation size constrained to X11 limits for widget
307 int width = allocation->width;
308 int height = allocation->height;
309 XSizeHints hints;
310 memset(&hints, 0, sizeof(hints));
311 XGetNormalHints(
312 GDK_WINDOW_XDISPLAY(window), (Window)socket->instance->ui_widget, &hints);
313 if (hints.flags & PMaxSize) {
314 width = MIN(width, hints.max_width);
315 height = MIN(height, hints.max_height);
316 }
317 if (hints.flags & PMinSize) {
318 width = MAX(width, hints.min_width);
319 height = MAX(height, hints.min_height);
320 }
321
322 // Resize widget window
323 XResizeWindow(GDK_WINDOW_XDISPLAY(window),
324 (Window)socket->instance->ui_widget,
325 (unsigned)width,
326 (unsigned)height);
327
328 // Get actual widget geometry
329 Window root = 0;
330 int wx = 0;
331 int wy = 0;
332 unsigned int ww = 0;
333 unsigned int wh = 0;
334 unsigned int ignored = 0;
335 XGetGeometry(GDK_WINDOW_XDISPLAY(window),
336 (Window)socket->instance->ui_widget,
337 &root,
338 &wx,
339 &wy,
340 &ww,
341 &wh,
342 &ignored,
343 &ignored);
344
345 // Center widget in allocation
346 wx = (allocation->width - (int)ww) / 2;
347 wy = (allocation->height - (int)wh) / 2;
348 XMoveWindow(
349 GDK_WINDOW_XDISPLAY(window), (Window)socket->instance->ui_widget, wx, wy);
350 } else {
351 /* Child has not been realized, so unable to resize now.
352 Queue an idle resize. */
353 socket->idle_size_request_id = g_idle_add(idle_size_request, socket);
354 }
355 }
356
357 static gboolean
suil_x11_wrapper_key_event(GtkWidget * widget,GdkEventKey * event)358 suil_x11_wrapper_key_event(GtkWidget* widget, GdkEventKey* event)
359 {
360 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
361
362 if (self->plug) {
363 return forward_key_event(self, (GdkEvent*)event);
364 }
365
366 return FALSE;
367 }
368
369 static void
suil_x11_wrapper_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)370 suil_x11_wrapper_get_preferred_width(GtkWidget* widget,
371 gint* minimum_width,
372 gint* natural_width)
373 {
374 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
375 if (x_window_is_valid(self)) {
376 query_wm_hints(self);
377
378 if (self->custom_size.is_set)
379 *natural_width = self->custom_size.width;
380 else if (self->base_size.is_set)
381 *natural_width = self->base_size.width;
382 else if (self->min_size.is_set)
383 *natural_width = self->min_size.width;
384 else
385 g_return_if_reached ();
386
387 if (self->min_size.is_set)
388 *minimum_width = self->min_size.width;
389 else if (self->custom_size.is_set)
390 *natural_width = self->base_size.width;
391 else if (self->base_size.is_set)
392 *natural_width = self->base_size.width;
393 else
394 g_return_if_reached ();
395 }
396 }
397
398 static void
suil_x11_wrapper_get_preferred_height(GtkWidget * widget,gint * minimum_height,gint * natural_height)399 suil_x11_wrapper_get_preferred_height(GtkWidget* widget,
400 gint* minimum_height,
401 gint* natural_height)
402 {
403 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
404 if (x_window_is_valid(self)) {
405 query_wm_hints(self);
406
407 if (self->custom_size.is_set)
408 *natural_height = self->custom_size.height;
409 else if (self->base_size.is_set)
410 *natural_height = self->base_size.height;
411 else if (self->min_size.is_set)
412 *natural_height = self->min_size.height;
413 else
414 g_return_if_reached ();
415
416 if (self->min_size.is_set)
417 *minimum_height = self->min_size.height;
418 else if (self->custom_size.is_set)
419 *natural_height = self->base_size.height;
420 else if (self->base_size.is_set)
421 *natural_height = self->base_size.height;
422 else
423 g_return_if_reached ();
424 }
425 }
426
427 static void
suil_x11_on_size_allocate(GtkWidget * widget,GtkAllocation * a)428 suil_x11_on_size_allocate(GtkWidget* widget, GtkAllocation* a)
429 {
430 SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
431
432 if (self->plug && gtk_widget_get_realized(widget) &&
433 gtk_widget_get_mapped(widget) && gtk_widget_get_visible(widget)) {
434 forward_size_request(self, a);
435 }
436 }
437
438 static void
suil_x11_wrapper_class_init(SuilX11WrapperClass * klass)439 suil_x11_wrapper_class_init(SuilX11WrapperClass* klass)
440 {
441 GObjectClass* const gobject_class = G_OBJECT_CLASS(klass);
442 GtkWidgetClass* const widget_class = GTK_WIDGET_CLASS(klass);
443
444 gobject_class->finalize = suil_x11_wrapper_finalize;
445 widget_class->realize = suil_x11_wrapper_realize;
446 widget_class->show = suil_x11_wrapper_show;
447 widget_class->key_press_event = suil_x11_wrapper_key_event;
448 widget_class->key_release_event = suil_x11_wrapper_key_event;
449 widget_class->get_preferred_width = suil_x11_wrapper_get_preferred_width;
450 widget_class->get_preferred_height = suil_x11_wrapper_get_preferred_height;
451 }
452
453 static void
suil_x11_wrapper_init(SuilX11Wrapper * self)454 suil_x11_wrapper_init(SuilX11Wrapper* self)
455 {
456 self->plug = GTK_PLUG(gtk_plug_new(0));
457 self->wrapper = NULL;
458 self->instance = NULL;
459 self->idle_iface = NULL;
460 self->idle_ms = 1000 / 30; // 30 Hz default
461 self->max_size = (SuilX11SizeHints){false, 0, 0};
462 self->custom_size = (SuilX11SizeHints){false, 0, 0};
463 self->base_size = (SuilX11SizeHints){false, 0, 0};
464 self->min_size = (SuilX11SizeHints){false, 0, 0};
465 self->query_wm = true;
466 }
467
468 static int
wrapper_resize(LV2UI_Feature_Handle handle,int width,int height)469 wrapper_resize(LV2UI_Feature_Handle handle, int width, int height)
470 {
471 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(handle);
472
473 wrap->custom_size.width = width;
474 wrap->custom_size.height = height;
475 wrap->custom_size.is_set = width > 0 && height > 0;
476
477 // Assume the plugin has also updated min/max size constraints
478 wrap->query_wm = true;
479
480 gtk_widget_queue_resize(GTK_WIDGET(handle));
481 return 0;
482 }
483
484 static gboolean
suil_x11_wrapper_idle(void * data)485 suil_x11_wrapper_idle(void* data)
486 {
487 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(data);
488
489 wrap->idle_iface->idle(wrap->instance->handle);
490
491 return TRUE; // Continue calling
492 }
493
494 static int
wrapper_wrap(SuilWrapper * wrapper,SuilInstance * instance)495 wrapper_wrap(SuilWrapper* wrapper, SuilInstance* instance)
496 {
497 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl);
498
499 instance->host_widget = GTK_WIDGET(wrap);
500 wrap->wrapper = wrapper;
501 wrap->instance = instance;
502
503 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(wrap->plug));
504 GdkDisplay* display = gdk_window_get_display(window);
505 Display* xdisplay = GDK_WINDOW_XDISPLAY(window);
506 Window xwindow = (Window)instance->ui_widget;
507
508 gdk_display_sync(display);
509 if (x_window_is_valid(wrap)) {
510 XWindowAttributes attrs;
511 XGetWindowAttributes(xdisplay, xwindow, &attrs);
512
513 query_wm_hints(wrap);
514
515 if (!wrap->base_size.is_set) {
516 // Fall back to using initial size as base size
517 wrap->base_size.is_set = true;
518 wrap->base_size.width = attrs.width;
519 wrap->base_size.height = attrs.height;
520 }
521 }
522
523 const LV2UI_Idle_Interface* idle_iface = NULL;
524 if (instance->descriptor->extension_data) {
525 idle_iface =
526 (const LV2UI_Idle_Interface*)instance->descriptor->extension_data(
527 LV2_UI__idleInterface);
528 }
529
530 if (idle_iface) {
531 wrap->idle_iface = idle_iface;
532 wrap->idle_id = g_timeout_add(wrap->idle_ms, suil_x11_wrapper_idle, wrap);
533 }
534
535 g_signal_connect(
536 G_OBJECT(wrap), "plug-removed", G_CALLBACK(on_plug_removed), NULL);
537
538 g_signal_connect(G_OBJECT(wrap),
539 "size-allocate",
540 G_CALLBACK(suil_x11_on_size_allocate),
541 NULL);
542
543 return 0;
544 }
545
546 static void
wrapper_free(SuilWrapper * wrapper)547 wrapper_free(SuilWrapper* wrapper)
548 {
549 if (wrapper->impl) {
550 SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl);
551 gtk_widget_destroy(GTK_WIDGET(wrap));
552 }
553 }
554
555 SuilWrapper*
suil_wrapper_new_x11(SuilHost * host,const char * host_type_uri,const char * ui_type_uri,LV2_Feature *** features,unsigned n_features)556 suil_wrapper_new_x11(SuilHost* host,
557 const char* host_type_uri,
558 const char* ui_type_uri,
559 LV2_Feature*** features,
560 unsigned n_features)
561 {
562 (void)host;
563 (void)host_type_uri;
564 (void)ui_type_uri;
565
566 SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper));
567 wrapper->wrap = wrapper_wrap;
568 wrapper->free = wrapper_free;
569
570 SuilX11Wrapper* const wrap =
571 SUIL_X11_WRAPPER(g_object_new(SUIL_TYPE_X11_WRAPPER, NULL));
572
573 wrapper->impl = wrap;
574 wrapper->resize.handle = wrap;
575 wrapper->resize.ui_resize = wrapper_resize;
576
577 gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE);
578 gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE);
579
580 const intptr_t parent_id = (intptr_t)gtk_plug_get_id(wrap->plug);
581 suil_add_feature(features, &n_features, LV2_UI__parent, (void*)parent_id);
582 suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize);
583 suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL);
584
585 // Scan for URID map and options
586 LV2_URID_Map* map = NULL;
587 LV2_Options_Option* options = NULL;
588 for (LV2_Feature** f = *features; *f && (!map || !options); ++f) {
589 if (!strcmp((*f)->URI, LV2_OPTIONS__options)) {
590 options = (LV2_Options_Option*)(*f)->data;
591 } else if (!strcmp((*f)->URI, LV2_URID__map)) {
592 map = (LV2_URID_Map*)(*f)->data;
593 }
594 }
595
596 if (map && options) {
597 // Set UI update rate if given
598 LV2_URID ui_updateRate = map->map(map->handle, LV2_UI__updateRate);
599 for (LV2_Options_Option* o = options; o->key; ++o) {
600 if (o->key == ui_updateRate) {
601 wrap->idle_ms = (guint)(1000.0f / *(const float*)o->value);
602 break;
603 }
604 }
605 }
606
607 return wrapper;
608 }
609
610 #endif /* ifdef HAVE X11 */
611
612 #endif /* ifndef HAVE_SUIL */
613