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