1 /*
2 * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
3 *
4 * This is free software: you can redistribute it and/or modify
5 * it under the terms of the Artistic License 2.0 as published by
6 * The Perl Foundation.
7 *
8 * This source is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * Artistic License 2.0 for more details.
12 *
13 * You should have received a copy of the Artistic License 2.0
14 * along the source as a COPYING file. If not, obtain it from
15 * http://www.perlfoundation.org/artistic_license_2_0.
16 */
17
18 #include <stdatomic.h>
19 #include <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <string.h>
24
25 #define CROSS_CLOCK_IMPLEMENTATION
26 #include <cross_clock/cross_clock.h>
27
28 #include <sandbox_slave.h>
29 #include <lv2/lv2plug.in/ns/extensions/ui/ui.h>
30
31 #include <xcb/xcb.h>
32 #include <xcb/xcb_icccm.h>
33 #include <signal.h>
34
35 typedef struct _app_t app_t;
36
37 struct _app_t {
38 sandbox_slave_t *sb;
39 LV2UI_Handle *handle;
40 const LV2UI_Idle_Interface *idle_iface;
41 const LV2UI_Resize *resize_iface;
42
43 xcb_connection_t *conn;
44 xcb_screen_t *screen;
45 xcb_drawable_t win;
46 xcb_drawable_t widget;
47 xcb_intern_atom_cookie_t cookie;
48 xcb_intern_atom_reply_t* reply;
49 xcb_intern_atom_cookie_t cookie2;
50 xcb_intern_atom_reply_t* reply2;
51 int w;
52 int h;
53 cross_clock_t clk_real;
54 };
55
56 static atomic_bool done = ATOMIC_VAR_INIT(false);
57
58 static inline void
_sig(int signum)59 _sig(int signum)
60 {
61 atomic_store_explicit(&done, true, memory_order_relaxed);
62 }
63
64 static inline int
_resize(void * data,int w,int h)65 _resize(void *data, int w, int h)
66 {
67 app_t *app= data;
68
69 app->w = w;
70 app->h = h;
71
72 const uint32_t values [2] = {app->w, app->h};
73 xcb_configure_window(app->conn, app->win,
74 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
75 xcb_flush(app->conn);
76
77 return 0;
78 }
79
80 static inline int
_init(sandbox_slave_t * sb,void * data)81 _init(sandbox_slave_t *sb, void *data)
82 {
83 app_t *app= data;
84
85 signal(SIGINT, _sig);
86
87 app->conn = xcb_connect(NULL, NULL);
88 app->screen = xcb_setup_roots_iterator(xcb_get_setup(app->conn)).data;
89 app->win = xcb_generate_id(app->conn);
90 const uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
91 const uint32_t values [2] = {
92 app->screen->white_pixel,
93 XCB_EVENT_MASK_STRUCTURE_NOTIFY
94 };
95
96 app->w = 640;
97 app->h = 360;
98 xcb_create_window(app->conn, XCB_COPY_FROM_PARENT, app->win, app->screen->root,
99 0, 0, app->w, app->h, 0,
100 XCB_WINDOW_CLASS_INPUT_OUTPUT, app->screen->root_visual, mask, values);
101
102 const char *title = sandbox_slave_title_get(sb);
103 if(title)
104 xcb_change_property(app->conn, XCB_PROP_MODE_REPLACE, app->win,
105 XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
106 strlen(title), title);
107
108 app->cookie = xcb_intern_atom(app->conn, 1, 12, "WM_PROTOCOLS");
109 app->reply = xcb_intern_atom_reply(app->conn, app->cookie, 0);
110
111 app->cookie2 = xcb_intern_atom(app->conn, 0, 16, "WM_DELETE_WINDOW");
112 app->reply2 = xcb_intern_atom_reply(app->conn, app->cookie2, 0);
113
114 xcb_change_property(app->conn,
115 XCB_PROP_MODE_REPLACE, app->win, (*app->reply).atom, 4, 32, 1, &(*app->reply2).atom);
116
117 xcb_map_window(app->conn, app->win);
118 xcb_flush(app->conn);
119
120 const LV2_Feature parent_feature = {
121 .URI = LV2_UI__parent,
122 .data = (void *)(uintptr_t)app->win
123 };
124
125 if(!(app->handle = sandbox_slave_instantiate(sb, &parent_feature, (uintptr_t *)&app->widget)))
126 return -1;
127 if(!app->widget)
128 return -1;
129
130 // clone size hints from widget to parent window
131 xcb_get_property_cookie_t reply = xcb_icccm_get_wm_size_hints(app->conn,
132 app->widget, XCB_ATOM_WM_NORMAL_HINTS);
133 xcb_size_hints_t size_hints;
134 xcb_icccm_get_wm_size_hints_reply(app->conn, reply, &size_hints, NULL);
135 xcb_icccm_set_wm_size_hints(app->conn, app->win, XCB_ATOM_WM_NORMAL_HINTS, &size_hints);
136 xcb_flush(app->conn);
137
138 app->idle_iface = sandbox_slave_extension_data(sb, LV2_UI__idleInterface);
139 app->resize_iface = sandbox_slave_extension_data(sb, LV2_UI__resize);
140
141 // work-around for broken lsp-plugins
142 if((uintptr_t)app->resize_iface == (uintptr_t)app->idle_iface)
143 {
144 app->resize_iface = NULL;
145 }
146
147 cross_clock_init(&app->clk_real, CROSS_CLOCK_REALTIME);
148
149 return 0;
150 }
151
152 static inline void
_run(sandbox_slave_t * sb,float update_rate,void * data)153 _run(sandbox_slave_t *sb, float update_rate, void *data)
154 {
155 app_t *app = data;
156 const unsigned ns = 1000000000 / update_rate;
157 struct timespec to;
158 cross_clock_gettime(&app->clk_real, &to);
159
160 while(!atomic_load_explicit(&done, memory_order_relaxed))
161 {
162 xcb_generic_event_t *e;
163 while((e = xcb_poll_for_event(app->conn)))
164 {
165 switch(e->response_type & ~0x80)
166 {
167 case XCB_CONFIGURE_NOTIFY:
168 {
169 const xcb_configure_notify_event_t *ev = (const xcb_configure_notify_event_t *)e;
170 if( (app->w != ev->width) || (app->h != ev->height) )
171 {
172 app->w = ev->width;
173 app->h = ev->height;
174
175 const uint32_t values [2] = {app->w, app->h};
176 xcb_configure_window(app->conn, app->widget,
177 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
178 &values);
179 xcb_flush(app->conn);
180
181 if(app->resize_iface)
182 {
183 app->resize_iface->ui_resize(app->handle, app->w, app->h);
184 }
185 }
186 break;
187 }
188 case XCB_CLIENT_MESSAGE:
189 {
190 const xcb_client_message_event_t *ev = (const xcb_client_message_event_t *)e;
191 if(ev->data.data32[0] == (*app->reply2).atom)
192 atomic_store_explicit(&done, true, memory_order_relaxed);
193 break;
194 }
195 }
196 free(e);
197 }
198
199 if(sandbox_slave_timedwait(sb, &to)) // timedout
200 {
201 struct timespec tf;
202
203 cross_clock_gettime(&app->clk_real, &tf);
204 const uint64_t dd = (tf.tv_sec - to.tv_sec) * 1000000000
205 + (tf.tv_nsec - to.tv_nsec);
206
207 #if 0
208 printf(":: %i, %lums\n", getpid(), dd/1000000);
209 #endif
210
211 if(dd <= ns)
212 {
213 if(app->idle_iface)
214 {
215 if(app->idle_iface->idle(app->handle))
216 atomic_store_explicit(&done, true, memory_order_relaxed);
217 }
218 }
219
220 to.tv_nsec += ns;
221 while(to.tv_nsec >= 1000000000)
222 {
223 to.tv_nsec -= 1000000000;
224 to.tv_sec += 1;
225 }
226 }
227 else
228 {
229 if(sandbox_slave_recv(sb))
230 atomic_store_explicit(&done, true, memory_order_relaxed);
231 }
232 }
233 }
234
235 static inline void
_deinit(void * data)236 _deinit(void *data)
237 {
238 app_t *app = data;
239
240 xcb_destroy_subwindows(app->conn, app->win);
241 xcb_destroy_window(app->conn, app->win);
242 xcb_disconnect(app->conn);
243
244 cross_clock_deinit(&app->clk_real);
245 }
246
247 static const sandbox_slave_driver_t driver = {
248 .init_cb = _init,
249 .run_cb = _run,
250 .deinit_cb = _deinit,
251 .resize_cb = _resize
252 };
253
254 int
main(int argc,char ** argv)255 main(int argc, char **argv)
256 {
257 static app_t app;
258 int res;
259
260 app.sb = sandbox_slave_new(argc, argv, &driver, &app, &res);
261 if(app.sb)
262 {
263 sandbox_slave_run(app.sb);
264 sandbox_slave_free(app.sb);
265 return res;
266 }
267
268 return res;
269 }
270