1 /* meters.lv2 openGL frontend
2  *
3  * Copyright (C) 2013-2016 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 //#undef HAVE_IDLE_IFACE // simulate old LV2
21 //#define USE_GUI_THREAD // use own thread (not idle callback), should be preferred even w/idle availale on most platforms
22 //#define THREADSYNC     // wake up GUI thread on port-event
23 
24 #ifdef XTERNAL_UI
25 #if defined USE_GUI_THREAD && defined _WIN32
26 #  define INIT_PUGL_IN_THREAD
27 #endif
28 #endif
29 
30 //#define TIMED_RESHAPE // resize view when idle
31 //#define DEBUG_RESIZE
32 //#define DEBUG_EXPOSURE
33 //#define VISIBLE_EXPOSE
34 //#define DEBUG_UI
35 
36 /* either USE_GUI_THREAD or HAVE_IDLE_IFACE needs to be defined.
37  *
38  * if both are defined:
39  * the goniometer window can change size by itself..
40  *
41  * IFF HAVE_IDLE_IFACE is available we can use it to
42  * resize the suil-swallowed window (gtk host only), by
43  * making a call to gtk_window_resize of the gtk_widget_get_toplevel
44  * in the thread-context of the main application.
45  *
46  * without USE_GUI_THREAD but with HAVE_IDLE_IFACE:
47  * All plugins run single threaded in the host's process context.
48  *
49  * That will avoid various threading issues - particularly with
50  * libcairo < 1.12.10, libpixman < 0.30.2 and libpango < 1.32.6
51  * which are not threadsafe)
52  *
53  * The plugin UI launching its own thread yields generally better
54  * performance (the host's idle call's timing is inaccurate and
55  * using the idle interface will also slow down the host's UI...
56  */
57 #if (!defined HAVE_IDLE_IFACE && !defined USE_GUI_THREAD)
58 #error At least one of HAVE_IDLE_IFACE or USE_GUI_THREAD must be defined.
59 #endif
60 
61 #define _POSIX_C_SOURCE 200809L
62 
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <unistd.h>
66 #include <string.h>
67 #include <math.h>
68 #include <pthread.h>
69 #include <assert.h>
70 
71 #include "pugl/pugl.h"
72 
73 #ifdef __APPLE__
74 #include "OpenGL/glu.h"
75 #else
76 #include <GL/glu.h>
77 #endif
78 
79 #ifndef GL_BGRA
80 #define GL_BGRA 0x80E1 // GL_BGRA_EXT
81 #endif
82 #ifndef GL_RGBA8
83 #define GL_RGBA8 GL_RGBA
84 #endif
85 #ifndef GL_TEXTURE_RECTANGLE_ARB
86 #define GL_TEXTURE_RECTANGLE_ARB 0x84F5
87 #endif
88 
89 #ifdef USE_GTK_RESIZE_HACK
90 #include <gtk/gtk.h>
91 #endif
92 
93 #ifndef M_PI
94 #define M_PI 3.14159265358979323846
95 #endif
96 
97 #define ROBTK_MOD_SHIFT PUGL_MOD_SHIFT
98 #define ROBTK_MOD_CTRL PUGL_MOD_CTRL
99 #include "gl/posringbuf.h"
100 #include "robtk.h"
101 
102 #ifdef WITH_SIGNATURE
103 # include "gpg_init.c"
104 #endif
105 
opengl_init()106 static void opengl_init () {
107 	glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
108 	glDisable (GL_DEPTH_TEST);
109 	glEnable (GL_BLEND);
110 	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
111 	glEnable (GL_TEXTURE_RECTANGLE_ARB);
112 }
113 
opengl_draw(int width,int height,unsigned char * surf_data,unsigned int texture_id)114 static void opengl_draw (int width, int height, unsigned char* surf_data, unsigned int texture_id) {
115 	if (!surf_data) { return; }
116 
117 	glMatrixMode(GL_MODELVIEW);
118 	glLoadIdentity();
119 	glClear(GL_COLOR_BUFFER_BIT);
120 
121 	glPushMatrix ();
122 
123 	glEnable(GL_TEXTURE_2D);
124 	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id);
125 	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
126 			width, height, /*border*/ 0,
127 			GL_BGRA, GL_UNSIGNED_BYTE, surf_data);
128 
129 	glBegin(GL_QUADS);
130 	glTexCoord2f(           0.0f, (GLfloat) height);
131 	glVertex2f(-1.0f, -1.0f);
132 
133 	glTexCoord2f((GLfloat) width, (GLfloat) height);
134 	glVertex2f( 1.0f, -1.0f);
135 
136 	glTexCoord2f((GLfloat) width, 0.0f);
137 	glVertex2f( 1.0f,  1.0f);
138 
139 	glTexCoord2f(            0.0f, 0.0f);
140 	glVertex2f(-1.0f,  1.0f);
141 	glEnd();
142 
143 	glDisable(GL_TEXTURE_2D);
144 	glPopMatrix();
145 }
146 
opengl_reallocate_texture(int width,int height,unsigned int * texture_id)147 static void opengl_reallocate_texture (int width, int height, unsigned int* texture_id) {
148 	glViewport (0, 0, width, height);
149 	glMatrixMode (GL_PROJECTION);
150 	glLoadIdentity ();
151 	glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
152 
153 	glClear (GL_COLOR_BUFFER_BIT);
154 
155 	glDeleteTextures (1, texture_id);
156 	glGenTextures (1, texture_id);
157 	glBindTexture (GL_TEXTURE_RECTANGLE_ARB, *texture_id);
158 	glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
159 			width, height, 0,
160 			GL_BGRA, GL_UNSIGNED_BYTE, NULL);
161 	glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
162 }
163 
opengl_create_cairo_t(int width,int height,cairo_surface_t ** surface,unsigned char ** buffer)164 static cairo_t* opengl_create_cairo_t (int width, int height, cairo_surface_t** surface, unsigned char** buffer)
165 {
166 	cairo_t* cr;
167 	const int bpp = 4;
168 
169 	*buffer = (unsigned char*) calloc (bpp * width * height, sizeof (unsigned char));
170 	if (!*buffer) {
171 		fprintf (stderr, "robtk: opengl surface out of memory.\n");
172 		return NULL;
173 	}
174 
175 	*surface = cairo_image_surface_create_for_data (*buffer,
176 			CAIRO_FORMAT_ARGB32, width, height, bpp * width);
177 	if (cairo_surface_status (*surface) != CAIRO_STATUS_SUCCESS) {
178 		free (*buffer);
179 		fprintf (stderr, "robtk: failed to create cairo surface\n");
180 		return NULL;
181 	}
182 
183 	cr = cairo_create (*surface);
184 	if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
185 		free (*buffer);
186 		fprintf (stderr, "robtk: cannot create cairo context\n");
187 		return NULL;
188 	}
189 
190 	return cr;
191 }
192 
193 /*****************************************************************************/
194 
195 #include "gl/xternalui.h"
196 
197 typedef struct {
198 	PuglView*            view;
199 	LV2UI_Resize*        resize;
200 	LV2UI_Write_Function write; // unused for now
201 	LV2UI_Controller     controller; // unused for now
202 	PuglNativeWindow     parent; // only valud during init
203 	bool                 ontop;
204 	unsigned long        transient_id; // X11 window ID
205 
206 	struct lv2_external_ui_host *extui;
207 	struct lv2_external_ui xternal_ui;
208 
209 	int                  width;
210 	int                  height;
211 	int                  xoff;
212 	int                  yoff;
213 	float                xyscale;
214 	bool                 gl_initialized;
215 #ifdef WITH_SIGNATURE
216 	bool                 gpg_verified;
217 	char                 gpg_data[128];
218 	float                gpg_shade;
219 #endif
220 #ifdef INIT_PUGL_IN_THREAD
221 	int                  ui_initialized;
222 #endif
223 	bool                 resize_in_progress;
224 	bool                 resize_toplevel;
225 
226 #ifdef USE_GUI_THREAD
227 	int                  ui_queue_puglXWindow;
228 	pthread_t            thread;
229 	int                  exit;
230 #endif
231 #ifdef TIMED_RESHAPE
232 	uint64_t         queue_reshape;
233 	int              queue_w;
234 	int              queue_h;
235 #else
236 	bool             relayout;
237 #endif
238 
239 	cairo_t*         cr;
240 	cairo_surface_t* surface;
241 	unsigned char*   surf_data;
242 #if __BIG_ENDIAN__
243 	unsigned char*   surf_data_be;
244 #endif
245 	unsigned int     texture_id;
246 
247 	/* top-level */
248 	RobWidget    *tl;
249 	LV2UI_Handle  ui;
250 
251 	/* toolkit state stuff */
252 	volatile cairo_rectangle_t expose_area;
253 	RobWidget *mousefocus;
254 	RobWidget *mousehover;
255 
256 	posringbuf *rb;
257 
258 #if (defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE)
259 	bool do_the_funky_resize;
260 #endif
261 	bool queue_canvas_realloc;
262 
263 	void (* ui_closed)(void* controller);
264 	bool close_ui; // used by xternalui
265 
266 #if (defined USE_GUI_THREAD && defined THREADSYNC)
267 	pthread_mutex_t msg_thread_lock;
268 	pthread_cond_t data_ready;
269 #endif
270 
271 	void (* expose_overlay)(RobWidget* toplevel, cairo_t* cr, cairo_rectangle_t *ev);
272 	float queue_widget_scale;
273 
274 } GLrobtkLV2UI;
275 
robtk_info(void * h)276 static const char * robtk_info(void *h) {
277 #ifdef WITH_SIGNATURE
278 	GLrobtkLV2UI * self = (GLrobtkLV2UI*) h;
279 	return self->gpg_data;
280 #else
281 	return "v" VERSION;
282 #endif
283 }
284 
robtk_close_self(void * h)285 static void robtk_close_self(void *h) {
286 	GLrobtkLV2UI * self = (GLrobtkLV2UI*) h;
287 	if (self->ui_closed) {
288 		self->ui_closed(self);
289 	}
290 }
291 
robtk_open_file_dialog(void * h,const char * title)292 static int robtk_open_file_dialog(void *h, const char *title) {
293 	GLrobtkLV2UI * self = (GLrobtkLV2UI*) h;
294 	return puglOpenFileDialog(self->view, title);
295 }
296 
297 /*****************************************************************************/
298 
299 #include PLUGIN_SOURCE
300 
301 /*****************************************************************************/
302 
303 #ifdef WITH_SIGNATURE
304 #include WITH_SIGNATURE
305 #endif
306 
307 #ifdef ROBTKAPP
308 static const void*
extension_data(const char * uri)309 extension_data(const char* uri)
310 {
311 	return NULL;
312 }
313 
314 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)315 port_event(LV2UI_Handle handle,
316            uint32_t     port_index,
317            uint32_t     buffer_size,
318            uint32_t     format,
319            const void*  buffer)
320 {
321 }
322 #endif
323 
324 /*****************************************************************************/
325 
326 #include "gl/xternalui.c"
327 
328 static void reallocate_canvas(GLrobtkLV2UI* self);
329 
330 /*****************************************************************************/
331 /* RobWidget implementation & glue */
332 
333 typedef struct {
334 	RobWidget *rw;
335 	cairo_rectangle_t a;
336 } RWArea;
337 
338 #ifdef WITH_SIGNATURE
lc_expose(GLrobtkLV2UI * self)339 static void lc_expose (GLrobtkLV2UI * self) {
340 	assert (self->tl);
341 	if (!self->tl) { return; }
342 	cairo_rectangle_t expose_area;
343 	posrb_read_clear(self->rb); // no fast-track
344 
345 	expose_area.x = expose_area.y = 0;
346 	expose_area.width = self->width;
347 	expose_area.height = self->height;
348 	cairo_save(self->cr);
349 	self->tl->resized = TRUE; // full re-expose
350 	self->tl->expose_event(self->tl, self->cr, &expose_area);
351 	cairo_restore(self->cr);
352 
353 	expose_area.x = expose_area.y = 0;
354 	expose_area.width = self->width;
355 	expose_area.height = self->height;
356 
357 	PangoFontDescription *xfont = pango_font_description_from_string("Sans 16px");
358 	cairo_rectangle (self->cr, 0, 0, self->width, self->height);
359 	cairo_set_operator (self->cr, CAIRO_OPERATOR_OVER);
360 	cairo_set_source_rgba(self->cr, 0, 0, 0, .15 + self->gpg_shade);
361 	if (self->gpg_shade < .6) self->gpg_shade += .001;
362 	cairo_fill(self->cr);
363 	write_text_full(self->cr, "Unregistered Version\nhttp://x42-plugins.com",
364 			xfont, self->width * .5, self->height * .5,
365 			self->width < 200 ? M_PI * -.5 : 0, -2, c_wht);
366 	pango_font_description_free(xfont);
367 
368 	cairo_surface_mark_dirty(self->surface);
369 }
370 #endif
371 
cairo_expose(GLrobtkLV2UI * self)372 static void cairo_expose(GLrobtkLV2UI * self) {
373 
374 	if (self->expose_overlay) {
375 		cairo_rectangle_t expose_area;
376 		posrb_read_clear(self->rb); // no fast-track
377 		self->tl->resized = TRUE; // full re-expose
378 		expose_area.x = expose_area.y = 0;
379 		expose_area.width = self->width;
380 		expose_area.height = self->height;
381 
382 		cairo_save(self->cr);
383 		self->tl->expose_event(self->tl, self->cr, &expose_area);
384 		cairo_restore(self->cr);
385 
386 		cairo_save(self->cr);
387 		self->expose_overlay (self->tl, self->cr, &expose_area);
388 		cairo_restore(self->cr);
389 		return;
390 	}
391 
392 	/* FAST TRACK EXPOSE */
393 	int qq = posrb_read_space(self->rb) / sizeof(RWArea);
394 	bool dirty = qq > 0;
395 #ifdef DEBUG_FASTTRACK
396 	/*if (qq > 0)*/ fprintf(stderr, " fast track %d events\n", qq);
397 #endif
398 	cairo_rectangle_t fast_track_area = {0,0,0,0};
399 	uint32_t fast_track_cnt = 0;
400 	while (--qq >= 0) {
401 		RWArea a;
402 		posrb_read(self->rb, (uint8_t*) &a, sizeof(RWArea));
403 		assert(a.rw);
404 
405 		/* skip duplicates */
406 		if (fast_track_cnt > 0) {
407 			if (   a.a.x+a.rw->trel.x >= fast_track_area.x
408 					&& a.a.y+a.rw->trel.y >= fast_track_area.y
409 					&& a.a.x+a.rw->trel.x+a.a.width  <= fast_track_area.x + fast_track_area.width
410 					&& a.a.y+a.rw->trel.y+a.a.height <= fast_track_area.y + fast_track_area.height) {
411 #ifdef DEBUG_FASTTRACK
412 				fprintf(stderr, " skip fast-track event #%d (%.1f x %.1f + %.1f + %.1f)\n", fast_track_cnt,
413 						a.a.width, a.a.height, a.a.x+a.rw->trel.x, a.a.y+a.rw->trel.y);
414 #endif
415 				continue;
416 			}
417 		}
418 		cairo_save(self->cr);
419 		cairo_translate(self->cr, a.rw->trel.x, a.rw->trel.y);
420 		a.rw->expose_event(a.rw, self->cr, &a.a);
421 
422 		/* keep track of exposed parts */
423 		a.a.x += a.rw->trel.x;
424 		a.a.y += a.rw->trel.y;
425 #ifdef DEBUG_FASTTRACK
426 		fprintf(stderr, "                       #%d (%.1f x %.1f @ %.1f + %.1f\n", fast_track_cnt,
427 						a.a.width, a.a.height, a.a.x, a.a.y);
428 #endif
429 #ifndef FASTTRACK_INTERSECT
430 		memcpy(&fast_track_area, &a.a, sizeof(cairo_rectangle_t));
431 		fast_track_cnt++;
432 #else // intersects
433 		if (fast_track_cnt++ == 0) {
434 			fast_track_area.x = a.a.x;
435 			fast_track_area.y = a.a.y;
436 			fast_track_area.width = a.a.width;
437 			fast_track_area.height = a.a.height;
438 		} else {
439 			rect_intersection(&fast_track_area, &a.a, &fast_track_area);
440 			//rect_combine(&fast_track_area, &a.a, &fast_track_area);
441 		}
442 #endif
443 
444 #ifdef VISIBLE_EXPOSE
445 		static int ftrk = 0;
446 		static int fcol = 0;
447 		ftrk = (ftrk + 1) %11;
448 		fcol = (fcol + 1) %17;
449 		cairo_rectangle (self->cr, 0, 0, a.rw->trel.width, a.rw->trel.height);
450 		cairo_set_operator (self->cr, CAIRO_OPERATOR_OVER);
451 		cairo_set_source_rgba(self->cr, .8, .5 + ftrk/25.0, .5 - fcol/40.0, .25 + ftrk/30.0);
452 		cairo_fill(self->cr);
453 #endif
454 
455 		cairo_restore(self->cr);
456 	}
457 
458 	if (self->expose_area.width == 0 || self->expose_area.height == 0) {
459 #ifdef DEBUG_EXPOSURE
460 		fprintf(stderr, " --- NO DRAW\n");
461 #endif
462 		if (dirty) {
463 			cairo_surface_mark_dirty(self->surface);
464 		}
465 		return;
466 	}
467 
468 #ifdef DEBUG_EXPOSURE
469 	fprintf(stderr, "XPS %.1f+%.1f  %.1fx%.1f\n", self->expose_area.x, self->expose_area.y, self->expose_area.width, self->expose_area.height);
470 #endif
471 
472 	/* make copy and clear -- should be an atomic op when using own thread */
473 	cairo_rectangle_t area;
474 	area.x      = self->expose_area.x;
475 	area.y      = self->expose_area.y;
476 	area.width  = self->expose_area.width;
477 	area.height = self->expose_area.height;
478 
479 	self->expose_area.x = 0;
480 	self->expose_area.y = 0;
481 	self->expose_area.width = 0;
482 	self->expose_area.height = 0;
483 
484 #ifdef DEBUG_EXPOSURE
485 	fprintf(stderr, "---- XPS ---\n");
486 #endif
487 
488 	// intersect exposure with toplevel
489 	cairo_rectangle_t expose_area;
490 	expose_area.x      = MAX(0, area.x - self->tl->area.x);
491 	expose_area.y      = MAX(0, area.y - self->tl->area.y);
492 	expose_area.width  = MIN(area.x + area.width, self->tl->area.x + self->tl->area.width) - MAX(area.x, self->tl->area.x);
493 	expose_area.height = MIN(area.y + area.height, self->tl->area.y + self->tl->area.height) - MAX(area.y, self->tl->area.y);
494 
495 	if (expose_area.width < 0 || expose_area.height < 0) {
496 		fprintf(stderr, " !!! EMPTY AREA\n"); return;
497 	}
498 
499 #define XPS_NO_DRAW { fprintf(stderr, " !!! OUTSIDE DRAW %.1fx%.1f %.1f+%.1f %.1fx%.1f\n", area.x, area.y,  self->tl->area.x, self->tl->area.y, self->tl->area.width, self->tl->area.height); return; }
500 
501 #if 1
502 	if (area.x > self->tl->area.x + self->tl->area.width) XPS_NO_DRAW
503 	if (area.y > self->tl->area.y + self->tl->area.height) XPS_NO_DRAW
504 	if (area.x < self->tl->area.x) XPS_NO_DRAW
505 	if (area.y < self->tl->area.y) XPS_NO_DRAW
506 #endif
507 
508 	cairo_save(self->cr);
509 	self->tl->expose_event(self->tl, self->cr, &expose_area);
510 	cairo_restore(self->cr);
511 
512 #ifdef VISIBLE_EXPOSE
513 	static int move = 0;
514 	static int hueh = 0;
515 	move = (move + 1) %10;
516 	hueh = (hueh + 1) %13;
517 	cairo_rectangle (self->cr, expose_area.x, expose_area.y, expose_area.width, expose_area.height);
518 	cairo_set_operator (self->cr, CAIRO_OPERATOR_OVER);
519 	cairo_set_source_rgba(self->cr, .7 + hueh / 50.0, .3, .5 - hueh/30, .25 + move/20.0);
520 	cairo_fill(self->cr);
521 #endif
522 
523 	cairo_surface_mark_dirty(self->surface);
524 }
525 
queue_draw_full(RobWidget * rw)526 static void queue_draw_full(RobWidget *rw) {
527 	GLrobtkLV2UI * const self =
528 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
529 	if (!self || !self->view) {
530 		rw->redraw_pending = true;
531 		return;
532 	}
533 #ifdef DEBUG_EXPOSURE
534 	fprintf(stderr, "~~ queue_draw_full '%s'\n", ROBWIDGET_NAME(rw));
535 #endif
536 
537 	self->expose_area.x = 0;
538 	self->expose_area.y = 0;
539 	self->expose_area.width = self->width;
540 	self->expose_area.height = self->height;
541 	puglPostRedisplay(self->view);
542 }
543 
queue_draw_area(RobWidget * rw,int x,int y,int width,int height)544 static void queue_draw_area(RobWidget *rw, int x, int y, int width, int height) {
545 	GLrobtkLV2UI * self =
546 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
547 	if (!self || !self->view) {
548 		rw->redraw_pending = true;
549 		return;
550 	}
551 
552 	if (x < 0) x = 0;
553 	if (y < 0) y = 0;
554 	if (x + width > rw->area.width) width = rw->area.width - x;
555 	if (y + height > rw->area.height) height = rw->area.height - y;
556 
557 	if (self->expose_area.width == 0 || self->expose_area.height == 0) {
558 		RobTkBtnEvent ev; ev.x = x; ev.y = y;
559 #ifdef DEBUG_EXPOSURE
560 		fprintf(stderr, "Q PARTIAL NEW   '%s': ", ROBWIDGET_NAME(rw));
561 #endif
562 		offset_traverse_from_child(rw, &ev);
563 #ifdef DEBUG_EXPOSURE
564 		fprintf(stderr, "Q PARTIAL -> %d+%d  -> %d+%d (%dx%d)\n", x, y, ev.x, ev.y, width, height);
565 #endif
566 		self->expose_area.x = ev.x;
567 		self->expose_area.y = ev.y;
568 		self->expose_area.width = width;
569 		self->expose_area.height = height;
570 	} else {
571 		RobTkBtnEvent ev; ev.x = x; ev.y = y;
572 #ifdef DEBUG_EXPOSURE
573 		fprintf(stderr, "Q PARTIAL AMEND '%s': ", ROBWIDGET_NAME(rw));
574 #endif
575 		offset_traverse_from_child(rw, &ev);
576 
577 #ifdef DEBUG_EXPOSURE
578 		fprintf(stderr, "Q PARTIAL -> %d+%d  -> %d+%d (%dx%d)\n", x, y, ev.x, ev.y, width, height);
579 #endif
580 
581 		cairo_rectangle_t r;
582 		r.x = ev.x; r.y = ev.y;
583 		r.width = width; r.height = height;
584 		rect_combine((cairo_rectangle_t*) &self->expose_area, &r, (cairo_rectangle_t*) &self->expose_area);
585 	}
586 	puglPostRedisplay(self->view);
587 }
588 
queue_draw(RobWidget * rw)589 static void queue_draw(RobWidget *rw) {
590 #ifdef DEBUG_EXPOSURE
591 	fprintf(stderr, "FULL widget -> ");
592 #endif
593 	queue_draw_area(rw, 0, 0, rw->area.width, rw->area.height);
594 }
595 
queue_tiny_rect(RobWidget * rw,cairo_rectangle_t * a)596 static void queue_tiny_rect(RobWidget *rw, cairo_rectangle_t *a) {
597 	if (!rw->cached_position) {
598 		rw->redraw_pending = true;
599 		queue_draw(rw);
600 		return;
601 	}
602 	GLrobtkLV2UI * self =
603 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);// XXX cache ?!
604 	if (!self || !self->view) {
605 		rw->redraw_pending = true;
606 		return;
607 	}
608 
609 	RWArea b;
610 	b.rw = rw;
611 	memcpy(&b.a, a, sizeof(cairo_rectangle_t));
612 	if (posrb_write(self->rb,  (uint8_t*) &b, sizeof(RWArea))<0) {
613 		queue_draw_area(rw, a->x, a->y, a->width, a->height);
614 	}
615 	puglPostRedisplay(self->view);
616 }
617 
queue_tiny_area(RobWidget * rw,float x,float y,float w,float h)618 static void queue_tiny_area(RobWidget *rw, float x, float y, float w, float h) {
619 	cairo_rectangle_t a;
620 	a.x = x; a.width = w;
621 	a.y = y - 1;
622 	a.height = h + 1;
623 	queue_tiny_rect(rw, &a);
624 }
625 
robwidget_layout(GLrobtkLV2UI * const self,bool setsize,bool init)626 static void robwidget_layout(GLrobtkLV2UI * const self, bool setsize, bool init) {
627 	RobWidget * rw = self->tl;
628 #ifdef DEBUG_RESIZE
629 	printf("robwidget_layout(%s) setsize:%s init:%s\n", ROBWIDGET_NAME(self->tl),
630 			setsize ? "true" : "false",
631 			init ? "true" : "false"
632 			);
633 #endif
634 
635 	int oldw = self->width;
636 	int oldh = self->height;
637 	bool size_changed = FALSE;
638 
639 	int nox, noy;
640 
641 	rtoplevel_scale (self->tl, self->tl->widget_scale);
642 	self->tl->size_request(self->tl, &nox, &noy);
643 
644 	if (!init && rw->size_limit) {
645 		self->tl->size_limit(self->tl, &self->width, &self->height);
646 		if (oldw != self->width || oldh != self->height) {
647 			size_changed = TRUE;
648 		}
649 	} else if (setsize) {
650 		if (oldw != nox || oldh != noy) {
651 			size_changed = TRUE;
652 		}
653 		self->width = nox;
654 		self->height = noy;
655 	} else if (nox > self->width || noy > self->height) {
656 		enum LVGLResize rsz = plugin_scale_mode(self->ui);
657 		if (rsz == LVGL_ZOOM_TO_ASPECT || rsz == LVGL_LAYOUT_TO_FIT) {
658 			puglUpdateGeometryConstraints(self->view, nox, noy, rsz == LVGL_ZOOM_TO_ASPECT);
659 			return;
660 		}
661 		fprintf(stderr, "WINDOW IS SMALLER THAN MINIMUM SIZE! %d > %d h: %d > %d\n",
662 				nox, self->width, noy, self->height);
663 		if (nox > self->width) self->width = nox;
664 		if (noy > self->height) self->height = noy;
665 	} else if (nox < self->width || noy < self->height) {
666 		enum LVGLResize rsz = plugin_scale_mode(self->ui);
667 		if (rsz == LVGL_ZOOM_TO_ASPECT || rsz == LVGL_LAYOUT_TO_FIT) {
668 			puglUpdateGeometryConstraints(self->view, nox, noy, rsz == LVGL_ZOOM_TO_ASPECT);
669 		}
670 	}
671 
672 	if (rw->size_allocate) {
673 		self->tl->size_allocate(rw, self->width, self->height);
674 	}
675 
676 	rtoplevel_cache(rw, TRUE);
677 
678 	if (init) {
679 		return;
680 	}
681 
682 	if (setsize && size_changed) {
683 		self->resize_in_progress = TRUE;
684 		puglPostResize(self->view);
685 	} else {
686 		queue_draw_full(rw);
687 	}
688 }
689 
690 // called by a widget if size changes
resize_self(RobWidget * rw)691 static void resize_self(RobWidget *rw) {
692 
693 	GLrobtkLV2UI * const self =
694 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
695 	if (!self || !self->view) { return; }
696 
697 #ifdef DEBUG_RESIZE
698 	printf("resize_self(%s)\n", ROBWIDGET_NAME(rw));
699 #endif
700 	robwidget_layout(self, TRUE, FALSE);
701 }
702 
resize_toplevel(RobWidget * rw,int w,int h)703 static void resize_toplevel(RobWidget *rw, int w, int h) {
704 	GLrobtkLV2UI * const self =
705 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
706 	if (!self || !self->view) { return; }
707 	self->width = w;
708 	self->height = h;
709 	resize_self(rw);
710 	self->resize_in_progress = TRUE;
711 	self->resize_toplevel = TRUE;
712 	puglPostResize(self->view);
713 }
714 
relayout_toplevel(RobWidget * rw)715 static void relayout_toplevel(RobWidget *rw) {
716 	GLrobtkLV2UI * const self =
717 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
718 	if (!self || !self->view) { return; }
719 #ifdef TIMED_RESHAPE
720 	if (!self->resize_in_progress) {
721 		self->queue_w = self->width;
722 		self->queue_h = self->height;
723 		self->resize_in_progress = TRUE;
724 		self->queue_reshape = 1;
725 	}
726 #else
727 	self->relayout = TRUE;
728 #endif
729 	puglPostRedisplay(self->view);
730 }
731 
732 /*****************************************************************************/
733 /* UI scaling  */
734 
robtk_queue_scale_change(RobWidget * rw,const float ws)735 static void robtk_queue_scale_change (RobWidget *rw, const float ws) {
736 	GLrobtkLV2UI * const self =
737 		(GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
738 	self->queue_widget_scale = ws;
739 	queue_draw (rw);
740 }
741 
set_toplevel_expose_overlay(RobWidget * rw,void (* expose_event)(struct _robwidget *,cairo_t *,cairo_rectangle_t *))742 static void set_toplevel_expose_overlay(RobWidget *rw, void (*expose_event)(struct _robwidget*, cairo_t*, cairo_rectangle_t *)){
743 	GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
744 	self->expose_overlay = expose_event;
745 	rw->resized = TRUE; //full re-expose
746 	queue_draw (rw);
747 }
748 
robtk_expose_ui_scale(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)749 static void robtk_expose_ui_scale (RobWidget* handle, cairo_t* cr, cairo_rectangle_t *ev) {
750 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
751 	cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
752 	cairo_set_source_rgba (cr, 0, 0, 0, .6);
753 	cairo_fill (cr);
754 
755 	// ui-scale buttons
756 	const int nbtn_col = 4;
757 	const int nbtn_row = 2;
758 	float bt_w = ev->width / (float)(nbtn_col * 2 + 1);
759 	float bt_h = ev->height / (float)(nbtn_row * 2 + 1);
760 
761 	static const char scales[8][8] = { "100%", "110%", "115%", "120%", "125%", "150%", "175%", "200%" };
762 	PangoFontDescription *font = pango_font_description_from_string("Sans 24px");
763 
764 	write_text_full (cr, "GUI Scaling", font, floor(ev->width * .5), floor(bt_h * .5), 0, 2, c_wht);
765 
766 	pango_font_description_free (font);
767 	font = pango_font_description_from_string("Sans 14px");
768 
769 	for (int y = 0; y < nbtn_row; ++y) {
770 		for (int x = 0; x < nbtn_col; ++x) {
771 			float x0 = floor ((1 + 2 * x) * bt_w);
772 			float y0 = floor ((1 + 2 * y) * bt_h);
773 
774 			rounded_rectangle (cr, x0, y0, floor (bt_w), floor (bt_h), 8);
775 			CairoSetSouerceRGBA(c_wht);
776 			cairo_set_line_width(cr, 1.5);
777 			cairo_stroke_preserve (cr);
778 			cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
779 			cairo_fill (cr);
780 
781 			int pos = x + y * nbtn_col;
782 			write_text_full (cr, scales[pos], font, floor(x0 + bt_w * .5), floor(y0 + bt_h * .5), 0, 2, c_wht);
783 		}
784 	}
785 	pango_font_description_free (font);
786 }
787 
robtk_event_ui_scale(RobWidget * rw,RobTkBtnEvent * ev)788 static bool robtk_event_ui_scale (RobWidget* rw, RobTkBtnEvent *ev) {
789 	const int nbtn_col = 4;
790 	const int nbtn_row = 2;
791 	float bt_w = rw->area.width / (float)(nbtn_col * 2 + 1);
792 	float bt_h = rw->area.height / (float)(nbtn_row * 2 + 1);
793 	int xp = floor (ev->x / bt_w);
794 	int yp = floor (ev->y / bt_h);
795 	if ((xp & 1) == 0 || (yp & 1) == 0) {
796 		return FALSE;
797 	}
798 	const int pos = (xp - 1) / 2 + nbtn_col * (yp - 1) / 2;
799 	if (pos < 0 || pos >= nbtn_col * nbtn_row) {
800 		// possible rounding-error, bottom right corner
801 		return FALSE;
802 	}
803 
804 	static const float scales[8] = { 1.0, 1.1, 1.15, 1.20, 1.25, 1.50, 1.75, 2.0 };
805 	robtk_queue_scale_change (rw, scales [pos]);
806 	return TRUE;
807 }
808 
robtk_tl_mousedown(RobWidget * rw,RobTkBtnEvent * ev)809 static RobWidget* robtk_tl_mousedown (RobWidget* rw, RobTkBtnEvent *ev) {
810 	if (rw->block_events) {
811 		if (robtk_event_ui_scale (rw, ev)) {
812 			rw->block_events = FALSE;
813 			set_toplevel_expose_overlay (rw, NULL);
814 		}
815 		return NULL;
816 	}
817 
818 	RobWidget *rv = rcontainer_mousedown (rw, ev);
819 	if (rv) return rv;
820 	if (ev->button != 3) {
821 		return NULL;
822 	}
823 
824 	RobWidget * c = decend_into_widget_tree(rw, ev->x, ev->y);
825 	if (c && c->mousedown) return NULL;
826 
827 	rw->block_events = TRUE;
828 	set_toplevel_expose_overlay (rw, &robtk_expose_ui_scale);
829 	return NULL;
830 }
831 
robwidget_toplevel_enable_scaling(RobWidget * rw)832 static void robwidget_toplevel_enable_scaling (RobWidget* rw) {
833 	assert (rw->parent == rw);
834 	assert (rw->mousedown == &rcontainer_mousedown);
835 	robwidget_set_mousedown (rw, robtk_tl_mousedown);
836 }
837 
838 
839 /*****************************************************************************/
840 /* helper functions */
841 
microtime(float offset)842 static uint64_t microtime(float offset) {
843 	struct timespec now;
844 	rtk_clock_gettime(&now);
845 
846 	now.tv_nsec += 1000000000 * offset;
847 	while (now.tv_nsec >= 1000000000) {
848 		now.tv_nsec -= 1000000000;
849 		now.tv_sec += 1;
850 	}
851 	return now.tv_sec * 1000 + now.tv_nsec / 1000000;
852 }
853 
myusleep(uint32_t usec)854 static void myusleep(uint32_t usec) {
855 #ifdef _WIN32
856 	Sleep(usec / 1000);
857 #else
858 	struct timespec slp;
859 	slp.tv_sec = usec / 1000000;
860 	slp.tv_nsec = (usec % 1000000) * 1000;
861 	nanosleep(&slp, NULL);
862 #endif
863 }
864 
865 /*****************************************************************************/
866 
reallocate_canvas(GLrobtkLV2UI * self)867 static void reallocate_canvas(GLrobtkLV2UI* self) {
868 #ifdef DEBUG_RESIZE
869 	printf("reallocate_canvas to %d x %d\n", self->width, self->height);
870 #endif
871 	self->queue_canvas_realloc = false;
872 	if (self->cr) {
873 		free (self->surf_data);
874 #if __BIG_ENDIAN__
875 		free (self->surf_data_be);
876 #endif
877 		cairo_destroy (self->cr);
878 	}
879 	opengl_reallocate_texture(self->width, self->height, &self->texture_id);
880 	if (self->surface) {
881 		cairo_surface_destroy (self->surface);
882 		self->surface = NULL;
883 	}
884 	self->cr = opengl_create_cairo_t(self->width, self->height, &self->surface, &self->surf_data);
885 
886 #if __BIG_ENDIAN__
887 	self->surf_data_be = (unsigned char*) calloc (4 * self->width * self->height, sizeof (unsigned char));
888 #endif
889 
890 	/* clear top window */
891 	cairo_save(self->cr);
892 	cairo_set_source_rgba (self->cr, .0, .0, .0, 1.0);
893 	cairo_set_operator (self->cr, CAIRO_OPERATOR_SOURCE);
894 	cairo_rectangle (self->cr, 0, 0, self->width, self->height);
895 	cairo_fill (self->cr);
896 	cairo_restore(self->cr);
897 }
898 
899 static void
onRealReshape(PuglView * view,int width,int height)900 onRealReshape(PuglView* view, int width, int height)
901 {
902 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
903 	self->resize_in_progress = FALSE;
904 	self->resize_toplevel = FALSE;
905 #ifdef DEBUG_RESIZE
906 	printf("onRealReshape (%s) %dx%d\n",
907 			ROBWIDGET_NAME(self->tl), width, height);
908 #endif
909 	switch(plugin_scale_mode(self->ui)) {
910 		case LVGL_LAYOUT_TO_FIT:
911 			self->xoff = 0; self->yoff = 0; self->xyscale = 1.0;
912 #ifdef DEBUG_RESIZE
913 			printf("onRealReshape pre-layout %dx%d -> %dx%d\n",
914 					self->width, self->height, width, height);
915 #endif
916 			self->width = width;
917 			self->height = height;
918 			robwidget_layout(self, FALSE, FALSE);
919 			self->width = self->tl->area.width;
920 			self->height = self->tl->area.height;
921 #ifdef DEBUG_RESIZE
922 			printf("onRealReshape post-layout %dx%d\n",
923 					self->width, self->height);
924 #endif
925 			reallocate_canvas(self);
926 			// fall-thru to scale
927 		case LVGL_ZOOM_TO_ASPECT:
928 			if (self->queue_canvas_realloc) {
929 				reallocate_canvas(self);
930 			}
931 			rtoplevel_cache(self->tl, TRUE); // redraw background
932 			if (self->width == width && self->height == height) {
933 	self->xoff = 0; self->yoff = 0; self->xyscale = 1.0;
934 	glViewport (0, 0, self->width, self->height);
935 			} else
936 			{
937 	reallocate_canvas(self);
938 	const float gl_aspect = width / (float) height;
939 	const float cl_aspect = self->width / (float) self->height;
940 	if (gl_aspect > cl_aspect) {
941 		self->xyscale = (float) self->height / (float) height;
942 		self->xoff = (width - self->width / self->xyscale)/2;
943 		self->yoff = (height - self->height / self->xyscale)/2;
944 	} else {
945 		self->xoff = 0;
946 		self->xyscale = (float) self->width / (float) width;
947 		self->xoff = (width - self->width / self->xyscale)/2;
948 		self->yoff = (height - self->height / self->xyscale)/2;
949 	}
950 	glViewport (self->xoff, self->yoff, self->width / self->xyscale, self->height / self->xyscale);
951 			}
952 			break;
953 		case LVGL_CENTER:
954 			{
955 	self->xyscale = 1.0;
956 	self->xoff = (width - self->width)/2;
957 	self->yoff = (height - self->height)/2;
958 	glViewport (self->xoff, self->yoff, self->width, self->height);
959 
960 			}
961 			break;
962 		case LVGL_TOP_LEFT:
963 			{
964 	self->xoff = 0; self->yoff = 0; self->xyscale = 1.0;
965 	glViewport (0, (height - self->height), self->width, self->height);
966 			}
967 			break;
968 	}
969 
970 	glMatrixMode (GL_PROJECTION);
971 	glLoadIdentity ();
972 	glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
973 	queue_draw_full(self->tl);
974 }
975 
976 /*****************************************************************************/
977 /* puGL callbacks */
978 
onGlInit(PuglView * view)979 static void onGlInit (PuglView *view) {
980 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
981 #ifdef DEBUG_UI
982 	printf("OpenGL version: %s\n", glGetString (GL_VERSION));
983 	printf("OpenGL vendor: %s\n", glGetString (GL_VENDOR));
984 	printf("OpenGL renderer: %s\n", glGetString (GL_RENDERER));
985 #endif
986 	opengl_init();
987 	reallocate_canvas(self);
988 }
989 
onClose(PuglView * view)990 static void onClose(PuglView* view) {
991 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
992 	self->close_ui = TRUE;
993 }
994 
995 // callback from puGL -outsize GLX context(!) when we requested a resize
onResize(PuglView * view,int * width,int * height,int * set_hints)996 static void onResize(PuglView* view, int *width, int *height, int *set_hints) {
997 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
998 	assert(width && height);
999 #ifdef DEBUG_RESIZE
1000 	printf("onResize( %d x %d  -> %d x %d)\n", *width, *height, self->width, self->height);
1001 #endif
1002 
1003 	if (*width != self->width || *height != self->height) {
1004 		self->queue_canvas_realloc = true;
1005 	}
1006 
1007 	*width = self->width;
1008 	*height = self->height;
1009 	if (self->resize_toplevel) {
1010 		*set_hints = 0;
1011 	}
1012 
1013 	if (self->extui) { return ; } // all taken care of already
1014 	if (!self->resize) { return ; }
1015 
1016 #if (defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE)
1017 
1018 	// schedule resize in parent GUI's thread (idle interface)
1019 	self->do_the_funky_resize = TRUE;
1020 
1021 #elif (!defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE)
1022 
1023 	// we can do that here if effectively called by the idle callback of the host
1024 	self->resize->ui_resize(self->resize->handle, self->width, self->height);
1025 #ifdef USE_GTK_RESIZE_HACK
1026 	printf("GTK window hack: %d %d\n", self->width, self->height);
1027 	guint w = self->width;
1028 	guint h = self->height;
1029 	if (gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))) {
1030 		gtk_window_resize(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))), w, h);
1031 	}
1032 #endif
1033 
1034 #else
1035 	// crash ahead:
1036 	//self->resize->ui_resize(self->resize->handle, self->width, self->height);
1037 #endif
1038 }
1039 
1040 #ifdef TIMED_RESHAPE
doReshape(GLrobtkLV2UI * self)1041 static void doReshape(GLrobtkLV2UI *self) {
1042 	self->queue_reshape = 0;
1043 	onRealReshape(self->view, self->queue_w, self->queue_h);
1044 }
1045 #endif
1046 
onFileSelected(PuglView * view,const char * filename)1047 static void onFileSelected(PuglView* view, const char *filename) {
1048 #ifdef RTK_FILE_DIRECT_CALLBACK
1049 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1050 	RTK_FILE_DIRECT_CALLBACK(self->ui, filename);
1051 #else
1052 	// TODO create port event (using urid:map)
1053 #endif
1054 }
1055 
onFocusChanged(PuglView * view,bool enter)1056 static void onFocusChanged(PuglView* view, bool enter) {
1057 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1058 	if (self->tl->enter_notify && enter) {
1059 		self->tl->enter_notify (self->tl);
1060 	} else if (self->tl->leave_notify && !enter) {
1061 		self->tl->leave_notify (self->tl);
1062 	}
1063 }
1064 
onReshape(PuglView * view,int width,int height)1065 static void onReshape(PuglView* view, int width, int height) {
1066 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1067 	if (!self->gl_initialized) {
1068 		onGlInit(view);
1069 		self->gl_initialized = true;
1070 		onRealReshape(view, width, height);
1071 		return;
1072 	}
1073 
1074 #ifdef DEBUG_RESIZE
1075 	printf("onReshape - %d %d\n", width, height);
1076 #endif
1077 #ifdef TIMED_RESHAPE
1078 	if (self->resize_in_progress) {
1079 		self->queue_reshape = 0;
1080 		onRealReshape(view, width, height);
1081 	} else if (!self->queue_reshape) {
1082 		self->queue_reshape = microtime(.08);
1083 	}
1084 	self->queue_w = width;
1085 	self->queue_h = height;
1086 #else
1087 	onRealReshape(view, width, height);
1088 #endif
1089 }
1090 
onDisplay(PuglView * view)1091 static void onDisplay(PuglView* view) {
1092 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1093 	if (!self->gl_initialized) {
1094 		onGlInit(view);
1095 		self->gl_initialized = true;
1096 		onRealReshape(view, self->width, self->height);
1097 	}
1098 #ifdef TIMED_RESHAPE
1099 	if (self->queue_reshape) {
1100 		uint64_t now = microtime(0);
1101 		if (self->queue_reshape < now) {
1102 			doReshape(self);
1103 		}
1104 	}
1105 #endif
1106 
1107 #if 1
1108 	if (self->tl && self->queue_widget_scale != self->tl->widget_scale) {
1109 		self->tl->widget_scale = self->queue_widget_scale;
1110 		resize_self (self->tl);
1111 		robwidget_resize_toplevel (self->tl, self->tl->area.width, self->tl->area.height);
1112 	}
1113 #endif
1114 
1115 	if (self->resize_in_progress) { return; }
1116 	if (!self->cr) return; // XXX exit failure
1117 
1118 #ifndef TIMED_RESHAPE
1119 	if (self->relayout) {
1120 		self->relayout = FALSE;
1121 		onRealReshape(view, self->width, self->height);
1122 	}
1123 #endif
1124 
1125 #ifdef WITH_SIGNATURE
1126 	if (!self->gpg_verified) {
1127 		lc_expose(self);
1128 	} else {
1129 		cairo_expose(self);
1130 	}
1131 #else
1132 	cairo_expose(self);
1133 #endif
1134 	cairo_surface_flush(self->surface);
1135 #if __BIG_ENDIAN__
1136 	int x, y;
1137 	for (y = 0; y < self->height; ++y) {
1138 		for (x = 0; x < self->width; ++x) {
1139 			const int off = 4 * (y * self->width + x);
1140 			self->surf_data_be[off + 0] = self->surf_data[off + 3];
1141 			self->surf_data_be[off + 1] = self->surf_data[off + 2];
1142 			self->surf_data_be[off + 2] = self->surf_data[off + 1];
1143 			self->surf_data_be[off + 3] = self->surf_data[off + 0];
1144 		}
1145 	}
1146 	opengl_draw(self->width, self->height, self->surf_data_be, self->texture_id);
1147 #else
1148 	opengl_draw(self->width, self->height, self->surf_data, self->texture_id);
1149 #endif
1150 
1151 }
1152 
1153 #define GL_MOUSEBOUNDS \
1154 	x = (x - self->xoff) * self->xyscale ; \
1155 	y = (y - self->yoff) * self->xyscale ;
1156 
1157 #define GL_MOUSEEVENT \
1158 	RobTkBtnEvent event; \
1159 	event.x = x - self->tl->area.x; \
1160 	event.y = y - self->tl->area.y; \
1161 	event.state = puglGetModifiers(view); \
1162 	event.direction = ROBTK_SCROLL_ZERO; \
1163 	event.button = -1;
1164 
onMotion(PuglView * view,int x,int y)1165 static void onMotion(PuglView* view, int x, int y) {
1166 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1167 	assert(self->tl->mousemove);
1168 
1169 	GL_MOUSEBOUNDS;
1170 	GL_MOUSEEVENT;
1171 	//fprintf(stderr, "Motion: %d,%d\n", x, y);
1172 	if (self->mousefocus && self->mousefocus->mousemove) {
1173 		offset_traverse_parents(self->mousefocus, &event);
1174 		self->mousefocus = self->mousefocus->mousemove(self->mousefocus, &event);
1175 		// proagate ?
1176 	} else {
1177 		self->tl->mousemove(self->tl, &event);
1178 	}
1179 
1180 #if 1 // do not send enter/leave events when dragging
1181 	if (self->mousefocus || self->tl->block_events) return;
1182 #endif
1183 
1184 	RobWidget *fc = decend_into_widget_tree(self->tl, x, y);
1185 	if (self->mousehover && fc != self->mousehover && self->mousehover->leave_notify) {
1186 		self->mousehover->leave_notify(self->mousehover);
1187 	}
1188 	if (fc && fc != self->mousehover && fc->enter_notify) {
1189 		fc->enter_notify(fc);
1190 	}
1191 	if (fc && fc->leave_notify) {
1192 		self->mousehover = fc;
1193 	} else {
1194 		self->mousehover = NULL;
1195 	}
1196 }
1197 
onMouse(PuglView * view,int button,bool press,int x,int y)1198 static void onMouse(PuglView* view, int button, bool press, int x, int y) {
1199 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1200 #ifdef WITH_SIGNATURE
1201 	if (!self->gpg_verified) {
1202 		if (press) {
1203 			if (self->gpg_shade < .3) self->gpg_shade = .3;
1204 			else if (self->gpg_shade < .5) self->gpg_shade += .05;
1205 			puglPostRedisplay(self->view);
1206 		} else {
1207 			rtk_open_url (RTK_URI);
1208 		}
1209 		return;
1210 	}
1211 #endif
1212 	//fprintf(stderr, "Mouse %d %s at %d,%d\n", button, press ? "down" : "up", x, y);
1213 
1214 	GL_MOUSEBOUNDS;
1215 	GL_MOUSEEVENT;
1216 	event.button = button;
1217 
1218 	if (!press) {
1219 		if (self->tl->mouseup) {
1220 			if (self->mousefocus && self->mousefocus->mouseup) {
1221 				offset_traverse_parents(self->mousefocus, &event);
1222 				self->mousefocus = self->mousefocus->mouseup(self->mousefocus, &event);
1223 			} else {
1224 				self->mousefocus = self->tl->mouseup(self->tl, &event);
1225 			}
1226 		}
1227 	} else {
1228 		if (x > self->tl->area.x + self->tl->area.width) return;
1229 		if (y > self->tl->area.y + self->tl->area.height) return;
1230 		if (x < self->tl->area.x) return;
1231 		if (y < self->tl->area.y) return;
1232 		if (self->tl->mousedown) {
1233 			self->mousefocus = self->tl->mousedown(self->tl, &event);
1234 		}
1235 	}
1236 }
1237 
onScroll(PuglView * view,int x,int y,float dx,float dy)1238 static void onScroll(PuglView* view, int x, int y, float dx, float dy) {
1239 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view);
1240 #ifdef WITH_SIGNATURE
1241 	if (!self->gpg_verified) return;
1242 #endif
1243 	self->mousefocus = NULL; // CHECK
1244 	GL_MOUSEBOUNDS;
1245 	GL_MOUSEEVENT;
1246 	if (dx < 0)      event.direction = ROBTK_SCROLL_LEFT;
1247 	else if (dx > 0) event.direction = ROBTK_SCROLL_RIGHT;
1248 	else if (dy < 0) event.direction = ROBTK_SCROLL_DOWN;
1249 	else if (dy > 0) event.direction = ROBTK_SCROLL_UP;
1250 	//fprintf(stderr, "Scroll @%d+%d  %f %f\n", x, y, dx, dy);
1251 	if (self->tl->mousescroll) self->tl->mousescroll(self->tl, & event);
1252 }
1253 
1254 
1255 /******************************************************************************
1256  * LV2 init/operation
1257  */
1258 
pugl_init(GLrobtkLV2UI * self)1259 static int pugl_init(GLrobtkLV2UI* self) {
1260 	int dflw = self->width;
1261 	int dflh = self->height;
1262 
1263 	if (self->tl->size_default) {
1264 		self->tl->size_default(self->tl, &dflw, &dflh);
1265 	}
1266 
1267 	// Set up GL UI
1268 	self->view = puglCreate(
1269 			self->extui ? (PuglNativeWindow) NULL : self->parent,
1270 			self->extui ? self->extui->plugin_human_id : RTK_URI,
1271 			self->width, self->height,
1272 			dflw, dflh,
1273 #ifdef LVGL_RESIZEABLE
1274 			true
1275 #else
1276 			false /* self->extui ? true : false */
1277 #endif
1278 			, self->ontop
1279 			, self->transient_id
1280 	);
1281 	if (!self->view) {
1282 		return -1;
1283 	}
1284 
1285 	puglSetHandle(self->view, self);
1286 	puglSetDisplayFunc(self->view, onDisplay);
1287 	puglSetReshapeFunc(self->view, onReshape);
1288 	puglSetResizeFunc(self->view, onResize);
1289 	puglSetFileSelectedFunc(self->view, onFileSelected);
1290 
1291 	if (self->tl->enter_notify || self->tl->leave_notify) {
1292 		puglSetFocusFunc(self->view, onFocusChanged);
1293 	}
1294 
1295 	if (self->extui) {
1296 		puglSetCloseFunc(self->view, onClose);
1297 		self->ui_closed = self->extui->ui_closed;
1298 		self->resize = NULL;
1299 	}
1300 	if (self->tl->mousemove) {
1301 		puglSetMotionFunc(self->view, onMotion);
1302 	}
1303 	if (self->tl->mousedown || self->tl->mouseup) {
1304 		puglSetMouseFunc(self->view, onMouse);
1305 	}
1306 	if (self->tl->mousescroll) {
1307 		puglSetScrollFunc(self->view, onScroll);
1308 	}
1309 
1310 #ifndef XTERNAL_UI // TODO set minimum size during init in app's thread
1311 	if (self->resize) { // XXX thread issue
1312 		self->resize->ui_resize(self->resize->handle, self->width, self->height);
1313 	}
1314 #endif
1315 
1316 	if (self->tl->size_default) {
1317 		self->tl->size_default(self->tl, &self->width, &self->height);
1318 		self->resize = NULL;
1319 	}
1320 #ifndef USE_GUI_THREAD
1321 	ui_enable (self->ui);
1322 #endif
1323 	return 0;
1324 }
1325 
pugl_cleanup(GLrobtkLV2UI * self)1326 static void pugl_cleanup(GLrobtkLV2UI* self) {
1327 #ifndef USE_GUI_THREAD
1328 	ui_disable (self->ui);
1329 #endif
1330 	glDeleteTextures (1, &self->texture_id); // XXX does his need glxContext ?!
1331 	free (self->surf_data);
1332 #if __BIG_ENDIAN__
1333 	free (self->surf_data_be);
1334 #endif
1335 	cairo_destroy (self->cr);
1336 	puglDestroy(self->view);
1337 }
1338 
1339 /////
1340 
1341 /* main-thread to be called at regular intervals */
process_gui_events(LV2UI_Handle handle)1342 static int process_gui_events(LV2UI_Handle handle) {
1343 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle;
1344 	puglProcessEvents(self->view);
1345 	if (!self->gl_initialized) {
1346 		puglPostRedisplay(self->view);
1347 	}
1348 #ifdef TIMED_RESHAPE
1349 	if (self->queue_reshape > 0) {
1350 		puglPostRedisplay(self->view);
1351 	}
1352 #endif
1353 	return 0;
1354 }
1355 
1356 #ifdef USE_GUI_THREAD
1357  // we can use idle to call 'ui_resize' in parent's thread context
idle(LV2UI_Handle handle)1358 static int idle(LV2UI_Handle handle) {
1359 #ifdef HAVE_IDLE_IFACE
1360 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle;
1361 
1362 	if (self->do_the_funky_resize && self->resize) {
1363 		self->resize->ui_resize(self->resize->handle, self->width, self->height);
1364 #ifdef USE_GTK_RESIZE_HACK
1365 		printf("GTK window hack (idle): %d %d\n", self->width, self->height);
1366 		guint w = self->width;
1367 		guint h = self->height;
1368 		if (gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))) {
1369 			gtk_window_resize(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))), w, h);
1370 		}
1371 #endif
1372 		self->do_the_funky_resize = FALSE;
1373 	}
1374 
1375 #endif
1376 	return 0;
1377 }
1378 
ui_thread(void * handle)1379 static void* ui_thread(void* handle) {
1380 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle;
1381 #ifdef THREADSYNC
1382 	pthread_mutex_lock (&self->msg_thread_lock);
1383 #endif
1384 #ifdef INIT_PUGL_IN_THREAD
1385 	if (pugl_init(self)) {
1386 		self->ui_initialized = -1;
1387 	}
1388 	self->ui_initialized = 1;
1389 #endif
1390 
1391 	while (!self->exit) {
1392 		if (self->ui_queue_puglXWindow > 0) {
1393 			puglShowWindow(self->view);
1394 			ui_enable(self->ui);
1395 			self->ui_queue_puglXWindow = 0;
1396 		}
1397 		process_gui_events(self);
1398 		if (self->ui_queue_puglXWindow < 0) {
1399 			ui_disable(self->ui);
1400 			puglHideWindow(self->view);
1401 			self->ui_queue_puglXWindow = 0;
1402 		}
1403 #ifdef THREADSYNC
1404 		//myusleep(1000000 / 60); // max FPS
1405 		struct timespec now;
1406 		rtk_clock_systime(&now);
1407 		now.tv_nsec += 1000000000 / 25; // min FPS
1408 		if (now.tv_nsec >= 1000000000) {
1409 			now.tv_nsec -= 1000000000;
1410 			now.tv_sec += 1;
1411 		}
1412 		assert(now.tv_nsec >= 0 && now.tv_nsec < 1000000000);
1413 		pthread_cond_timedwait (&self->data_ready, &self->msg_thread_lock, &now);
1414 #else
1415 		myusleep(1000000 / 50); // FPS
1416 #endif
1417 	}
1418 #ifdef THREADSYNC
1419 	pthread_mutex_unlock (&self->msg_thread_lock);
1420 #endif
1421 #ifdef INIT_PUGL_IN_THREAD
1422 	pugl_cleanup(self);
1423 #endif
1424 	return NULL;
1425 }
1426 #endif
1427 
1428 
1429 /******************************************************************************
1430  * LV2 callbacks
1431  */
1432 
1433 static LV2UI_Handle
gl_instantiate(const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,LV2UI_Widget * widget,const LV2_Feature * const * features)1434 gl_instantiate(const LV2UI_Descriptor*   descriptor,
1435                const char*               plugin_uri,
1436                const char*               bundle_path,
1437                LV2UI_Write_Function      write_function,
1438                LV2UI_Controller          controller,
1439                LV2UI_Widget*             widget,
1440                const LV2_Feature* const* features)
1441 {
1442 #ifdef DEBUG_UI
1443 	printf("gl_instantiate: %s\n", plugin_uri);
1444 #endif
1445 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)calloc(1, sizeof(GLrobtkLV2UI));
1446 	if (!self) {
1447 		fprintf (stderr, "robtk: out of memory.\n");
1448 		return NULL;
1449 	}
1450 
1451 	self->write      = write_function;
1452 	self->controller = controller;
1453 	self->view       = NULL;
1454 	self->extui      = NULL;
1455 	self->parent     = 0;
1456 #ifdef DEFAULT_NOT_ONTOP
1457 	self->ontop      = false;
1458 #else
1459 	self->ontop      = true;
1460 #endif
1461 	self->transient_id = 0;
1462 	self->queue_widget_scale = 1.0;
1463 	self->queue_canvas_realloc = false;
1464 
1465 #if (defined USE_GUI_THREAD && defined THREADSYNC)
1466 	pthread_mutex_init(&self->msg_thread_lock, NULL);
1467 	pthread_cond_init(&self->data_ready, NULL);
1468 #endif
1469 
1470 	const LV2_Options_Option* options = NULL;
1471 	LV2_URID_Map*             map     = NULL;
1472 
1473 	for (int i = 0; features && features[i]; ++i) {
1474 		if (!strcmp(features[i]->URI, LV2_UI__parent)) {
1475 			self->parent = (PuglNativeWindow)features[i]->data;
1476 		} else if (!strcmp(features[i]->URI, LV2_UI__resize)) {
1477 			self->resize = (LV2UI_Resize*)features[i]->data;
1478 		} else if (!strcmp(features[i]->URI, LV2_URID__map)) {
1479 			map = (LV2_URID_Map*)features[i]->data;
1480 		} else if (!strcmp(features[i]->URI, LV2_OPTIONS__options)) {
1481 			options = (LV2_Options_Option*)features[i]->data;
1482 		}
1483 #ifdef XTERNAL_UI
1484 		else if (!strcmp(features[i]->URI, LV2_EXTERNAL_UI_URI) && !self->extui) {
1485 			self->extui = (struct lv2_external_ui_host*) features[i]->data;
1486 		}
1487 		else if (!strcmp(features[i]->URI, LV2_EXTERNAL_UI_URI__KX__Host)) {
1488 			self->extui = (struct lv2_external_ui_host*) features[i]->data;
1489 #ifdef DEBUG_UI
1490 		} else {
1491 			printf("Feature: '%s'\n", features[i]->URI);
1492 #endif
1493 		}
1494 #endif
1495 	}
1496 
1497 	if (options && map) {
1498 		LV2_URID atom_Long = map->map(map->handle, LV2_ATOM__Long);
1499 		LV2_URID transient_for = map->map (map->handle, "http://kxstudio.sf.net/ns/lv2ext/props#TransientWindowId");
1500 #ifdef RTK_USE_HOST_COLORS
1501 		LV2_URID atom_Int = map->map(map->handle, LV2_ATOM__Int);
1502 		LV2_URID color_fg = map->map (map->handle, "http://lv2plug.in/ns/extensions/ui#foregroundColor");
1503 		LV2_URID color_bg = map->map (map->handle, "http://lv2plug.in/ns/extensions/ui#backgroundColor");
1504 #endif
1505 
1506 		for (const LV2_Options_Option* o = options; o->key; ++o) {
1507 			if (o->context == LV2_OPTIONS_INSTANCE && o->key == transient_for && o->type == atom_Long) {
1508 				self->transient_id = *(const unsigned long*)o->value;
1509 			}
1510 #ifdef RTK_USE_HOST_COLORS
1511 			// TODO: consider adding a host bg colored border when the plugin does not use RTK_USE_HOST_COLORS
1512 			else if (o->context == LV2_OPTIONS_INSTANCE && o->key == color_fg && o->type == atom_Int) {
1513 				set_host_color (0, *(const uint32_t*)o->value);
1514 			} else if (o->context == LV2_OPTIONS_INSTANCE && o->key == color_bg && o->type == atom_Int) {
1515 				set_host_color (1, *(const uint32_t*)o->value);
1516 			}
1517 #endif
1518 		}
1519 	}
1520 
1521 	if (self->transient_id != 0) {
1522 		self->ontop = false;
1523 	}
1524 
1525 	if (getenv("X42_ON_TOP")) {
1526 		self->ontop = 0 != atoi (getenv("X42_ON_TOP"));
1527 	}
1528 
1529 	if (!self->parent && !self->extui) {
1530 		fprintf(stderr, "error: No parent window provided.\n");
1531 		free(self);
1532 		return NULL;
1533 	}
1534 
1535 	self->ui_closed = NULL;
1536 	self->close_ui = FALSE;
1537 	self->rb = posrb_alloc(sizeof(RWArea) * 48); // depends on plugin and threading stategy
1538 
1539 #ifdef WITH_SIGNATURE
1540 	self->gpg_shade = 0;
1541 # include "gpg_check.c"
1542 #endif
1543 
1544 	self->tl = NULL;
1545 	self->ui = instantiate(self,
1546 			descriptor, plugin_uri, bundle_path,
1547 			write_function, controller, &self->tl, features);
1548 
1549 	if (!self->ui) {
1550 		posrb_free(self->rb);
1551 		free(self);
1552 		return NULL;
1553 	}
1554 	if (!self->tl || !self->tl->expose_event || !self->tl->size_request) {
1555 		posrb_free(self->rb);
1556 		free(self);
1557 		return NULL;
1558 	}
1559 
1560 	robwidget_layout(self, TRUE, TRUE);
1561 
1562 	assert(self->width > 0 && self->height > 0);
1563 
1564 	self->cr = NULL;
1565 	self->surface= NULL;
1566 	self->surf_data = NULL; // ditto
1567 #if __BIG_ENDIAN__
1568 	self->surf_data_be = NULL;
1569 #endif
1570 	self->texture_id = 0; // already too much of this to keep valgrind happy
1571 	self->xoff = self->yoff = 0; self->xyscale = 1.0;
1572 	self->gl_initialized   = 0;
1573 	self->expose_area.x = 0;
1574 	self->expose_area.y = 0;
1575 	self->expose_area.width = self->width;
1576 	self->expose_area.height = self->height;
1577 	self->mousefocus = NULL;
1578 	self->mousehover = NULL;
1579 	self->resize_in_progress = FALSE;
1580 	self->resize_toplevel = FALSE;
1581 
1582 #ifdef INIT_PUGL_IN_THREAD
1583 	self->ui_initialized   = 0;
1584 #endif
1585 #ifdef TIMED_RESHAPE
1586 	self->queue_reshape = 0;
1587 	self->queue_w = 0;
1588 	self->queue_h = 0;
1589 #else
1590 	self->relayout = FALSE;
1591 #endif
1592 
1593 #if (defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE)
1594 	self->do_the_funky_resize = FALSE;
1595 #endif
1596 
1597 #if (!defined USE_GUI_THREAD) || (!defined INIT_PUGL_IN_THREAD)
1598 	if (pugl_init(self)) {
1599 		return NULL;
1600 	}
1601 #endif
1602 
1603 #ifdef USE_GUI_THREAD
1604 	self->ui_queue_puglXWindow = 0;
1605 	self->exit = false;
1606 	pthread_create(&self->thread, NULL, ui_thread, self);
1607 #ifdef INIT_PUGL_IN_THREAD
1608 	while (!self->ui_initialized) {
1609 		myusleep(1000);
1610 		sched_yield();
1611 	}
1612 	if (self->ui_initialized < 0) {
1613 		self->exit = true;
1614 		pthread_join(self->thread, NULL);
1615 		return NULL;
1616 	}
1617 #endif
1618 #endif
1619 
1620 
1621 #ifdef XTERNAL_UI
1622 	if (self->extui) {
1623 #ifdef DEBUG_UI
1624 		printf("robtk: Xternal UI\n");
1625 #endif
1626 		self->xternal_ui.run  = &x_run;
1627 		self->xternal_ui.show = &x_show;
1628 		self->xternal_ui.hide = &x_hide;
1629 		self->xternal_ui.self = (void*) self;
1630 		*widget = (void*) &self->xternal_ui;
1631 	} else
1632 #endif
1633 	{
1634 #ifdef DEBUG_UI
1635 		printf("robtk: Interal UI\n");
1636 #endif
1637 		*widget = (void*)puglGetNativeWindow(self->view);
1638 	}
1639 
1640 	return self;
1641 }
1642 
gl_cleanup(LV2UI_Handle handle)1643 static void gl_cleanup(LV2UI_Handle handle) {
1644 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle;
1645 #ifdef USE_GUI_THREAD
1646 	self->exit = true;
1647 	pthread_join(self->thread, NULL);
1648 #endif
1649 #if (!defined USE_GUI_THREAD) || (!defined INIT_PUGL_IN_THREAD)
1650 	pugl_cleanup(self);
1651 #endif
1652 #if (defined USE_GUI_THREAD && defined THREADSYNC)
1653 	pthread_mutex_destroy(&self->msg_thread_lock);
1654 	pthread_cond_destroy(&self->data_ready);
1655 #endif
1656 	if (self->surface) {
1657 		cairo_surface_destroy (self->surface);
1658 		self->surface = NULL;
1659 	}
1660 	cleanup(self->ui);
1661 	posrb_free(self->rb);
1662 	free(self);
1663 }
1664 
1665 static void
gl_port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)1666 gl_port_event(LV2UI_Handle handle,
1667            uint32_t     port_index,
1668            uint32_t     buffer_size,
1669            uint32_t     format,
1670            const void*  buffer)
1671 {
1672 	/* these arrive in GUI context of the parent thread
1673 	 * -- could do some trickery here, too
1674 	 */
1675 	GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle;
1676 	port_event(self->ui, port_index, buffer_size, format, buffer);
1677 #if (defined USE_GUI_THREAD && defined THREADSYNC)
1678 	if (pthread_mutex_trylock (&self->msg_thread_lock) == 0) {
1679 		pthread_cond_signal (&self->data_ready);
1680 		pthread_mutex_unlock (&self->msg_thread_lock);
1681 	}
1682 #endif
1683 }
1684 
1685 
1686 /******************************************************************************
1687  * LV2 setup
1688  */
1689 
1690 #ifdef HAVE_IDLE_IFACE
1691 #ifdef USE_GUI_THREAD
1692 static const LV2UI_Idle_Interface idle_iface = { idle };
1693 #else
1694 static const LV2UI_Idle_Interface idle_iface = { process_gui_events };
1695 #endif
1696 #endif
1697 
1698 static const void*
gl_extension_data(const char * uri)1699 gl_extension_data(const char* uri)
1700 {
1701 #ifdef HAVE_IDLE_IFACE
1702 	if (!strcmp(uri, LV2_UI__idleInterface)) {
1703 		return &idle_iface;
1704 	} else
1705 #endif
1706 	return extension_data(uri);
1707 }
1708 
1709 static const LV2UI_Descriptor gl_descriptor = {
1710 	RTK_URI RTK_GUI "_gl",
1711 	gl_instantiate,
1712 	gl_cleanup,
1713 	gl_port_event,
1714 	gl_extension_data
1715 };
1716 
1717 #ifdef RTK_DESCRIPTOR // standalone lv2
1718 
1719 const LV2UI_Descriptor*
RTK_DESCRIPTOR(uint32_t index)1720 RTK_DESCRIPTOR(uint32_t index) {
1721 	return &gl_descriptor;
1722 }
1723 
1724 #else
1725 
1726 #undef LV2_SYMBOL_EXPORT
1727 #ifdef _WIN32
1728 #    define LV2_SYMBOL_EXPORT __declspec(dllexport)
1729 #else
1730 #    define LV2_SYMBOL_EXPORT  __attribute__ ((visibility ("default")))
1731 #endif
1732 LV2_SYMBOL_EXPORT
1733 const LV2UI_Descriptor*
lv2ui_descriptor(uint32_t index)1734 lv2ui_descriptor(uint32_t index)
1735 {
1736 #if (defined _WIN32 && defined RTK_STATIC_INIT)
1737 	static int once = 0;
1738 	if (!once) {once = 1; gobject_init_ctor();}
1739 #endif
1740 	switch (index) {
1741 	case 0:
1742 		return &gl_descriptor;
1743 	default:
1744 		return NULL;
1745 	}
1746 }
1747 
1748 #if (defined _WIN32 && defined RTK_STATIC_INIT)
x42_init()1749 static void __attribute__((constructor)) x42_init() {
1750 	pthread_win32_process_attach_np();
1751 }
1752 
x42_fini()1753 static void __attribute__((destructor)) x42_fini() {
1754 	pthread_win32_process_detach_np();
1755 }
1756 #endif
1757 
1758 #endif
1759