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