1 /*
2  * Copyright (C) 2018-2020 Oleg Kapitonov
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  * --------------------------------------------------------------------------
18  */
19 
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <libgen.h>
23 #include <limits.h>
24 #include <math.h>
25 #include <stdint.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "lv2/lv2plug.in/ns/lv2core/lv2.h"
30 #include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
31 #include <lv2/lv2plug.in/ns/ext/urid/urid.h>
32 #include "lv2/lv2plug.in/ns/ext/atom/forge.h"
33 #include "lv2/lv2plug.in/ns/ext/patch/patch.h"
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 
38 #include <cairo.h>
39 #include <cairo-xcb.h>
40 
41 #include <xcb/xcb.h>
42 #include <xcb/xcb_aux.h>
43 #include <xcb/xcb_icccm.h>
44 
45 #include "kpp_file_picker.h"
46 
47 // Port numbers for communication with the main LV2 plugin module
48 enum {PORT_BASS, PORT_MIDDLE, PORT_TREBLE, PORT_DRIVE, PORT_MASTERGAIN, PORT_VOLUME,
49   PORT_CABINET, PORT_IN0, PORT_IN1, PORT_OUT0, PORT_OUT1, PORT_CONTROL, PORT_NOTIFY};
50 
51 // Dial GUI element structure
52 typedef struct
53 {
54   int value; // 0 - 100
55   int start_value; // Holds value at the start of dialing
56   int base_x; // Coordinates of Dial in main window
57   int base_y; //
58 } st_dial;
59 
60 // Slider GUI element structure
61 typedef struct
62 {
63   int value; // 0 - 100
64   int start_value; // Holds value at the start of sliding
65   int base_x; // Coordinates of Slider in main window
66   int base_y; //
67 } st_slider;
68 
69 // Point
70 typedef struct
71 {
72   int x;
73   int y;
74 }st_point;
75 
76 // Color
77 typedef struct
78 {
79   double r;
80   double g;
81   double b;
82 }st_rgb;
83 
84 typedef struct {
85   LV2_URID atom_eventTransfer;
86   LV2_URID patch_Get;
87   LV2_URID patch_Set;
88   LV2_URID profile;
89   LV2_URID patch_property;
90   LV2_URID patch_value;
91   LV2_URID atom_Path;
92 } tubeAmpURIs;
93 
94 // GUI window structure
95 typedef struct
96 {
97   xcb_connection_t *connection;
98 
99   xcb_window_t win;
100 
101   int width, height;
102 
103   void *controller;
104 
105   LV2UI_Write_Function write_function;
106   LV2UI_Resize* resize;
107   LV2_URID_Map* map;
108 
109   LV2_Atom_Forge forge;  // Forge for writing atoms in run thread
110 
111   uint8_t forge_buf[4096];
112 
113   tubeAmpURIs uris;
114 
115   // Main window
116   //win_t win;
117 
118   // Dials
119   st_dial driveDial;
120 
121   st_dial bassDial;
122   st_dial middleDial;
123   st_dial trebleDial;
124 
125   st_dial mastergainDial;
126 
127   // Sliders
128 
129   st_slider levelSlider;
130   st_slider cabinetSlider;
131 
132   // Hold mouse coordinates at the start of dialing
133   int pos_x;
134   int pos_y;
135 
136   // Cairo objects
137   cairo_t *cr;
138   cairo_surface_t *surface;
139   xcb_visualtype_t *visual;
140 
141   cairo_surface_t *image, *image2;
142   cairo_device_t *device;
143 
144   // Port number (from enum) of the Dial, which is now
145   // adjusted by the user
146   // -1 means that no dial is adjusted
147   int active_dial;
148 
149   int profile_select_fd;
150 
151   char profile_path[PATH_MAX];
152   char new_profile_path[PATH_MAX];
153   size_t new_profile_path_len;
154 
155   KPPFilePicker::Win *fp_win;
156 
157 } win_t;
158 
159 // Create main window
win_init(win_t * win,xcb_screen_t * screen,xcb_window_t parentXwindow)160 static void win_init(win_t *win, xcb_screen_t *screen,
161     xcb_window_t parentXwindow)
162 {
163   win->fp_win = NULL;
164   win->width = 1000;
165   win->height = 376;
166 
167   win->win = xcb_generate_id(win->connection);
168 
169   uint32_t mask = XCB_CW_EVENT_MASK;
170   uint32_t mask_values[1] = {
171     XCB_EVENT_MASK_STRUCTURE_NOTIFY |
172     XCB_EVENT_MASK_EXPOSURE |
173     XCB_EVENT_MASK_BUTTON_PRESS |
174     XCB_EVENT_MASK_BUTTON_1_MOTION };
175 
176   xcb_create_window(win->connection, XCB_COPY_FROM_PARENT,
177                     win->win, parentXwindow,
178                     0, 0, win->width, win->height, 0, XCB_WINDOW_CLASS_COPY_FROM_PARENT,
179                     XCB_COPY_FROM_PARENT, mask, mask_values);
180 
181 
182   xcb_size_hints_t size_hints;
183   memset(&size_hints, 0, sizeof(size_hints));
184   xcb_icccm_size_hints_set_size(&size_hints, 1, win->width, win->height);
185   xcb_icccm_size_hints_set_min_size(&size_hints, win->width, win->height);
186   xcb_icccm_size_hints_set_max_size(&size_hints, win->width, win->height);
187 
188   xcb_icccm_set_wm_normal_hints(win->connection, win->win, &size_hints);
189 
190   xcb_map_window(win->connection, win->win);
191   xcb_flush(win->connection);
192 }
193 
194 // LV2 initialization function
195 static LV2UI_Handle
instantiate(const struct _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)196 instantiate(const struct _LV2UI_Descriptor * descriptor,
197             const char * plugin_uri,
198             const char * bundle_path,
199             LV2UI_Write_Function write_function,
200             LV2UI_Controller controller,
201             LV2UI_Widget * widget,
202             const LV2_Feature * const * features)
203 {
204   // Quit if GUI module loaded with wrong main module
205   if (strcmp(plugin_uri, PLUGIN_URI))
206   {
207     fprintf(stderr,
208       "%s: ERROR: this GUI does not support plugin with URI %s\n",
209       PLUGIN_URI, plugin_uri);
210     return NULL;
211   }
212 
213   win_t *win = (win_t *) malloc(sizeof(win_t));
214 
215   win->active_dial = -1;
216 
217   win->profile_select_fd = -1;
218 
219   win->driveDial.value = 0;
220   win->driveDial.start_value = 0;
221   win->driveDial.base_x = 215;
222   win->driveDial.base_y = 235;
223 
224   win->bassDial.value = 0;
225   win->bassDial.start_value = 0;
226   win->bassDial.base_x = 377;
227   win->bassDial.base_y = 235;
228 
229   win->middleDial.value = 0;
230   win->middleDial.start_value = 0;
231   win->middleDial.base_x = 479;
232   win->middleDial.base_y = 235;
233 
234   win->trebleDial.value = 0;
235   win->trebleDial.start_value = 0;
236   win->trebleDial.base_x = 583;
237   win->trebleDial.base_y = 235;
238 
239   win->mastergainDial.value = 0;
240   win->mastergainDial.start_value = 0;
241   win->mastergainDial.base_x = 808;
242   win->mastergainDial.base_y = 235;
243 
244   win->levelSlider.value = 0;
245   win->levelSlider.start_value = 0;
246   win->levelSlider.base_x = 511;
247   win->levelSlider.base_y = 354;
248 
249   win->cabinetSlider.value = 0;
250   win->cabinetSlider.start_value = 0;
251   win->cabinetSlider.base_x = 812;
252   win->cabinetSlider.base_y = 354;
253 
254   win->profile_path[0] = '\0';
255 
256   // X11 handle of host window, in which plugin GUI
257   // window must be embedded
258   void * parentXwindow = 0;
259 
260   // Resize function provided by LV2 host
261   LV2UI_Resize* resize = NULL;
262 
263   // Obtain this pointers
264   for (int i = 0; features[i]; ++i)
265   {
266     if (!strcmp(features[i]->URI, LV2_UI__parent))
267     {
268       parentXwindow = features[i]->data;
269     }
270     else if (!strcmp(features[i]->URI, LV2_UI__resize))
271     {
272       resize = (LV2UI_Resize*)features[i]->data;
273     }
274     if (!strcmp(features[i]->URI, LV2_URID__map))
275     {
276       win->map = (LV2_URID_Map*)features[i]->data;
277     }
278   }
279 
280   win->uris.atom_eventTransfer = win->map->map(win->map->handle, LV2_ATOM__eventTransfer);
281   win->uris.patch_Get = win->map->map(win->map->handle, LV2_PATCH__Get);
282   win->uris.patch_Set = win->map->map(win->map->handle, LV2_PATCH__Set);
283   win->uris.profile = win->map->map(win->map->handle, "https://faustlv2.bitbucket.io/kpp_tubeamp#profile");
284   win->uris.patch_property = win->map->map(win->map->handle, LV2_PATCH__property);
285   win->uris.patch_value = win->map->map(win->map->handle, LV2_PATCH__value);
286   win->uris.atom_Path = win->map->map(win->map->handle, LV2_ATOM__Path);
287 
288   lv2_atom_forge_init(&win->forge, win->map);
289 
290   win->connection = xcb_connect(NULL, NULL);
291 
292   if (win->connection == NULL)
293   {
294     fprintf(stderr, "Failed to open display\n");
295     return NULL;
296   }
297 
298   xcb_screen_t *screen =
299     xcb_setup_roots_iterator(xcb_get_setup(win->connection)).data;
300 
301   win_init(win, screen, (xcb_window_t) (size_t) parentXwindow);
302 
303   win->visual = xcb_aux_find_visual_by_id(screen, screen->root_visual);
304   xcb_clear_area(win->connection, 0, win->win, 0, 0, 0, 0);
305   win->surface = cairo_xcb_surface_create(win->connection, win->win, win->visual,
306                                         win->width, win->height);
307 
308   win->device = cairo_device_reference(cairo_surface_get_device(win->surface));
309 
310   win->cr = cairo_create(win->surface);
311 
312   char image_path[PATH_MAX];
313 
314   // Load background image
315   snprintf(image_path, sizeof(image_path), "%s/base_scale.png", bundle_path);
316   image_path[sizeof(image_path) - 1] = '\0';
317   win->image = cairo_image_surface_create_from_png (image_path);
318 
319   // Load dial point image
320   snprintf(image_path, sizeof(image_path), "%s/light.png", bundle_path);
321   image_path[sizeof(image_path) - 1] = '\0';
322   win->image2 = cairo_image_surface_create_from_png (image_path);
323 
324   // Return to host the X11 handle of created window
325   //*widget = (void*) (size_t) win->win;
326   *(uintptr_t *)widget = (uintptr_t)win->win;
327 
328   if (resize)
329   {
330     win->resize = resize;
331     // Ask the host to properly resize the plugin host window
332     resize->ui_resize(resize->handle, win->width, win->height);
333   }
334 
335   win->controller = controller;
336   win->write_function = write_function;
337 
338 
339   lv2_atom_forge_set_buffer(&win->forge, win->forge_buf, sizeof(win->forge_buf));
340   LV2_Atom_Forge_Frame frame;
341   LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&win->forge,
342                                                    &frame,
343                                                    0,
344                                                    win->uris.patch_Get);
345   lv2_atom_forge_pop(&win->forge, &frame);
346 
347   win->write_function(win->controller, PORT_CONTROL, lv2_atom_total_size(msg),
348                       win->uris.atom_eventTransfer,
349                       msg);
350 
351   return (LV2UI_Handle)win;
352 }
353 
win_deinit(win_t * win)354 static void win_deinit(win_t *win)
355 {
356   if (win->fp_win != NULL)
357   {
358     delete win->fp_win;
359   }
360   xcb_destroy_window(win->connection, win->win);
361 }
362 
363 static void
cleanup(LV2UI_Handle ui)364 cleanup(LV2UI_Handle ui)
365 {
366   win_t *win = (win_t *)ui;
367   win_deinit(win);
368   cairo_destroy(win->cr);
369   cairo_surface_destroy(win->surface);
370   cairo_surface_destroy(win->image);
371   cairo_surface_destroy(win->image2);
372   cairo_device_finish(win->device);
373   cairo_device_destroy(win->device);
374   xcb_disconnect(win->connection);
375   free(win);
376 }
377 
378 // Limit value by range 0 - 100
clamp(int value)379 int clamp(int value)
380 {
381   if (value < 0) return 0;
382   if (value > 100) return 100;
383   return value;
384 }
385 
386 // Convert a value in dB to the linear range 0 - 100
387 // 0 corresponds to `-range` dB, 100 corresponds to `range` dB
db_to_value(float db,float range)388 int db_to_value(float db, float range)
389 {
390   int value = clamp((int)((db + range)/2.0/range*100.0));
391   return value;
392 }
393 
394 // Calculate RGB color of Dial's LED for given value
value_to_color(int value)395 st_rgb value_to_color(int value)
396 {
397   st_rgb color;
398   color.r = 0.01*pow(value,1.0/3.0)*100.0/pow(100,1.0/3.0);
399   color.g = 0.7874*(1.0-pow(value,3)/1000000.0);
400   color.b = 0;
401   return color;
402 }
403 
404 // Calculate coordinates of Dial's LED for given value
value_to_xy(int value)405 st_point value_to_xy(int value)
406 {
407   int pointerAngle = value/100.0*(360.0-80.0-80.0) - 105.0;
408   st_point p;
409   p.x = 25 + 15*sin(pointerAngle/180.0*M_PI);
410   p.y = 25 - 15*cos(pointerAngle/180.0*M_PI);
411   return p;
412 }
413 
draw_dial(cairo_t * cr,cairo_surface_t * image,int value,int base_x,int base_y)414 void draw_dial(cairo_t *cr, cairo_surface_t *image, int value, int base_x, int base_y)
415 {
416   st_rgb color = value_to_color(value);
417   st_point p = value_to_xy(value);
418 
419   cairo_set_source_rgb(cr, color.r, color.g, color.b);
420   cairo_arc(cr, base_x+p.x+10.5, base_y+p.y+10, 5.5, 0, 2 * M_PI);
421   cairo_fill(cr);
422   cairo_set_source_surface (cr, image, base_x+p.x, base_y+p.y);
423   cairo_paint (cr);
424 }
425 
draw_slider(cairo_t * cr,int value,int base_x,int base_y)426 void draw_slider(cairo_t *cr, int value, int base_x, int base_y)
427 {
428   cairo_set_source_rgb(cr, 0, 0.35, 0);
429 
430   int length = (int)(1.83 * value);
431 
432   cairo_rectangle(cr, base_x, base_y, length, 19);
433   cairo_fill(cr);
434 }
435 
win_draw(win_t * win)436 static void win_draw(win_t *win)
437 {
438   cairo_push_group (win->cr);
439 
440   // File select dialog is pending
441   if (win->profile_select_fd != -1)
442   {
443     // Draw profile name element
444     cairo_set_source_rgb(win->cr, 1, 0, 0);
445     cairo_rectangle(win->cr, 75, 354, 439 - 75, 373 - 354);
446     cairo_fill(win->cr);
447 
448     // Draw profile name
449     cairo_set_source_rgb(win->cr, 0, 0, 0);
450     cairo_select_font_face(win->cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
451     cairo_set_font_size(win->cr, 18);
452     cairo_move_to(win->cr, 77, 370);
453   }
454   // Draw profile name element in normal state
455   else
456   {
457     // Draw profile name element
458     cairo_set_source_rgb(win->cr, 0, 0, 0);
459     cairo_rectangle(win->cr, 75, 354, 439 - 75, 373 - 354);
460     cairo_fill(win->cr);
461 
462     // Draw profile name
463     cairo_set_source_rgb(win->cr, 1, 1, 1);
464     cairo_select_font_face(win->cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
465     cairo_set_font_size(win->cr, 18);
466     cairo_move_to(win->cr, 77, 370);
467   }
468 
469   char profile_name[PATH_MAX];
470 
471   strncpy(profile_name, win->profile_path, sizeof(profile_name));
472   size_t profile_name_len = strlen(profile_name);
473   if (profile_name_len >= 5)
474   {
475     profile_name[strlen(profile_name) - 5] = '\0';
476     char *base = basename(profile_name);
477     cairo_show_text(win->cr, base);
478   }
479 
480   // Draw background image
481   cairo_set_source_surface (win->cr, win->image, 0, 0);
482   cairo_paint (win->cr);
483 
484   draw_slider(win->cr,
485               win->levelSlider.value,
486               win->levelSlider.base_x,
487               win->levelSlider.base_y);
488 
489   draw_slider(win->cr,
490               win->cabinetSlider.value,
491               win->cabinetSlider.base_x,
492               win->cabinetSlider.base_y);
493 
494   draw_dial(win->cr,
495             win->image2,
496             win->driveDial.value,
497             win->driveDial.base_x,
498             win->driveDial.base_y);
499   draw_dial(win->cr,
500             win->image2,
501             win->bassDial.value,
502             win->bassDial.base_x,
503             win->bassDial.base_y);
504   draw_dial(win->cr,
505             win->image2,
506             win->middleDial.value,
507             win->middleDial.base_x,
508             win->middleDial.base_y);
509   draw_dial(win->cr,
510             win->image2,
511             win->trebleDial.value,
512             win->trebleDial.base_x,
513             win->trebleDial.base_y);
514   draw_dial(win->cr,
515             win->image2,
516             win->mastergainDial.value,
517             win->mastergainDial.base_x,
518             win->mastergainDial.base_y);
519 
520   cairo_pop_group_to_source (win->cr);
521   cairo_paint (win->cr);
522 }
523 
524 // Callback from host to change Dial values
port_event(LV2UI_Handle ui,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)525 static void port_event(LV2UI_Handle ui,
526                         uint32_t port_index,
527                         uint32_t buffer_size,
528                         uint32_t format,
529                         const void * buffer)
530 {
531   win_t *win = (win_t *)ui;
532 
533   // Atom event received
534   if (format == win->uris.atom_eventTransfer)
535   {
536     const LV2_Atom* atom = (const LV2_Atom*)buffer;
537 
538     if (lv2_atom_forge_is_object_type(&win->forge, atom->type))
539     {
540       const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
541       if (obj->body.otype == win->uris.patch_Set)
542       {
543         // Received patch_Set message
544         const LV2_Atom* property = NULL;
545         lv2_atom_object_get(obj, win->uris.patch_property, &property, 0);
546 
547         if (((const LV2_Atom_URID*)property)->body == win->uris.profile)
548         {
549           const LV2_Atom* value = NULL;
550 
551           lv2_atom_object_get(obj, win->uris.patch_value, &value, 0);
552 
553           if (value->type == win->uris.atom_Path)
554           {
555             // Received new profile path
556             strncpy(win->profile_path, (const char*)LV2_ATOM_BODY_CONST(value),
557                 sizeof(win->profile_path));
558             win->profile_path[sizeof(win->profile_path) - 1] = '\0';
559           }
560         }
561       }
562     }
563   }
564   // Port event received
565   else if (format == 0)
566   {
567     switch (port_index)
568     {
569       case PORT_BASS:
570         win->bassDial.value = db_to_value(*(float*)buffer, 10.0);
571       break;
572       case PORT_DRIVE:
573         win->driveDial.value = clamp((int)(*(float*)buffer));
574       break;
575       case PORT_MASTERGAIN:
576         win->mastergainDial.value = clamp((int)(*(float*)buffer));
577       break;
578       case PORT_MIDDLE:
579         win->middleDial.value = db_to_value(*(float*)buffer, 10.0);
580       break;
581       case PORT_TREBLE:
582         win->trebleDial.value = db_to_value(*(float*)buffer, 10.0);
583       break;
584       case PORT_VOLUME:
585         win->levelSlider.value = clamp((int)((*(float*)buffer)*100.0));
586       break;
587       case PORT_CABINET:
588         win->cabinetSlider.value = clamp((int)((*(float*)buffer)*100.0));
589       break;
590     }
591   }
592   // Redraw window due to value change
593   win_draw(win);
594 }
595 
596 // Determine if the point with given coordinates
597 // belongs to the given Dial
is_point_in_dial_area(int x,int y,st_dial * dial)598 bool is_point_in_dial_area(int x, int y, st_dial *dial)
599 {
600   if ((x >= dial->base_x + 5) && (x <= dial->base_x + 65)
601       && (y >= dial->base_y + 5) && (y <= dial->base_y + 65))
602   {
603     return true;
604   }
605   return false;
606 }
607 
608 // Determine if the point with given coordinates
609 // belongs to the given Slider
is_point_in_slider_area(int x,int y,st_slider * slider)610 bool is_point_in_slider_area(int x, int y, st_slider *slider)
611 {
612   if ((x >= slider->base_x) && (x <= slider->base_x + 183)
613       && (y >= slider->base_y) && (y <= slider->base_y + 19))
614   {
615     return true;
616   }
617   return false;
618 }
619 
620 // Determine if the point with given coordinates
621 // belongs to the Profile choose control
is_point_in_profile_area(int x,int y)622 bool is_point_in_profile_area(int x, int y)
623 {
624   if ((x >= 75) && (x <= 439)
625       && (y >= 354) && (y <= 373))
626   {
627     return true;
628   }
629   return false;
630 }
631 
632 // Determine if the point with given coordinates
633 // belongs to the given area
is_point_in_area(int x,int y,int topleft_x,int topleft_y,int bottomright_x,int bottomright_y)634 bool is_point_in_area(int x, int y, int topleft_x, int topleft_y,
635                       int bottomright_x, int bottomright_y)
636 {
637   if ((x >= topleft_x) && (x <= bottomright_x)
638     && (y >= topleft_y) && (y <= bottomright_y))
639   {
640     return true;
641   }
642   return false;
643 }
644 
645 // Convert the linear value 0 - 100 to the dB value,
646 // 0 corresponds to `-range` dB, 100 corresponds to `range` dB
value_to_db(int value,float range)647 float value_to_db(int value, float range)
648 {
649   float db = value / 100.0 * 2.0 * range - range;
650   return db;
651 }
652 
653 // Shows file select dialog and sends Atom message
654 // to change profile.
select_new_profile_file(win_t * win)655 static int select_new_profile_file(win_t *win)
656 {
657   if (win->fp_win != NULL)
658   {
659     delete win->fp_win;
660     win->fp_win = NULL;
661   }
662   win->fp_win = new KPPFilePicker::Win();
663   win->fp_win->init();
664   win->fp_win->list.filters.push_back("tapf");
665 
666   std::string currentDir = win->profile_path;
667   currentDir = currentDir.substr(0, currentDir.find_last_of('/'));
668   win->fp_win->setTitle("Open *.tapf Profile File");
669   //win->fp_win->list.showHidden = true;
670   win->fp_win->list.readDir(currentDir);
671 
672   return 0;
673 }
674 
675 // Process X11 events
676 static void
win_handle_events(win_t * win)677 win_handle_events(win_t *win)
678 {
679   if (win->fp_win != NULL)
680   {
681     if (win->fp_win->isInited)
682     {
683       xcb_flush(win->fp_win->connection);
684       xcb_generic_event_t *event;
685       while ((event = xcb_poll_for_event(win->fp_win->connection)) != NULL)
686       {
687         win->fp_win->handle_events(event);
688         free(event);
689       }
690       xcb_flush(win->fp_win->connection);
691     }
692   }
693 
694   xcb_flush(win->connection);
695   xcb_generic_event_t *event;
696   while ((event = xcb_poll_for_event(win->connection)) != NULL)
697   {
698     switch(event->response_type & ~0x80)
699     {
700       case XCB_EXPOSE: // the window must be redrawn
701       {
702         xcb_expose_event_t *eev = (xcb_expose_event_t *) event;
703 
704         if (eev->count == 0)
705         {
706           win_draw(win);
707         }
708       }
709       break;
710       case XCB_BUTTON_PRESS: // Left mouse button pressed
711       {
712         xcb_button_press_event_t *bev = (xcb_button_press_event_t *) event;
713         if (bev->detail == XCB_BUTTON_INDEX_1)
714         {
715           // Save position
716           win->pos_x = bev->event_x;
717           win->pos_y = bev->event_y;
718 
719           // Find on which dial the button was pressed
720           if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->driveDial)))
721           {
722             win->driveDial.start_value = win->driveDial.value;
723             win->active_dial = PORT_DRIVE;
724           }
725           else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->bassDial)))
726           {
727             win->bassDial.start_value = win->bassDial.value;
728             win->active_dial = PORT_BASS;
729           }
730           else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->middleDial)))
731           {
732             win->middleDial.start_value = win->middleDial.value;
733             win->active_dial = PORT_MIDDLE;
734           }
735           else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->trebleDial)))
736           {
737             win->trebleDial.start_value = win->trebleDial.value;
738             win->active_dial = PORT_TREBLE;
739           }
740           else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->mastergainDial)))
741           {
742             win->mastergainDial.start_value = win->mastergainDial.value;
743             win->active_dial = PORT_MASTERGAIN;
744           }
745           else if (is_point_in_slider_area(win->pos_x, win->pos_y, &(win->levelSlider)))
746           {
747             win->levelSlider.start_value = win->levelSlider.value;
748             win->active_dial = PORT_VOLUME;
749           }
750           else if (is_point_in_slider_area(win->pos_x, win->pos_y, &(win->cabinetSlider)))
751           {
752             win->cabinetSlider.start_value = win->cabinetSlider.value;
753             win->active_dial = PORT_CABINET;
754           }
755           else if (is_point_in_profile_area(win->pos_x, win->pos_y))
756           {
757             select_new_profile_file(win);
758             win->active_dial = -1;
759             win_draw(win);
760           }
761           else
762           {
763             win->active_dial = -1;
764           }
765         }
766       }
767       break;
768       case XCB_MOTION_NOTIFY: // Mouse move while left button is pressed
769       {
770         xcb_motion_notify_event_t *mev = (xcb_motion_notify_event_t *) event;
771 
772         if (win->active_dial != -1)
773         {
774           // Recalculate active Dial value and send new value to the
775           // main LV2 plugin module
776           switch (win->active_dial)
777           {
778             float value;
779             case PORT_DRIVE:
780               win->driveDial.value = clamp(win->driveDial.start_value + win->pos_y - mev->event_y);
781               value = win->driveDial.value;
782               win->write_function(win->controller,PORT_DRIVE,sizeof(float),0,&value);
783             break;
784             case PORT_BASS:
785               win->bassDial.value = clamp(win->bassDial.start_value + win->pos_y - mev->event_y);
786               value = value_to_db(win->bassDial.value, 10.0);
787               win->write_function(win->controller,PORT_BASS,sizeof(float),0,&value);
788             break;
789             case PORT_MIDDLE:
790               win->middleDial.value = clamp(win->middleDial.start_value + win->pos_y - mev->event_y);
791               value = value_to_db(win->middleDial.value, 10.0);
792               win->write_function(win->controller,PORT_MIDDLE,sizeof(float),0,&value);
793             break;
794             case PORT_TREBLE:
795               win->trebleDial.value = clamp(win->trebleDial.start_value + win->pos_y - mev->event_y);
796               value = value_to_db(win->trebleDial.value, 10.0);
797               win->write_function(win->controller,PORT_TREBLE,sizeof(float),0,&value);
798             break;
799             case PORT_MASTERGAIN:
800               win->mastergainDial.value = clamp(win->mastergainDial.start_value + win->pos_y - mev->event_y);
801               value = win->mastergainDial.value;
802               win->write_function(win->controller,PORT_MASTERGAIN,sizeof(float),0,&value);
803             break;
804             case PORT_VOLUME:
805               win->levelSlider.value = clamp(win->levelSlider.start_value - win->pos_x + mev->event_x);
806               value = win->levelSlider.value / 100.0;
807               win->write_function(win->controller,PORT_VOLUME,sizeof(float),0,&value);
808             break;
809             case PORT_CABINET:
810               win->cabinetSlider.value = clamp(win->cabinetSlider.start_value - win->pos_x + mev->event_x);
811               value = win->cabinetSlider.value / 100.0;
812               win->write_function(win->controller,PORT_CABINET,sizeof(float),0,&value);
813           }
814           win_draw(win);
815         }
816       }
817       break;
818     }
819     free(event);
820   }
821   xcb_flush(win->connection);
822 
823   if (win->fp_win != NULL)
824   {
825     if (win->fp_win->isInited)
826     {
827       if (win->fp_win->isFilenameUpdated)
828       {
829         strncpy(win->profile_path, win->fp_win->newFileName.c_str(), win->fp_win->newFileName.size());
830         win->profile_path[win->fp_win->newFileName.size()] = '\0';
831 
832         lv2_atom_forge_set_buffer(&win->forge, win->forge_buf, sizeof(win->forge_buf));
833         LV2_Atom_Forge_Frame frame;
834         LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(&win->forge,
835                                                          &frame,
836                                                          0,
837                                                          win->uris.patch_Set);
838 
839         lv2_atom_forge_key(&win->forge, win->uris.patch_property);
840         lv2_atom_forge_urid(&win->forge, win->uris.profile);
841         lv2_atom_forge_key(&win->forge, win->uris.patch_value);
842         lv2_atom_forge_path(&win->forge, win->profile_path, win->fp_win->newFileName.size());
843 
844         lv2_atom_forge_pop(&win->forge, &frame);
845 
846         win->write_function(win->controller, PORT_CONTROL, lv2_atom_total_size(msg),
847                             win->uris.atom_eventTransfer,
848                             msg);
849         win_draw(win);
850         win->fp_win->isFilenameUpdated = false;
851         delete win->fp_win;
852         win->fp_win = NULL;
853       }
854     }
855   }
856 }
857 
858 // LV2 callback function to organize event handling
859 static int
idle(LV2UI_Handle handle)860 idle(LV2UI_Handle handle)
861 {
862   win_t *win = (win_t *)handle;
863   win_handle_events(win);
864 
865   return 0;
866 }
867 
868 
869 // LV2 interfaces and descriptors
870 static const LV2UI_Idle_Interface idle_iface = { idle };
871 
872 static const void*
extension_data(const char * uri)873 extension_data(const char* uri)
874 {
875   if (!strcmp(uri, LV2_UI__idleInterface))
876   {
877     return &idle_iface;
878   }
879   return NULL;
880 }
881 
882 static const LV2UI_Descriptor descriptor =
883 {
884   PLUGIN_URI "ui",
885   (void* (*)(const LV2UI_Descriptor*, const char*, const char*, void (*)(void*, unsigned int, unsigned int, unsigned int, const void*), void*, void**, const LV2_Feature* const*))instantiate,
886   cleanup,
887   port_event,
888   extension_data
889 };
890 
891 LV2_SYMBOL_EXPORT
892 const LV2UI_Descriptor*
lv2ui_descriptor(uint32_t index)893 lv2ui_descriptor(uint32_t index)
894 {
895   switch (index)
896   {
897     case 0:
898       return &descriptor;
899     default:
900       return NULL;
901   }
902 }
903