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