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