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