1 /******************************************************************************
2     Copyright (C) 2017 by Hugh Bailey <jim@obsproject.com>
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 2 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, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17 
18 #include <obs.h>
19 #include <util/dstr.h>
20 #include <util/platform.h>
21 #include <util/threading.h>
22 #include <util/circlebuf.h>
23 
24 #include "obs-scripting-internal.h"
25 #include "obs-scripting-callback.h"
26 #include "obs-scripting-config.h"
27 
28 #if COMPILE_LUA
29 extern obs_script_t *obs_lua_script_create(const char *path,
30 					   obs_data_t *settings);
31 extern bool obs_lua_script_load(obs_script_t *s);
32 extern void obs_lua_script_unload(obs_script_t *s);
33 extern void obs_lua_script_destroy(obs_script_t *s);
34 extern void obs_lua_load(void);
35 extern void obs_lua_unload(void);
36 
37 extern obs_properties_t *obs_lua_script_get_properties(obs_script_t *script);
38 extern void obs_lua_script_update(obs_script_t *script, obs_data_t *settings);
39 extern void obs_lua_script_save(obs_script_t *script);
40 #endif
41 
42 #if COMPILE_PYTHON
43 extern obs_script_t *obs_python_script_create(const char *path,
44 					      obs_data_t *settings);
45 extern bool obs_python_script_load(obs_script_t *s);
46 extern void obs_python_script_unload(obs_script_t *s);
47 extern void obs_python_script_destroy(obs_script_t *s);
48 extern void obs_python_load(void);
49 extern void obs_python_unload(void);
50 
51 extern obs_properties_t *obs_python_script_get_properties(obs_script_t *script);
52 extern void obs_python_script_update(obs_script_t *script,
53 				     obs_data_t *settings);
54 extern void obs_python_script_save(obs_script_t *script);
55 #endif
56 
57 pthread_mutex_t detach_mutex;
58 struct script_callback *detached_callbacks;
59 
60 static struct dstr file_filter = {0};
61 static bool scripting_loaded = false;
62 
63 static const char *supported_formats[] = {
64 #if COMPILE_LUA
65 	"lua",
66 #endif
67 #if COMPILE_PYTHON
68 	"py",
69 #endif
70 	NULL};
71 
72 /* -------------------------------------------- */
73 
74 static pthread_mutex_t defer_call_mutex;
75 static struct circlebuf defer_call_queue;
76 static bool defer_call_exit = false;
77 static os_sem_t *defer_call_semaphore;
78 static pthread_t defer_call_thread;
79 
80 struct defer_call {
81 	defer_call_cb call;
82 	void *cb;
83 };
84 
defer_thread(void * unused)85 static void *defer_thread(void *unused)
86 {
87 	UNUSED_PARAMETER(unused);
88 
89 	while (os_sem_wait(defer_call_semaphore) == 0) {
90 		struct defer_call info;
91 
92 		pthread_mutex_lock(&defer_call_mutex);
93 		if (defer_call_exit) {
94 			pthread_mutex_unlock(&defer_call_mutex);
95 			return NULL;
96 		}
97 
98 		circlebuf_pop_front(&defer_call_queue, &info, sizeof(info));
99 		pthread_mutex_unlock(&defer_call_mutex);
100 
101 		info.call(info.cb);
102 	}
103 
104 	return NULL;
105 }
106 
defer_call_post(defer_call_cb call,void * cb)107 void defer_call_post(defer_call_cb call, void *cb)
108 {
109 	struct defer_call info;
110 	info.call = call;
111 	info.cb = cb;
112 
113 	pthread_mutex_lock(&defer_call_mutex);
114 	if (!defer_call_exit)
115 		circlebuf_push_back(&defer_call_queue, &info, sizeof(info));
116 	pthread_mutex_unlock(&defer_call_mutex);
117 
118 	os_sem_post(defer_call_semaphore);
119 }
120 
121 /* -------------------------------------------- */
122 
obs_scripting_load(void)123 bool obs_scripting_load(void)
124 {
125 	circlebuf_init(&defer_call_queue);
126 
127 	if (pthread_mutex_init(&detach_mutex, NULL) != 0) {
128 		return false;
129 	}
130 	if (pthread_mutex_init(&defer_call_mutex, NULL) != 0) {
131 		pthread_mutex_destroy(&detach_mutex);
132 		return false;
133 	}
134 	if (os_sem_init(&defer_call_semaphore, 0) != 0) {
135 		pthread_mutex_destroy(&defer_call_mutex);
136 		pthread_mutex_destroy(&detach_mutex);
137 		return false;
138 	}
139 
140 	if (pthread_create(&defer_call_thread, NULL, defer_thread, NULL) != 0) {
141 		os_sem_destroy(defer_call_semaphore);
142 		pthread_mutex_destroy(&defer_call_mutex);
143 		pthread_mutex_destroy(&detach_mutex);
144 		return false;
145 	}
146 
147 #if COMPILE_LUA
148 	obs_lua_load();
149 #endif
150 
151 #if COMPILE_PYTHON
152 	obs_python_load();
153 #ifndef _WIN32 /* don't risk python startup load issues on windows */
154 	obs_scripting_load_python(NULL);
155 #endif
156 #endif
157 
158 	scripting_loaded = true;
159 	return true;
160 }
161 
obs_scripting_unload(void)162 void obs_scripting_unload(void)
163 {
164 	if (!scripting_loaded)
165 		return;
166 
167 		/* ---------------------- */
168 
169 #if COMPILE_LUA
170 	obs_lua_unload();
171 #endif
172 
173 #if COMPILE_PYTHON
174 	obs_python_unload();
175 #endif
176 
177 	dstr_free(&file_filter);
178 
179 	/* ---------------------- */
180 
181 	int total_detached = 0;
182 
183 	pthread_mutex_lock(&detach_mutex);
184 
185 	struct script_callback *cur = detached_callbacks;
186 	while (cur) {
187 		struct script_callback *next = cur->next;
188 		just_free_script_callback(cur);
189 		cur = next;
190 
191 		++total_detached;
192 	}
193 
194 	pthread_mutex_unlock(&detach_mutex);
195 	pthread_mutex_destroy(&detach_mutex);
196 
197 	blog(LOG_INFO, "[Scripting] Total detached callbacks: %d",
198 	     total_detached);
199 
200 	/* ---------------------- */
201 
202 	pthread_mutex_lock(&defer_call_mutex);
203 
204 	/* TODO */
205 
206 	defer_call_exit = true;
207 	circlebuf_free(&defer_call_queue);
208 
209 	pthread_mutex_unlock(&defer_call_mutex);
210 
211 	os_sem_post(defer_call_semaphore);
212 	pthread_join(defer_call_thread, NULL);
213 
214 	pthread_mutex_destroy(&defer_call_mutex);
215 	os_sem_destroy(defer_call_semaphore);
216 
217 	scripting_loaded = false;
218 }
219 
obs_scripting_supported_formats(void)220 const char **obs_scripting_supported_formats(void)
221 {
222 	return supported_formats;
223 }
224 
pointer_valid(const void * x,const char * name,const char * func)225 static inline bool pointer_valid(const void *x, const char *name,
226 				 const char *func)
227 {
228 	if (!x) {
229 		blog(LOG_WARNING, "obs-scripting: [%s] %s is null", func, name);
230 		return false;
231 	}
232 
233 	return true;
234 }
235 
236 #define ptr_valid(x) pointer_valid(x, #x, __FUNCTION__)
237 
obs_script_create(const char * path,obs_data_t * settings)238 obs_script_t *obs_script_create(const char *path, obs_data_t *settings)
239 {
240 	obs_script_t *script = NULL;
241 	const char *ext;
242 
243 	if (!scripting_loaded)
244 		return NULL;
245 	if (!ptr_valid(path))
246 		return NULL;
247 
248 	ext = strrchr(path, '.');
249 	if (!ext)
250 		return NULL;
251 
252 #if COMPILE_LUA
253 	if (strcmp(ext, ".lua") == 0) {
254 		script = obs_lua_script_create(path, settings);
255 	} else
256 #endif
257 #if COMPILE_PYTHON
258 		if (strcmp(ext, ".py") == 0) {
259 		script = obs_python_script_create(path, settings);
260 	} else
261 #endif
262 	{
263 		blog(LOG_WARNING, "Unsupported/unknown script type: %s", path);
264 	}
265 
266 	return script;
267 }
268 
obs_script_get_description(const obs_script_t * script)269 const char *obs_script_get_description(const obs_script_t *script)
270 {
271 	return ptr_valid(script) ? script->desc.array : NULL;
272 }
273 
obs_script_get_path(const obs_script_t * script)274 const char *obs_script_get_path(const obs_script_t *script)
275 {
276 	const char *path = ptr_valid(script) ? script->path.array : "";
277 	return path ? path : "";
278 }
279 
obs_script_get_file(const obs_script_t * script)280 const char *obs_script_get_file(const obs_script_t *script)
281 {
282 	const char *file = ptr_valid(script) ? script->file.array : "";
283 	return file ? file : "";
284 }
285 
obs_script_get_lang(const obs_script_t * script)286 enum obs_script_lang obs_script_get_lang(const obs_script_t *script)
287 {
288 	return ptr_valid(script) ? script->type : OBS_SCRIPT_LANG_UNKNOWN;
289 }
290 
obs_script_get_settings(obs_script_t * script)291 obs_data_t *obs_script_get_settings(obs_script_t *script)
292 {
293 	obs_data_t *settings;
294 
295 	if (!ptr_valid(script))
296 		return NULL;
297 
298 	settings = script->settings;
299 	obs_data_addref(settings);
300 	return settings;
301 }
302 
obs_script_get_properties(obs_script_t * script)303 obs_properties_t *obs_script_get_properties(obs_script_t *script)
304 {
305 	obs_properties_t *props = NULL;
306 
307 	if (!ptr_valid(script))
308 		return NULL;
309 #if COMPILE_LUA
310 	if (script->type == OBS_SCRIPT_LANG_LUA) {
311 		props = obs_lua_script_get_properties(script);
312 		goto out;
313 	}
314 #endif
315 #if COMPILE_PYTHON
316 	if (script->type == OBS_SCRIPT_LANG_PYTHON) {
317 		props = obs_python_script_get_properties(script);
318 		goto out;
319 	}
320 #endif
321 
322 out:
323 	if (!props)
324 		props = obs_properties_create();
325 	return props;
326 }
327 
obs_script_save(obs_script_t * script)328 obs_data_t *obs_script_save(obs_script_t *script)
329 {
330 	obs_data_t *settings;
331 
332 	if (!ptr_valid(script))
333 		return NULL;
334 
335 #if COMPILE_LUA
336 	if (script->type == OBS_SCRIPT_LANG_LUA) {
337 		obs_lua_script_save(script);
338 		goto out;
339 	}
340 #endif
341 #if COMPILE_PYTHON
342 	if (script->type == OBS_SCRIPT_LANG_PYTHON) {
343 		obs_python_script_save(script);
344 		goto out;
345 	}
346 #endif
347 
348 out:
349 	settings = script->settings;
350 	obs_data_addref(settings);
351 	return settings;
352 }
353 
clear_queue_signal(void * p_event)354 static void clear_queue_signal(void *p_event)
355 {
356 	os_event_t *event = p_event;
357 	os_event_signal(event);
358 }
359 
clear_call_queue(void)360 static void clear_call_queue(void)
361 {
362 	os_event_t *event;
363 	if (os_event_init(&event, OS_EVENT_TYPE_AUTO) != 0)
364 		return;
365 
366 	defer_call_post(clear_queue_signal, event);
367 
368 	os_event_wait(event);
369 	os_event_destroy(event);
370 }
371 
obs_script_update(obs_script_t * script,obs_data_t * settings)372 void obs_script_update(obs_script_t *script, obs_data_t *settings)
373 {
374 	if (!ptr_valid(script))
375 		return;
376 #if COMPILE_LUA
377 	if (script->type == OBS_SCRIPT_LANG_LUA) {
378 		obs_lua_script_update(script, settings);
379 	}
380 #endif
381 #if COMPILE_PYTHON
382 	if (script->type == OBS_SCRIPT_LANG_PYTHON) {
383 		obs_python_script_update(script, settings);
384 	}
385 #endif
386 }
387 
obs_script_reload(obs_script_t * script)388 bool obs_script_reload(obs_script_t *script)
389 {
390 	if (!scripting_loaded)
391 		return false;
392 	if (!ptr_valid(script))
393 		return false;
394 
395 #if COMPILE_LUA
396 	if (script->type == OBS_SCRIPT_LANG_LUA) {
397 		obs_lua_script_unload(script);
398 		clear_call_queue();
399 		obs_lua_script_load(script);
400 		goto out;
401 	}
402 #endif
403 #if COMPILE_PYTHON
404 	if (script->type == OBS_SCRIPT_LANG_PYTHON) {
405 		obs_python_script_unload(script);
406 		clear_call_queue();
407 		obs_python_script_load(script);
408 		goto out;
409 	}
410 #endif
411 
412 out:
413 	return script->loaded;
414 }
415 
obs_script_loaded(const obs_script_t * script)416 bool obs_script_loaded(const obs_script_t *script)
417 {
418 	return ptr_valid(script) ? script->loaded : false;
419 }
420 
obs_script_destroy(obs_script_t * script)421 void obs_script_destroy(obs_script_t *script)
422 {
423 	if (!script)
424 		return;
425 
426 #if COMPILE_LUA
427 	if (script->type == OBS_SCRIPT_LANG_LUA) {
428 		obs_lua_script_unload(script);
429 		obs_lua_script_destroy(script);
430 		return;
431 	}
432 #endif
433 #if COMPILE_PYTHON
434 	if (script->type == OBS_SCRIPT_LANG_PYTHON) {
435 		obs_python_script_unload(script);
436 		obs_python_script_destroy(script);
437 		return;
438 	}
439 #endif
440 }
441 
442 #if !COMPILE_PYTHON
obs_scripting_load_python(const char * python_path)443 bool obs_scripting_load_python(const char *python_path)
444 {
445 	UNUSED_PARAMETER(python_path);
446 	return false;
447 }
448 
obs_scripting_python_loaded(void)449 bool obs_scripting_python_loaded(void)
450 {
451 	return false;
452 }
453 
obs_scripting_python_runtime_linked(void)454 bool obs_scripting_python_runtime_linked(void)
455 {
456 	return (bool)true;
457 }
458 #endif
459