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