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