1 /*
2  *                           0BSD
3  *
4  *                    BSD Zero Clause License
5  *
6  *  Copyright (c) 2019 Hermann Meyer
7  *
8  * Permission to use, copy, modify, and/or distribute this software for any
9  * purpose with or without fee is hereby granted.
10 
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13  * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  *
19  */
20 
21 
22 #include "lv2_plugin.h"
23 
24 /*---------------------------------------------------------------------
25 -----------------------------------------------------------------------
26                 the main LV2 handle->XWindow
27 -----------------------------------------------------------------------
28 ----------------------------------------------------------------------*/
29 
30 // setup a color theme
set_default_theme(Xputty * main)31 static void set_default_theme(Xputty *main) {
32     main->color_scheme->normal = (Colors) {
33          /* cairo    / r  / g  / b  / a  /  */
34         /*fg */       { 0.68, 0.44, 0.00, 1.00},
35         /*bg */       { 0.1, 0.1, 0.1, 1.0},
36         /*base */     { 0.1, 0.1, 0.1, 1.0},
37         /*text */     { 0.85, 0.52, 0.00, 1.00},
38         /*shadow */   { 0.1, 0.1, 0.1, 0.2},
39         /*frame */    { 0.0, 0.0, 0.0, 1.0},
40         /*light */    { 0.1, 0.1, 0.2, 1.0}
41     };
42 
43     main->color_scheme->prelight = (Colors) {
44         /*fg */       { 1.0, 1.0, 1.0, 1.0},
45         /*bg */       { 0.25, 0.25, 0.25, 1.0},
46         /*base */     { 0.2, 0.2, 0.2, 1.0},
47         /*text */     { 0.7, 0.7, 0.7, 1.0},
48         /*shadow */   { 0.1, 0.1, 0.1, 0.4},
49         /*frame */    { 0.3, 0.3, 0.3, 1.0},
50         /*light */    { 0.3, 0.3, 0.3, 1.0}
51     };
52 
53     main->color_scheme->selected = (Colors) {
54         /*fg */       { 0.9, 0.9, 0.9, 1.0},
55         /*bg */       { 0.2, 0.2, 0.2, 1.0},
56         /*base */     { 0.1, 0.1, 0.1, 1.0},
57         /*text */     { 1.0, 1.0, 1.0, 1.0},
58         /*shadow */   { 0.18, 0.18, 0.18, 0.2},
59         /*frame */    { 0.18, 0.18, 0.18, 1.0},
60         /*light */    { 0.18, 0.18, 0.28, 1.0}
61     };
62 }
63 
set_default_knob_color(KnobColors * kp)64 static void set_default_knob_color(KnobColors* kp) {
65     *kp = (KnobColors) {
66          /* cairo    / r  / g  / b  / a  /  */
67         /*p1f */       { 0.349, 0.313, 0.243, 1.0},
68         /*p2f */       { 0.349, 0.235, 0.011, 1.0},
69         /*p3f */       { 0.15, 0.15, 0.15, 1.0},
70         /*p4f */       { 0.1, 0.1, 0.1, 1.00},
71         /*p5f */       { 0.05, 0.05, 0.05, 1.0},
72         /*p1k */       { 0.349, 0.313, 0.243, 1.0},
73         /*p2k */       { 0.349, 0.235, 0.011, 1.0},
74         /*p3k */       { 0.15, 0.15, 0.15, 1.0},
75         /*p4k */       { 0.1, 0.1, 0.1, 1.00},
76         /*p5k */       { 0.05, 0.05, 0.05, 1.0},
77     };
78 }
79 
80 // draw the window
draw_window(void * w_,void * user_data)81 static void draw_window(void *w_, void* user_data) {
82     Widget_t *w = (Widget_t*)w_;
83     set_pattern(w,&w->app->color_scheme->selected,&w->app->color_scheme->normal,BACKGROUND_);
84     cairo_paint (w->crb);
85     set_pattern(w,&w->app->color_scheme->normal,&w->app->color_scheme->selected,BACKGROUND_);
86     cairo_rectangle (w->crb,4,4,w->width-8,w->height-8);
87     cairo_set_line_width(w->crb,4);
88     cairo_stroke(w->crb);
89 
90     cairo_text_extents_t extents;
91     use_text_color_scheme(w, get_color_state(w));
92     cairo_set_font_size (w->crb, w->app->big_font/w->scale.ascale);
93     cairo_text_extents(w->crb,w->label , &extents);
94     double tw = extents.width/2.0;
95 
96     widget_set_scale(w);
97     cairo_move_to (w->crb, (w->scale.init_width*0.5)-tw, w->scale.init_height-10 );
98     cairo_show_text(w->crb, w->label);
99     widget_reset_scale(w);
100     cairo_new_path (w->crb);
101 }
102 
103 // draw the knobs
draw_my_knob(void * w_,void * user_data)104 static void draw_my_knob(void *w_, void* user_data) {
105     Widget_t *w = (Widget_t*)w_;
106     X11_UI* ui = (X11_UI*)w->parent_struct;
107     int width = w->width-2;
108     int height = w->height-2;
109 
110     const double scale_zero = 20 * (M_PI/180); // defines "dead zone" for knobs
111     int arc_offset = 2;
112     int knob_x = 0;
113     int knob_y = 0;
114 
115     int grow = (width > height) ? height:width;
116     knob_x = grow-1;
117     knob_y = grow-1;
118     /** get values for the knob **/
119 
120     int knobx = (width - knob_x) * 0.5;
121     int knobx1 = width* 0.5;
122 
123     int knoby = (height - knob_y) * 0.5;
124     int knoby1 = height * 0.5;
125 
126     double knobstate = adj_get_state(w->adj_y);
127     double angle = scale_zero + knobstate * 2 * (M_PI - scale_zero);
128 
129     double pointer_off =knob_x/3.5;
130     double radius = min(knob_x-pointer_off, knob_y-pointer_off) / 2;
131     double lengh_x = (knobx+radius+pointer_off/2) - radius * sin(angle);
132     double lengh_y = (knoby+radius+pointer_off/2) + radius * cos(angle);
133     double radius_x = (knobx+radius+pointer_off/2) - radius/ 1.18 * sin(angle);
134     double radius_y = (knoby+radius+pointer_off/2) + radius/ 1.18 * cos(angle);
135     cairo_pattern_t* pat;
136     cairo_new_path (w->crb);
137 
138     pat = cairo_pattern_create_linear (0, 0, 0, knob_y);
139     cairo_pattern_add_color_stop_rgba (pat, 1, ui->kp->p1f[0],ui->kp->p1f[1],ui->kp->p1f[2],ui->kp->p1f[3]);
140     cairo_pattern_add_color_stop_rgba (pat, 0.75, ui->kp->p2f[0],ui->kp->p2f[1],ui->kp->p2f[2],ui->kp->p2f[3]);
141     cairo_pattern_add_color_stop_rgba (pat, 0.5,  ui->kp->p3f[0],ui->kp->p3f[1],ui->kp->p3f[2],ui->kp->p3f[3]);
142     cairo_pattern_add_color_stop_rgba (pat, 0.25,  ui->kp->p4f[0],ui->kp->p4f[1],ui->kp->p4f[2],ui->kp->p4f[3]);
143     cairo_pattern_add_color_stop_rgba (pat, 0,  ui->kp->p5f[0],ui->kp->p5f[1],ui->kp->p5f[2],ui->kp->p5f[3]);
144 
145     cairo_scale (w->crb, 0.95, 1.05);
146     cairo_arc(w->crb,knobx1+arc_offset/2, knoby1-arc_offset, knob_x/2.2, 0, 2 * M_PI );
147     cairo_set_source (w->crb, pat);
148     cairo_fill_preserve (w->crb);
149     cairo_set_source_rgb (w->crb, 0.1, 0.1, 0.1);
150     cairo_set_line_width(w->crb,1);
151     cairo_stroke(w->crb);
152     cairo_scale (w->crb, 1.05, 0.95);
153     cairo_new_path (w->crb);
154     cairo_pattern_destroy (pat);
155     pat = NULL;
156 
157     pat = cairo_pattern_create_linear (0, 0, 0, knob_y);
158     cairo_pattern_add_color_stop_rgba (pat, 0, ui->kp->p1k[0],ui->kp->p1k[1],ui->kp->p1k[2],ui->kp->p1k[3]);
159     cairo_pattern_add_color_stop_rgba (pat, 0.25, ui->kp->p2k[0],ui->kp->p2k[1],ui->kp->p2k[2],ui->kp->p2k[3]);
160     cairo_pattern_add_color_stop_rgba (pat, 0.5, ui->kp->p3k[0],ui->kp->p3k[1],ui->kp->p3k[2],ui->kp->p3k[3]);
161     cairo_pattern_add_color_stop_rgba (pat, 0.75, ui->kp->p4k[0],ui->kp->p4k[1],ui->kp->p4k[2],ui->kp->p4k[3]);
162     cairo_pattern_add_color_stop_rgba (pat, 1, ui->kp->p5k[0],ui->kp->p5k[1],ui->kp->p5k[2],ui->kp->p5k[3]);
163 
164     cairo_arc(w->crb,knobx1, knoby1, knob_x/2.6, 0, 2 * M_PI );
165     cairo_set_source (w->crb, pat);
166     cairo_fill_preserve (w->crb);
167     cairo_set_source_rgb (w->crb, 0.1, 0.1, 0.1);
168     cairo_set_line_width(w->crb,1);
169     cairo_stroke(w->crb);
170     cairo_new_path (w->crb);
171     cairo_pattern_destroy (pat);
172 
173     use_text_color_scheme(w, get_color_state(w));
174 
175     /** create a rotating pointer on the kob**/
176     cairo_set_line_cap(w->crb, CAIRO_LINE_CAP_ROUND);
177     cairo_set_line_join(w->crb, CAIRO_LINE_JOIN_BEVEL);
178     cairo_move_to(w->crb, radius_x, radius_y);
179     cairo_line_to(w->crb,lengh_x,lengh_y);
180     cairo_set_line_width(w->crb,3);
181     cairo_stroke(w->crb);
182     cairo_new_path (w->crb);
183 
184     cairo_text_extents_t extents;
185     /** show value on the kob**/
186     if (w->state>0.0 && w->state<4.0) {
187         float value = adj_get_value(w->adj);
188         char s[64];
189         const char* format[] = {"%.1f", "%.2f", "%.3f"};
190         if (fabs(w->adj->step)>0.99) {
191             snprintf(s, 63,"%d",  (int) value);
192         } else if (fabs(w->adj->step)>0.09) {
193             snprintf(s, 63, format[1-1], value);
194         } else {
195             snprintf(s, 63, format[2-1], value);
196         }
197         cairo_set_font_size (w->crb, w->app->small_font/w->scale.ascale);
198         cairo_text_extents(w->crb, s, &extents);
199         cairo_move_to (w->crb, knobx1-extents.width/2, knoby1+extents.height/2);
200         cairo_show_text(w->crb, s);
201         cairo_new_path (w->crb);
202     }
203 
204     /** show label below the knob**/
205     cairo_set_font_size (w->crb, w->app->normal_font/w->scale.ascale);
206     cairo_text_extents(w->crb,w->label , &extents);
207 
208     cairo_move_to (w->crb, knobx1-extents.width/2, height-2 );
209     cairo_show_text(w->crb, w->label);
210     cairo_new_path (w->crb);
211 }
212 
213 // if controller value changed send notify to host
value_changed(void * w_,void * user_data)214 static void value_changed(void *w_, void* user_data) {
215     Widget_t *w = (Widget_t*)w_;
216     X11_UI* ui = (X11_UI*)w->parent_struct;
217     if (ui->block_event != w->data) {
218         float v = adj_get_value(w->adj);
219         ui->write_function(ui->controller,w->data,sizeof(float),0,&v);
220         plugin_value_changed(ui, w, (PortIndex)w->data);
221     }
222     ui->block_event = -1;
223 }
224 
225 // shortcut to create knobs with portindex binding
add_my_knob(Widget_t * w,PortIndex index,const char * label,X11_UI * ui,int x,int y,int width,int height)226 Widget_t* add_my_knob(Widget_t *w, PortIndex index, const char * label,
227                                 X11_UI* ui, int x, int y, int width, int height) {
228     w = add_knob(ui->win, label, x, y, width, height);
229     w->func.expose_callback = draw_my_knob;
230     w->parent_struct = ui;
231     w->data = index;
232     w->func.value_changed_callback = value_changed;
233     return w;
234 }
235 
236 // shortcut to create image knobs with portindex binding
add_my_image_knob(Widget_t * w,PortIndex index,const char * label,X11_UI * ui,int x,int y,int width,int height)237 Widget_t* add_my_image_knob(Widget_t *w, PortIndex index, const char * label,
238                                 X11_UI* ui, int x, int y, int width, int height) {
239     w = add_image_knob(ui->win, label, x, y, width, height);
240     w->parent_struct = ui;
241     w->data = index;
242     w->func.value_changed_callback = value_changed;
243     return w;
244 }
245 
246 // shortcut to create image knobs with portindex binding
add_my_value_knob(Widget_t * w,PortIndex index,const char * label,X11_UI * ui,int x,int y,int width,int height)247 Widget_t* add_my_value_knob(Widget_t *w, PortIndex index, const char * label,
248                                 X11_UI* ui, int x, int y, int width, int height) {
249     w = add_knob(ui->win, label, x, y, width, height);
250     w->parent_struct = ui;
251     w->data = index;
252     w->func.value_changed_callback = value_changed;
253     return w;
254 }
255 
256 // shortcut to create image knobs with portindex binding
add_my_switch_image(Widget_t * w,PortIndex index,const char * label,X11_UI * ui,int x,int y,int width,int height)257 Widget_t* add_my_switch_image(Widget_t *w, PortIndex index, const char * label,
258                                 X11_UI* ui, int x, int y, int width, int height) {
259     w = add_switch_image_button(ui->win, label, x, y, width, height);
260     w->parent_struct = ui;
261     w->data = index;
262     w->func.value_changed_callback = value_changed;
263     return w;
264 }
265 
266 // shortcut to create comboboxes with portindex binding
add_my_combobox(Widget_t * w,PortIndex index,const char * label,const char ** items,size_t len,int active,X11_UI * ui,int x,int y,int width,int height)267 Widget_t* add_my_combobox(Widget_t *w, PortIndex index, const char * label, const char** items,
268                                 size_t len, int active, X11_UI* ui, int x, int y, int width, int height) {
269     w = add_combobox(ui->win, label, x, y, width, height);
270     size_t st = 0;
271     for(; st < len; st++) {
272         combobox_add_entry(w,items[st]);
273     }
274     w->parent_struct = ui;
275     w->data = index;
276     combobox_set_active_entry(w, active);
277     w->func.value_changed_callback = value_changed;
278     return w;
279 }
280 
281 // init the xwindow and return the LV2UI handle
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)282 static LV2UI_Handle instantiate(const LV2UI_Descriptor * descriptor,
283             const char * plugin_uri, const char * bundle_path,
284             LV2UI_Write_Function write_function,
285             LV2UI_Controller controller, LV2UI_Widget * widget,
286             const LV2_Feature * const * features) {
287 
288     X11_UI* ui = (X11_UI*)malloc(sizeof(X11_UI));
289 
290     if (!ui) {
291         fprintf(stderr,"ERROR: failed to instantiate plugin with URI %s\n", plugin_uri);
292         return NULL;
293     }
294 
295     ui->parentXwindow = 0;
296     ui->block_event = -1;
297     ui->private_ptr = NULL;
298 
299     int i = 0;
300     for(;i<CONTROLS;i++)
301         ui->widget[i] = NULL;
302 
303     i = 0;
304     for (; features[i]; ++i) {
305         if (!strcmp(features[i]->URI, LV2_UI__parent)) {
306             ui->parentXwindow = features[i]->data;
307         } else if (!strcmp(features[i]->URI, LV2_UI__resize)) {
308             ui->resize = (LV2UI_Resize*)features[i]->data;
309         }
310     }
311 
312     if (ui->parentXwindow == NULL)  {
313         fprintf(stderr, "ERROR: Failed to open parentXwindow for %s\n", plugin_uri);
314         free(ui);
315         return NULL;
316     }
317     // init Xputty
318     main_init(&ui->main);
319     ui->kp = (KnobColors*)malloc(sizeof(KnobColors));
320     set_default_knob_color(ui->kp);
321     set_default_theme(&ui->main);
322     int w = 1;
323     int h = 1;
324     plugin_set_window_size(&w,&h,plugin_uri);
325     // create the toplevel Window on the parentXwindow provided by the host
326     ui->win = create_window(&ui->main, (Window)ui->parentXwindow, 0, 0, w, h);
327     ui->win->parent_struct = ui;
328     ui->win->label = plugin_set_name();
329     // connect the expose func
330     ui->win->func.expose_callback = draw_window;
331     // create controller widgets
332     plugin_create_controller_widgets(ui,plugin_uri);
333     // map all widgets into the toplevel Widget_t
334     widget_show_all(ui->win);
335     // set the widget pointer to the X11 Window from the toplevel Widget_t
336     *widget = (void*)ui->win->widget;
337     // request to resize the parentXwindow to the size of the toplevel Widget_t
338     if (ui->resize){
339         ui->resize->ui_resize(ui->resize->handle, w, h);
340     }
341     // store pointer to the host controller
342     ui->controller = controller;
343     // store pointer to the host write function
344     ui->write_function = write_function;
345 
346     return (LV2UI_Handle)ui;
347 }
348 
349 // cleanup after usage
cleanup(LV2UI_Handle handle)350 static void cleanup(LV2UI_Handle handle) {
351     X11_UI* ui = (X11_UI*)handle;
352     free(ui->kp);
353     plugin_cleanup(ui);
354     // Xputty free all memory used
355     main_quit(&ui->main);
356     free(ui->private_ptr);
357     free(ui);
358 }
359 
360 /*---------------------------------------------------------------------
361 -----------------------------------------------------------------------
362                         LV2 interface
363 -----------------------------------------------------------------------
364 ----------------------------------------------------------------------*/
365 
366 // port value change message from host
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)367 static void port_event(LV2UI_Handle handle, uint32_t port_index,
368                         uint32_t buffer_size, uint32_t format,
369                         const void * buffer) {
370     X11_UI* ui = (X11_UI*)handle;
371     float value = *(float*)buffer;
372     int i=0;
373     for (;i<CONTROLS;i++) {
374         if (ui->widget[i] && port_index == (uint32_t)ui->widget[i]->data) {
375             // prevent event loop between host and plugin
376             ui->block_event = (int)port_index;
377             // Xputty check if the new value differs from the old one
378             // and set new one, when needed
379             adj_set_value(ui->widget[i]->adj, value);
380         }
381    }
382    plugin_port_event(handle, port_index, buffer_size, format, buffer);
383 }
384 
385 // LV2 idle interface to host
ui_idle(LV2UI_Handle handle)386 static int ui_idle(LV2UI_Handle handle) {
387     X11_UI* ui = (X11_UI*)handle;
388     // Xputty event loop setup to run one cycle when called
389     run_embedded(&ui->main);
390     return 0;
391 }
392 
393 // LV2 resize interface to host
ui_resize(LV2UI_Feature_Handle handle,int w,int h)394 static int ui_resize(LV2UI_Feature_Handle handle, int w, int h) {
395     X11_UI* ui = (X11_UI*)handle;
396     // Xputty sends configure event to the toplevel widget to resize itself
397     if (ui) send_configure_event(ui->win,0, 0, w, h);
398     return 0;
399 }
400 
401 // connect idle and resize functions to host
extension_data(const char * uri)402 static const void* extension_data(const char* uri) {
403     static const LV2UI_Idle_Interface idle = { ui_idle };
404     static const LV2UI_Resize resize = { 0 ,ui_resize };
405     if (!strcmp(uri, LV2_UI__idleInterface)) {
406         return &idle;
407     }
408     if (!strcmp(uri, LV2_UI__resize)) {
409         return &resize;
410     }
411     return NULL;
412 }
413 
414 static const LV2UI_Descriptor descriptors[] = {
415     {PLUGIN_UI_URI,instantiate,cleanup,port_event,extension_data},
416 #ifdef GXPLUGIN_UI_URI2
417     {PLUGIN_UI_URI2,instantiate,cleanup,port_event,extension_data},
418 #endif
419 };
420 
421 
422 
423 #ifdef __cplusplus
424 extern "C" {
425 #endif
426 LV2_SYMBOL_EXPORT
lv2ui_descriptor(uint32_t index)427 const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) {
428     if (index >= sizeof(descriptors) / sizeof(descriptors[0])) {
429         return NULL;
430     }
431     return descriptors + index;
432 }
433 #ifdef __cplusplus
434 }
435 #endif
436 
437