From 9eabfdbf1ed25d6130c115fcd2d3c7abdc19c773 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Mon, 25 Dec 2017 14:09:11 -0800 Subject: [PATCH] deps/obs-scripting: Add scripting support Allows Lua/Python scripting support. --- .gitignore | 3 + CMakeLists.txt | 1 + deps/CMakeLists.txt | 1 + deps/obs-scripting/CMakeLists.txt | 173 ++ deps/obs-scripting/cstrcache.cpp | 28 + deps/obs-scripting/cstrcache.h | 13 + deps/obs-scripting/obs-scripting-callback.h | 91 + deps/obs-scripting/obs-scripting-config.h.in | 22 + deps/obs-scripting/obs-scripting-internal.h | 51 + deps/obs-scripting/obs-scripting-logging.c | 59 + .../obs-scripting-lua-frontend.c | 315 +++ deps/obs-scripting/obs-scripting-lua-source.c | 762 ++++++++ deps/obs-scripting/obs-scripting-lua.c | 1287 +++++++++++++ deps/obs-scripting/obs-scripting-lua.h | 262 +++ .../obs-scripting-python-frontend.c | 361 ++++ .../obs-scripting-python-import.c | 149 ++ .../obs-scripting-python-import.h | 198 ++ deps/obs-scripting/obs-scripting-python.c | 1705 +++++++++++++++++ deps/obs-scripting/obs-scripting-python.h | 244 +++ deps/obs-scripting/obs-scripting.c | 455 +++++ deps/obs-scripting/obs-scripting.h | 73 + deps/obs-scripting/obslua/CMakeLists.txt | 41 + deps/obs-scripting/obslua/obslua.i | 99 + deps/obs-scripting/obspython/CMakeLists.txt | 60 + deps/obs-scripting/obspython/obspython.i | 106 + 25 files changed, 6559 insertions(+) create mode 100644 deps/obs-scripting/CMakeLists.txt create mode 100644 deps/obs-scripting/cstrcache.cpp create mode 100644 deps/obs-scripting/cstrcache.h create mode 100644 deps/obs-scripting/obs-scripting-callback.h create mode 100644 deps/obs-scripting/obs-scripting-config.h.in create mode 100644 deps/obs-scripting/obs-scripting-internal.h create mode 100644 deps/obs-scripting/obs-scripting-logging.c create mode 100644 deps/obs-scripting/obs-scripting-lua-frontend.c create mode 100644 deps/obs-scripting/obs-scripting-lua-source.c create mode 100644 deps/obs-scripting/obs-scripting-lua.c create mode 100644 deps/obs-scripting/obs-scripting-lua.h create mode 100644 deps/obs-scripting/obs-scripting-python-frontend.c create mode 100644 deps/obs-scripting/obs-scripting-python-import.c create mode 100644 deps/obs-scripting/obs-scripting-python-import.h create mode 100644 deps/obs-scripting/obs-scripting-python.c create mode 100644 deps/obs-scripting/obs-scripting-python.h create mode 100644 deps/obs-scripting/obs-scripting.c create mode 100644 deps/obs-scripting/obs-scripting.h create mode 100644 deps/obs-scripting/obslua/CMakeLists.txt create mode 100644 deps/obs-scripting/obslua/obslua.i create mode 100644 deps/obs-scripting/obspython/CMakeLists.txt create mode 100644 deps/obs-scripting/obspython/obspython.i diff --git a/.gitignore b/.gitignore index f77e28298..b1289c525 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ install-sh Makefile.in Makefile +#python +__pycache__ + #sphinx /docs/sphinx/_build/* !/docs/sphinx/_build/.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index e5f28d020..1323fc7bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ if(WIN32) endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +set(ENABLE_SCRIPTING OFF CACHE BOOL "" FORCE) include(ObsHelpers) include(ObsCpack) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 33704b2e9..333dc6a3b 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -12,6 +12,7 @@ endif() add_subdirectory(media-playback) add_subdirectory(file-updater) +add_subdirectory(obs-scripting) if(WIN32) add_subdirectory(blake2) diff --git a/deps/obs-scripting/CMakeLists.txt b/deps/obs-scripting/CMakeLists.txt new file mode 100644 index 000000000..4e43fc886 --- /dev/null +++ b/deps/obs-scripting/CMakeLists.txt @@ -0,0 +1,173 @@ +cmake_minimum_required(VERSION 2.8) +project(obs-scripting) + +if(MSVC) + set(obs-scripting_PLATFORM_DEPS + w32-pthreads) +endif() + +find_package(Luajit QUIET) +find_package(PythonLibs QUIET 3.4) +find_package(SWIG QUIET 2) + +set(COMPILE_PYTHON FALSE CACHE BOOL "" FORCE) +set(COMPILE_LUA FALSE CACHE BOOL "" FORCE) + +if(NOT SWIG_FOUND) + message(STATUS "Scripting: SWIG not found; scripting disabled") + return() +endif() + +if(NOT PYTHONLIBS_FOUND AND NOT LUAJIT_FOUND) + message(STATUS "Scripting: Neither Python 3 nor Luajit was found; scripting plugin disabled") + return() +endif() + +if(NOT LUAJIT_FOUND) + message(STATUS "Scripting: Luajit not found; Luajit support disabled") +else() + message(STATUS "Scripting: Luajit supported") + set(COMPILE_LUA TRUE CACHE BOOL "" FORCE) +endif() + +if(NOT PYTHONLIBS_FOUND) + message(STATUS "Scripting: Python 3 not found; Python support disabled") + set(PYTHON_FOUND FALSE) + set(PYTHONLIBS_FOUND FALSE) +else() + message(STATUS "Scripting: Python 3 supported") + set(PYTHON_FOUND TRUE) + set(COMPILE_PYTHON TRUE CACHE BOOL "" FORCE) + + get_filename_component(PYTHON_LIB "${PYTHON_LIBRARIES}" NAME) + string(REGEX REPLACE "\\.[^.]*$" "" PYTHON_LIB ${PYTHON_LIB}) + if(WIN32) + string(REGEX REPLACE "_d" "" PYTHON_LIB "${PYTHON_LIB}") + endif() +endif() + +set(ENABLE_SCRIPTING ON CACHE BOOL "" FORCE) + +if(UI_ENABLED) + set(EXTRA_LIBS obs-frontend-api) + include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/UI/obs-frontend-api") +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/obs-scripting-config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/obs-scripting-config.h") + +include(${SWIG_USE_FILE}) + +include_directories(${CMAKE_SOURCE_DIR}/libobs) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +if(PYTHONLIBS_FOUND) + include_directories(${PYTHON_INCLUDE_DIR}) + + set(obs-scripting-python_SOURCES + obs-scripting-python.c + ) + set(obs-scripting-python_HEADERS + obs-scripting-python.h + obs-scripting-python-import.h + ) + + if(UI_ENABLED) + set(obs-scripting-python_SOURCES + ${obs-scripting-python_SOURCES} + obs-scripting-python-frontend.c + ) + endif() + if(WIN32 OR APPLE) + set(obs-scripting-python_SOURCES + ${obs-scripting-python_SOURCES} + obs-scripting-python-import.c + ) + else() + set(EXTRA_LIBS ${EXTRA_LIBS} ${PYTHON_LIBRARIES}) + endif() +endif() + +if(LUAJIT_FOUND) + include_directories(${LUAJIT_INCLUDE_DIR}) + + set(obs-scripting-lua_SOURCES + obs-scripting-lua.c + obs-scripting-lua-source.c + ) + set(obs-scripting-lua_HEADERS + obs-scripting-lua.h + ) + if(UI_ENABLED) + set(obs-scripting-lua_SOURCES + ${obs-scripting-lua_SOURCES} + obs-scripting-lua-frontend.c + ) + endif() +endif() + +set(obs-scripting_SOURCES + obs-scripting.c + obs-scripting-logging.c + cstrcache.cpp + ) +set(obs-scripting_HEADERS + ${CMAKE_CURRENT_BINARY_DIR}/obs-scripting-config.h + obs-scripting.h + obs-scripting-callback.h + cstrcache.h + ) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/swig) + +if(PYTHONLIBS_FOUND) + set(SWIG_PY_RUNTIME swig/swigpyrun.h) + add_custom_command(OUTPUT ${SWIG_PY_RUNTIME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + PRE_BUILD + COMMAND ${SWIG_EXECUTABLE} -python -external-runtime ${SWIG_PY_RUNTIME} + COMMENT "Scripting plugin: Building Python SWIG interface header" + ) + set_source_files_properties(${SWIG_PY_RUNTIME} PROPERTIES GENERATED TRUE) +endif() + +if(LUAJIT_FOUND) + set(SWIG_LUA_RUNTIME swig/swigluarun.h) + add_custom_command(OUTPUT ${SWIG_LUA_RUNTIME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + PRE_BUILD + COMMAND ${SWIG_EXECUTABLE} -lua -external-runtime ${SWIG_LUA_RUNTIME} + COMMENT "Scripting: Building Lua SWIG interface header" + ) + set_source_files_properties(${SWIG_LUA_RUNTIME} PROPERTIES GENERATED TRUE) +endif() + +add_library(obs-scripting SHARED + ${obs-scripting_SOURCES} + ${obs-scripting_HEADERS} + ${obs-scripting-python_SOURCES} + ${obs-scripting-python_HEADERS} + ${obs-scripting-lua_SOURCES} + ${obs-scripting-lua_HEADERS} + ${SWIG_PY_RUNTIME} + ${SWIG_LUA_RUNTIME} + ) + +target_link_libraries(obs-scripting + libobs + ${LUAJIT_LIBRARIES} + ${EXTRA_LIBS} + ${obs-scripting_PLATFORM_DEPS} + ) + +if(PYTHONLIBS_FOUND) + add_subdirectory(obspython) +endif() + +if(LUAJIT_FOUND) + add_subdirectory(obslua) +endif() + +install_obs_core(obs-scripting) diff --git a/deps/obs-scripting/cstrcache.cpp b/deps/obs-scripting/cstrcache.cpp new file mode 100644 index 000000000..786583768 --- /dev/null +++ b/deps/obs-scripting/cstrcache.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "cstrcache.h" + +using namespace std; + +struct const_string_table { + unordered_map strings; +}; + +static struct const_string_table table; + +const char *cstrcache_get(const char *str) +{ + if (!str || !*str) + return ""; + + auto &strings = table.strings; + auto pair = strings.find(str); + + if (pair == strings.end()) { + strings[str] = str; + pair = strings.find(str); + } + + return pair->second.c_str(); +} diff --git a/deps/obs-scripting/cstrcache.h b/deps/obs-scripting/cstrcache.h new file mode 100644 index 000000000..8934cf627 --- /dev/null +++ b/deps/obs-scripting/cstrcache.h @@ -0,0 +1,13 @@ +#pragma once + +/* simple constant string cache table using STL unordered_map as storage */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char *cstrcache_get(const char *str); + +#ifdef __cplusplus +} +#endif diff --git a/deps/obs-scripting/obs-scripting-callback.h b/deps/obs-scripting/obs-scripting-callback.h new file mode 100644 index 000000000..ee317f3d4 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-callback.h @@ -0,0 +1,91 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "obs-scripting-internal.h" + +extern pthread_mutex_t detach_mutex; +extern struct script_callback *detached_callbacks; + +struct script_callback { + struct script_callback *next; + struct script_callback **p_prev_next; + + void (*on_remove)(void *p_cb); + obs_script_t *script; + calldata_t extra; + + bool removed; +}; + +static inline void *add_script_callback( + struct script_callback **first, + obs_script_t *script, + size_t extra_size) +{ + struct script_callback *cb = bzalloc(sizeof(*cb) + extra_size); + cb->script = script; + + struct script_callback *next = *first; + cb->next = next; + cb->p_prev_next = first; + if (next) next->p_prev_next = &cb->next; + *first = cb; + + return cb; +} + +static inline void remove_script_callback(struct script_callback *cb) +{ + cb->removed = true; + + struct script_callback *next = cb->next; + if (next) next->p_prev_next = cb->p_prev_next; + *cb->p_prev_next = cb->next; + + pthread_mutex_lock(&detach_mutex); + next = detached_callbacks; + cb->next = next; + if (next) next->p_prev_next = &cb->next; + cb->p_prev_next = &detached_callbacks; + detached_callbacks = cb; + pthread_mutex_unlock(&detach_mutex); + + if (cb->on_remove) + cb->on_remove(cb); +} + +static inline void just_free_script_callback(struct script_callback *cb) +{ + calldata_free(&cb->extra); + bfree(cb); +} + +static inline void free_script_callback(struct script_callback *cb) +{ + pthread_mutex_lock(&detach_mutex); + struct script_callback *next = cb->next; + if (next) next->p_prev_next = cb->p_prev_next; + *cb->p_prev_next = cb->next; + pthread_mutex_unlock(&detach_mutex); + + just_free_script_callback(cb); +} diff --git a/deps/obs-scripting/obs-scripting-config.h.in b/deps/obs-scripting/obs-scripting-config.h.in new file mode 100644 index 000000000..e3f60a601 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-config.h.in @@ -0,0 +1,22 @@ +#pragma once + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef ON +#define ON 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef OFF +#define OFF 0 +#endif + +#define PYTHON_LIB "@PYTHON_LIB@" +#define COMPILE_LUA @LUAJIT_FOUND@ +#define COMPILE_PYTHON @PYTHON_FOUND@ +#define UI_ENABLED @UI_ENABLED@ diff --git a/deps/obs-scripting/obs-scripting-internal.h b/deps/obs-scripting/obs-scripting-internal.h new file mode 100644 index 000000000..dade53a94 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-internal.h @@ -0,0 +1,51 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include "obs-scripting.h" + +struct obs_script { + enum obs_script_lang type; + bool loaded; + + obs_data_t *settings; + + struct dstr path; + struct dstr file; + struct dstr desc; +}; + +struct script_callback; +typedef void (*defer_call_cb)(void *param); + +extern void defer_call_post(defer_call_cb call, void *cb); + +extern void script_log(obs_script_t *script, int level, const char *format, ...); +extern void script_log_va(obs_script_t *script, int level, const char *format, + va_list args); + +#define script_error(script, format, ...) \ + script_log(script, LOG_ERROR, format, ##__VA_ARGS__) +#define script_warn(script, format, ...) \ + script_log(script, LOG_WARNING, format, ##__VA_ARGS__) +#define script_info(script, format, ...) \ + script_log(script, LOG_INFO, format, ##__VA_ARGS__) +#define script_debug(script, format, ...) \ + script_log(script, LOG_DEBUG, format, ##__VA_ARGS__) diff --git a/deps/obs-scripting/obs-scripting-logging.c b/deps/obs-scripting/obs-scripting-logging.c new file mode 100644 index 000000000..18c5fd5f6 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-logging.c @@ -0,0 +1,59 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-internal.h" +#include + +static scripting_log_handler_t callback = NULL; +static void *param = NULL; + +void script_log_va(obs_script_t *script, int level, const char *format, + va_list args) +{ + char msg[2048]; + const char *lang = "(Unknown)"; + size_t start_len; + + switch (script->type) { + case OBS_SCRIPT_LANG_UNKNOWN: lang = "(Unknown language)"; break; + case OBS_SCRIPT_LANG_LUA: lang = "Lua"; break; + case OBS_SCRIPT_LANG_PYTHON: lang = "Python"; break; + } + + start_len = snprintf(msg, sizeof(msg), "[%s: %s] ", + lang, script->file.array); + vsnprintf(msg + start_len, sizeof(msg) - start_len, format, args); + + if (callback) + callback(param, script, level, msg + start_len); + blog(level, "%s", msg); +} + +void script_log(obs_script_t *script, int level, const char *format, ...) +{ + va_list args; + va_start(args, format); + script_log_va(script, level, format, args); + va_end(args); +} + +void obs_scripting_set_log_callback(scripting_log_handler_t handler, + void *log_param) +{ + callback = handler; + param = log_param; +} diff --git a/deps/obs-scripting/obs-scripting-lua-frontend.c b/deps/obs-scripting/obs-scripting-lua-frontend.c new file mode 100644 index 000000000..71066507a --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua-frontend.c @@ -0,0 +1,315 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include + +#include "obs-scripting-lua.h" + +#define ls_get_libobs_obj(type, lua_index, obs_obj) \ + ls_get_libobs_obj_(script, #type " *", lua_index, obs_obj, \ + NULL, __FUNCTION__, __LINE__) +#define ls_push_libobs_obj(type, obs_obj, ownership) \ + ls_push_libobs_obj_(script, #type " *", obs_obj, ownership, \ + NULL, __FUNCTION__, __LINE__) +#define call_func(func, args, rets) \ + call_func_(script, cb->reg_idx, args, rets, #func, "frontend API") + +/* ----------------------------------- */ + +static int get_scene_names(lua_State *script) +{ + char **names = obs_frontend_get_scene_names(); + char **name = names; + int i = 0; + + lua_newtable(script); + + while (name && *name) { + lua_pushstring(script, *name); + lua_rawseti(script, -2, i++); + name++; + } + + bfree(names); + return 1; +} + +static int get_scenes(lua_State *script) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_scenes(&list); + + lua_createtable(script, (int)list.sources.num, 0); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + ls_push_libobs_obj(obs_source_t, source, false); + lua_rawseti(script, -2, (int)i++); + } + + da_free(list.sources); + return 1; +} + +static int get_current_scene(lua_State *script) +{ + obs_source_t *source = obs_frontend_get_current_scene(); + ls_push_libobs_obj(obs_source_t, source, false); + return 1; +} + +static int set_current_scene(lua_State *script) +{ + obs_source_t *source = NULL; + ls_get_libobs_obj(obs_source_t, 1, &source); + obs_frontend_set_current_scene(source); + return 0; +} + +static int get_transitions(lua_State *script) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_transitions(&list); + + lua_createtable(script, (int)list.sources.num, 0); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + ls_push_libobs_obj(obs_source_t, source, false); + lua_rawseti(script, -2, (int)i++); + } + + da_free(list.sources); + return 1; +} + +static int get_current_transition(lua_State *script) +{ + obs_source_t *source = obs_frontend_get_current_transition(); + ls_push_libobs_obj(obs_source_t, source, false); + return 1; +} + +static int set_current_transition(lua_State *script) +{ + obs_source_t *source = NULL; + ls_get_libobs_obj(obs_source_t, 1, &source); + obs_frontend_set_current_transition(source); + return 0; +} + +static int get_scene_collections(lua_State *script) +{ + char **names = obs_frontend_get_scene_collections(); + char **name = names; + int i = 0; + + lua_newtable(script); + + while (name && *name) { + lua_pushstring(script, *name); + lua_rawseti(script, -2, i++); + name++; + } + + bfree(names); + return 1; +} + +static int get_current_scene_collection(lua_State *script) +{ + char *name = obs_frontend_get_current_scene_collection(); + lua_pushstring(script, name); + bfree(name); + return 1; +} + +static int set_current_scene_collection(lua_State *script) +{ + if (lua_isstring(script, 1)) { + const char *name = lua_tostring(script, 1); + obs_frontend_set_current_scene_collection(name); + } + return 0; +} + +static int get_profiles(lua_State *script) +{ + char **names = obs_frontend_get_profiles(); + char **name = names; + int i = 0; + + lua_newtable(script); + + while (name && *name) { + lua_pushstring(script, *name); + lua_rawseti(script, -2, i++); + name++; + } + + bfree(names); + return 1; +} + +static int get_current_profile(lua_State *script) +{ + char *name = obs_frontend_get_current_profile(); + lua_pushstring(script, name); + bfree(name); + return 1; +} + +static int set_current_profile(lua_State *script) +{ + if (lua_isstring(script, 1)) { + const char *name = lua_tostring(script, 1); + obs_frontend_set_current_profile(name); + } + return 0; +} + +/* ----------------------------------- */ + +static void frontend_event_callback(enum obs_frontend_event event, void *priv) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_frontend_remove_event_callback(frontend_event_callback, cb); + return; + } + + lock_callback(); + + lua_pushinteger(script, (int)event); + call_func(frontend_event_callback, 1, 0); + + unlock_callback(); +} + +static int remove_event_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) { + remove_lua_obs_callback(cb); + } + return 0; +} + +static void add_event_callback_defer(void *cb) +{ + obs_frontend_add_event_callback(frontend_event_callback, cb); +} + +static int add_event_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(add_event_callback_defer, cb); + return 0; +} + +/* ----------------------------------- */ + +static void frontend_save_callback(obs_data_t *save_data, bool saving, + void *priv) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_frontend_remove_save_callback(frontend_save_callback, cb); + return; + } + + lock_callback(); + + ls_push_libobs_obj(obs_data_t, save_data, false); + lua_pushboolean(script, saving); + call_func(frontend_save_callback, 2, 0); + + unlock_callback(); +} + +static int remove_save_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) { + remove_lua_obs_callback(cb); + } + return 0; +} + +static void add_save_callback_defer(void *cb) +{ + obs_frontend_add_save_callback(frontend_save_callback, cb); +} + +static int add_save_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(add_save_callback_defer, cb); + return 0; +} + +/* ----------------------------------- */ + +void add_lua_frontend_funcs(lua_State *script) +{ + lua_getglobal(script, "obslua"); + +#define add_func(name) \ + do { \ + lua_pushstring(script, "obs_frontend_" #name); \ + lua_pushcfunction(script, name); \ + lua_rawset(script, -3); \ + } while (false) + + add_func(get_scene_names); + add_func(get_scenes); + add_func(get_current_scene); + add_func(set_current_scene); + add_func(get_transitions); + add_func(get_current_transition); + add_func(set_current_transition); + add_func(get_scene_collections); + add_func(get_current_scene_collection); + add_func(set_current_scene_collection); + add_func(get_profiles); + add_func(get_current_profile); + add_func(set_current_profile); + add_func(remove_event_callback); + add_func(add_event_callback); + add_func(remove_save_callback); + add_func(add_save_callback); +#undef add_func + + lua_pop(script, 1); +} diff --git a/deps/obs-scripting/obs-scripting-lua-source.c b/deps/obs-scripting/obs-scripting-lua-source.c new file mode 100644 index 000000000..9865c1e51 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua-source.c @@ -0,0 +1,762 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-lua.h" +#include "cstrcache.h" + +#include + +/* ========================================================================= */ + +static inline const char *get_table_string_(lua_State *script, int idx, + const char *name, const char *func) +{ + const char *str = ""; + + lua_pushstring(script, name); + lua_gettable(script, idx - 1); + if (!lua_isstring(script, -1)) + warn("%s: no item '%s' of type %s", func, name, "string"); + else + str = cstrcache_get(lua_tostring(script, -1)); + lua_pop(script, 1); + + return str; +} + +static inline int get_table_int_(lua_State *script, int idx, + const char *name, const char *func) +{ + int val = 0; + + lua_pushstring(script, name); + lua_gettable(script, idx - 1); + val = (int)lua_tointeger(script, -1); + lua_pop(script, 1); + + UNUSED_PARAMETER(func); + + return val; +} + +static inline void get_callback_from_table_(lua_State *script, int idx, + const char *name, int *p_reg_idx, const char *func) +{ + *p_reg_idx = LUA_REFNIL; + + lua_pushstring(script, name); + lua_gettable(script, idx - 1); + if (!lua_isfunction(script, -1)) { + if (!lua_isnil(script, -1)) { + warn("%s: item '%s' is not a function", func, name); + } + lua_pop(script, 1); + } else { + *p_reg_idx = luaL_ref(script, LUA_REGISTRYINDEX); + } +} + +#define get_table_string(script, idx, name) \ + get_table_string_(script, idx, name, __FUNCTION__) +#define get_table_int(script, idx, name) \ + get_table_int_(script, idx, name, __FUNCTION__) +#define get_callback_from_table(script, idx, name, p_reg_idx) \ + get_callback_from_table_(script, idx, name, p_reg_idx, __FUNCTION__) + +bool ls_get_libobs_obj_(lua_State * script, + const char *type, + int lua_idx, + void * libobs_out, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(script, type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + int ret = SWIG_ConvertPtr(script, lua_idx, libobs_out, info, 0); + if (!SWIG_IsOK(ret)) { + warn("%s:%d: SWIG failed to convert lua object to obs " + "object: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + return true; +} + +#define ls_get_libobs_obj(type, lua_index, obs_obj) \ + ls_get_libobs_obj_(ls->script, #type " *", lua_index, obs_obj, \ + ls->id, __FUNCTION__, __LINE__) + +bool ls_push_libobs_obj_(lua_State * script, + const char *type, + void * libobs_in, + bool ownership, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(script, type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + SWIG_NewPointerObj(script, libobs_in, info, (int)ownership); + return true; +} + +#define ls_push_libobs_obj(type, obs_obj, ownership) \ + ls_push_libobs_obj_(ls->script, #type " *", obs_obj, ownership, \ + ls->id, __FUNCTION__, __LINE__) + +/* ========================================================================= */ + +struct obs_lua_data; + +struct obs_lua_source { + struct obs_lua_script *data; + + lua_State * script; + const char *id; + const char *display_name; + int func_create; + int func_destroy; + int func_get_width; + int func_get_height; + int func_get_defaults; + int func_get_properties; + int func_update; + int func_activate; + int func_deactivate; + int func_show; + int func_hide; + int func_video_tick; + int func_video_render; + int func_save; + int func_load; + + pthread_mutex_t definition_mutex; + struct obs_lua_data *first_source; + + struct obs_lua_source *next; + struct obs_lua_source **p_prev_next; +}; + +extern pthread_mutex_t lua_source_def_mutex; +struct obs_lua_source *first_source_def = NULL; + +struct obs_lua_data { + obs_source_t * source; + struct obs_lua_source *ls; + int lua_data_ref; + + struct obs_lua_data *next; + struct obs_lua_data **p_prev_next; +}; + +#define call_func(name, args, rets) \ + call_func_(ls->script, ls->func_ ## name, args, rets, #name, \ + ls->display_name) +#define have_func(name) \ + (ls->func_ ## name != LUA_REFNIL) +#define ls_push_data() \ + lua_rawgeti(ls->script, LUA_REGISTRYINDEX, ld->lua_data_ref) +#define ls_pop(count) \ + lua_pop(ls->script, count) +#define lock_script() \ + struct obs_lua_script *__data = ls->data; \ + struct obs_lua_script *__prev_script = current_lua_script; \ + current_lua_script = __data; \ + pthread_mutex_lock(&__data->mutex); +#define unlock_script() \ + pthread_mutex_unlock(&__data->mutex); \ + current_lua_script = __prev_script; + +static const char *obs_lua_source_get_name(void *type_data) +{ + struct obs_lua_source *ls = type_data; + return ls->display_name; +} + +static void *obs_lua_source_create(obs_data_t *settings, obs_source_t *source) +{ + struct obs_lua_source *ls = obs_source_get_type_data(source); + struct obs_lua_data *data = NULL; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(create)) + goto fail; + + lock_script(); + + ls_push_libobs_obj(obs_data_t, settings, false); + ls_push_libobs_obj(obs_source_t, source, false); + call_func(create, 2, 1); + + int lua_data_ref = luaL_ref(ls->script, LUA_REGISTRYINDEX); + if (lua_data_ref != LUA_REFNIL) { + data = bmalloc(sizeof(*data)); + data->source = source; + data->ls = ls; + data->lua_data_ref = lua_data_ref; + } + + unlock_script(); + + if (data) { + struct obs_lua_data *next = ls->first_source; + data->next = next; + data->p_prev_next = &ls->first_source; + if (next) next->p_prev_next = &data->next; + ls->first_source = data; + } + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return data; +} + +static void call_destroy(struct obs_lua_data *ld) +{ + struct obs_lua_source *ls = ld->ls; + + ls_push_data(); + call_func(destroy, 1, 0); + luaL_unref(ls->script, LUA_REGISTRYINDEX, ld->lua_data_ref); + ld->lua_data_ref = LUA_REFNIL; +} + +static void obs_lua_source_destroy(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + struct obs_lua_data * next; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(destroy)) + goto fail; + + lock_script(); + call_destroy(ld); + unlock_script(); + +fail: + next = ld->next; + *ld->p_prev_next = next; + if (next) next->p_prev_next = ld->p_prev_next; + + bfree(data); + pthread_mutex_unlock(&ls->definition_mutex); +} + +static uint32_t obs_lua_source_get_width(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + uint32_t width = 0; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_width)) + goto fail; + + lock_script(); + + ls_push_data(); + if (call_func(get_width, 1, 1)) { + width = (uint32_t)lua_tointeger(ls->script, -1); + ls_pop(1); + } + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return width; +} + +static uint32_t obs_lua_source_get_height(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + uint32_t height = 0; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_height)) + goto fail; + + lock_script(); + + ls_push_data(); + if (call_func(get_height, 1, 1)) { + height = (uint32_t)lua_tointeger(ls->script, -1); + ls_pop(1); + } + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return height; +} + +static void obs_lua_source_get_defaults(void *type_data, obs_data_t *settings) +{ + struct obs_lua_source *ls = type_data; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_defaults)) + goto fail; + + lock_script(); + + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(get_defaults, 1, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static obs_properties_t *obs_lua_source_get_properties(void *data) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + obs_properties_t * props = NULL; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(get_properties)) + goto fail; + + lock_script(); + + ls_push_data(); + if (call_func(get_properties, 1, 1)) { + ls_get_libobs_obj(obs_properties_t, -1, &props); + ls_pop(1); + } + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); + return props; +} + +static void obs_lua_source_update(void *data, obs_data_t *settings) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(update)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(update, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +#define DEFINE_VOID_DATA_CALLBACK(name) \ + static void obs_lua_source_ ## name(void *data) \ + { \ + struct obs_lua_data * ld = data; \ + struct obs_lua_source *ls = ld->ls; \ + if (!have_func(name)) \ + return; \ + lock_script(); \ + ls_push_data(); \ + call_func(name, 1, 0); \ + unlock_script(); \ + } +DEFINE_VOID_DATA_CALLBACK(activate) +DEFINE_VOID_DATA_CALLBACK(deactivate) +DEFINE_VOID_DATA_CALLBACK(show) +DEFINE_VOID_DATA_CALLBACK(hide) +#undef DEFINE_VOID_DATA_CALLBACK + +static void obs_lua_source_video_tick(void *data, float seconds) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(video_tick)) + goto fail; + + lock_script(); + + ls_push_data(); + lua_pushnumber(ls->script, (double)seconds); + call_func(video_tick, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void obs_lua_source_video_render(void *data, gs_effect_t *effect) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(video_render)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(gs_effect_t, effect, false); + call_func(video_render, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void obs_lua_source_save(void *data, obs_data_t *settings) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(save)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(save, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void obs_lua_source_load(void *data, obs_data_t *settings) +{ + struct obs_lua_data * ld = data; + struct obs_lua_source *ls = ld->ls; + + pthread_mutex_lock(&ls->definition_mutex); + if (!ls->script) + goto fail; + if (!have_func(load)) + goto fail; + + lock_script(); + + ls_push_data(); + ls_push_libobs_obj(obs_data_t, settings, false); + call_func(load, 2, 0); + + unlock_script(); + +fail: + pthread_mutex_unlock(&ls->definition_mutex); +} + +static void source_type_unload(struct obs_lua_source *ls) +{ +#define unref(name) \ + luaL_unref(ls->script, LUA_REGISTRYINDEX, name); \ + name = LUA_REFNIL + + unref(ls->func_create); + unref(ls->func_destroy); + unref(ls->func_get_width); + unref(ls->func_get_height); + unref(ls->func_get_defaults); + unref(ls->func_get_properties); + unref(ls->func_update); + unref(ls->func_activate); + unref(ls->func_deactivate); + unref(ls->func_show); + unref(ls->func_hide); + unref(ls->func_video_tick); + unref(ls->func_video_render); + unref(ls->func_save); + unref(ls->func_load); +#undef unref +} + +static void obs_lua_source_free_type_data(void *type_data) +{ + struct obs_lua_source *ls = type_data; + + pthread_mutex_lock(&ls->definition_mutex); + + if (ls->script) { + lock_script(); + source_type_unload(ls); + unlock_script(); + ls->script = NULL; + } + + pthread_mutex_unlock(&ls->definition_mutex); + pthread_mutex_destroy(&ls->definition_mutex); + bfree(ls); +} + +EXPORT void obs_enable_source_type(const char *name, bool enable); + +static inline struct obs_lua_source *find_existing(const char *id) +{ + struct obs_lua_source *existing = NULL; + + pthread_mutex_lock(&lua_source_def_mutex); + struct obs_lua_source *ls = first_source_def; + while (ls) { + /* can compare pointers here due to string table */ + if (ls->id == id) { + existing = ls; + break; + } + + ls = ls->next; + } + pthread_mutex_unlock(&lua_source_def_mutex); + + return existing; +} + +static int obs_lua_register_source(lua_State *script) +{ + struct obs_lua_source ls = {0}; + struct obs_lua_source *existing = NULL; + struct obs_lua_source *v = NULL; + struct obs_source_info info = {0}; + const char *id; + + if (!verify_args1(script, is_table)) + goto fail; + + id = get_table_string(script, -1, "id"); + if (!id || !*id) + goto fail; + + /* redefinition */ + existing = find_existing(id); + if (existing) { + if (existing->script) { + existing = NULL; + goto fail; + } + + pthread_mutex_lock(&existing->definition_mutex); + } + + v = existing ? existing : &ls; + + v->script = script; + v->id = id; + + info.id = v->id; + info.type = (enum obs_source_type)get_table_int(script, -1, "type"); + + info.output_flags = get_table_int(script, -1, "output_flags"); + + lua_pushstring(script, "get_name"); + lua_gettable(script, -2); + if (lua_pcall(script, 0, 1, 0) == 0) { + v->display_name = cstrcache_get(lua_tostring(script, -1)); + lua_pop(script, 1); + } + + if (!v->display_name || + !*v->display_name || + !*info.id || + !info.output_flags) + goto fail; + +#define get_callback(val) \ + do { \ + get_callback_from_table(script, -1, #val, &v->func_ ## val); \ + info.val = obs_lua_source_ ## val; \ + } while (false) + + get_callback(create); + get_callback(destroy); + get_callback(get_width); + get_callback(get_height); + get_callback(get_properties); + get_callback(update); + get_callback(activate); + get_callback(deactivate); + get_callback(show); + get_callback(hide); + get_callback(video_tick); + get_callback(video_render); + get_callback(save); + get_callback(load); +#undef get_callback + + get_callback_from_table(script, -1, "get_defaults", + &v->func_get_defaults); + + if (!existing) { + ls.data = current_lua_script; + + pthread_mutex_init(&ls.definition_mutex, NULL); + info.type_data = bmemdup(&ls, sizeof(ls)); + info.free_type_data = obs_lua_source_free_type_data; + info.get_name = obs_lua_source_get_name; + info.get_defaults2 = obs_lua_source_get_defaults; + obs_register_source(&info); + + pthread_mutex_lock(&lua_source_def_mutex); + v = info.type_data; + + struct obs_lua_source *next = first_source_def; + v->next = next; + if (next) next->p_prev_next = &v->next; + v->p_prev_next = &first_source_def; + first_source_def = v; + + pthread_mutex_unlock(&lua_source_def_mutex); + } else { + existing->script = script; + existing->data = current_lua_script; + obs_enable_source_type(id, true); + + struct obs_lua_data *ld = v->first_source; + while (ld) { + struct obs_lua_source *ls = v; + + if (have_func(create)) { + obs_source_t *source = ld->source; + obs_data_t *settings = obs_source_get_settings( + source); + + ls_push_libobs_obj(obs_data_t, settings, false); + ls_push_libobs_obj(obs_source_t, source, false); + call_func(create, 2, 1); + + ld->lua_data_ref = luaL_ref(ls->script, + LUA_REGISTRYINDEX); + obs_data_release(settings); + } + + ld = ld->next; + } + } + +fail: + if (existing) { + pthread_mutex_unlock(&existing->definition_mutex); + } + return 0; +} + +/* ========================================================================= */ + +void add_lua_source_functions(lua_State *script) +{ + lua_getglobal(script, "obslua"); + + lua_pushstring(script, "obs_register_source"); + lua_pushcfunction(script, obs_lua_register_source); + lua_rawset(script, -3); + + lua_pop(script, 1); +} + +static inline void undef_source_type(struct obs_lua_script *data, + struct obs_lua_source *ls) +{ + pthread_mutex_lock(&ls->definition_mutex); + pthread_mutex_lock(&data->mutex); + + obs_enable_source_type(ls->id, false); + + struct obs_lua_data *ld = ls->first_source; + while (ld) { + call_destroy(ld); + ld = ld->next; + } + + source_type_unload(ls); + ls->script = NULL; + + pthread_mutex_unlock(&data->mutex); + pthread_mutex_unlock(&ls->definition_mutex); +} + +void undef_lua_script_sources(struct obs_lua_script *data) +{ + pthread_mutex_lock(&lua_source_def_mutex); + + struct obs_lua_source *def = first_source_def; + while (def) { + if (def->script == data->script) + undef_source_type(data, def); + def = def->next; + } + + pthread_mutex_unlock(&lua_source_def_mutex); +} diff --git a/deps/obs-scripting/obs-scripting-lua.c b/deps/obs-scripting/obs-scripting-lua.c new file mode 100644 index 000000000..cf73f0836 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua.c @@ -0,0 +1,1287 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-lua.h" +#include "obs-scripting-config.h" +#include +#include +#include + +#include + +/* ========================================================================= */ + +#if ARCH_BITS == 64 +# define ARCH_DIR "64bit" +#else +# define ARCH_DIR "32bit" +#endif + +static const char *startup_script_template = "\ +for val in pairs(package.preload) do\n\ + package.preload[val] = nil\n\ +end\n\ +require \"obslua\"\n\ +package.path = package.path .. \"%s\"\n"; + +static const char *get_script_path_func = "\ +function script_path()\n\ + return \"%s\"\n\ +end\n\ +package.path = package.path .. \";\" .. script_path() .. \"/?.lua\"\n"; + +static char *startup_script = NULL; + +static pthread_mutex_t tick_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct obs_lua_script *first_tick_script = NULL; + +pthread_mutex_t lua_source_def_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define ls_get_libobs_obj(type, lua_index, obs_obj) \ + ls_get_libobs_obj_(script, #type " *", lua_index, obs_obj, \ + NULL, __FUNCTION__, __LINE__) +#define ls_push_libobs_obj(type, obs_obj, ownership) \ + ls_push_libobs_obj_(script, #type " *", obs_obj, ownership, \ + NULL, __FUNCTION__, __LINE__) +#define call_func(name, args, rets) \ + call_func_(script, cb->reg_idx, \ + args, rets, #name, __FUNCTION__) + +/* ========================================================================= */ + +static void add_hook_functions(lua_State *script); +static int obs_lua_remove_tick_callback(lua_State *script); +static int obs_lua_remove_main_render_callback(lua_State *script); + +#if UI_ENABLED +void add_lua_frontend_funcs(lua_State *script); +#endif + +static bool load_lua_script(struct obs_lua_script *data) +{ + struct dstr str = {0}; + bool success = false; + int ret; + + lua_State *script = luaL_newstate(); + if (!script) { + script_warn(&data->base, "Failed to create new lua state"); + goto fail; + } + + pthread_mutex_lock(&data->mutex); + + luaL_openlibs(script); + + if (luaL_dostring(script, startup_script) != 0) { + script_warn(&data->base, "Error executing startup script 1: %s", + lua_tostring(script, -1)); + goto fail; + } + + dstr_printf(&str, get_script_path_func, data->dir.array); + ret = luaL_dostring(script, str.array); + dstr_free(&str); + + if (ret != 0) { + script_warn(&data->base, "Error executing startup script 2: %s", + lua_tostring(script, -1)); + goto fail; + } + + add_lua_source_functions(script); + add_hook_functions(script); +#if UI_ENABLED + add_lua_frontend_funcs(script); +#endif + + if (luaL_loadfile(script, data->base.path.array) != 0) { + script_warn(&data->base, "Error loading file: %s", + lua_tostring(script, -1)); + goto fail; + } + + if (lua_pcall(script, 0, LUA_MULTRET, 0) != 0) { + script_warn(&data->base, "Error running file: %s", + lua_tostring(script, -1)); + goto fail; + } + + ret = lua_gettop(script); + if (ret == 1 && lua_isboolean(script, -1)) { + bool success = lua_toboolean(script, -1); + + if (!success) { + goto fail; + } + } + + lua_getglobal(script, "script_tick"); + if (lua_isfunction(script, -1)) { + pthread_mutex_lock(&tick_mutex); + + struct obs_lua_script *next = first_tick_script; + data->next_tick = next; + data->p_prev_next_tick = &first_tick_script; + if (next) next->p_prev_next_tick = &data->next_tick; + first_tick_script = data; + + data->tick = luaL_ref(script, LUA_REGISTRYINDEX); + + pthread_mutex_unlock(&tick_mutex); + } + + lua_getglobal(script, "script_properties"); + if (lua_isfunction(script, -1)) + data->get_properties = luaL_ref(script, LUA_REGISTRYINDEX); + else + data->get_properties = LUA_REFNIL; + + lua_getglobal(script, "script_update"); + if (lua_isfunction(script, -1)) + data->update = luaL_ref(script, LUA_REGISTRYINDEX); + else + data->update = LUA_REFNIL; + + lua_getglobal(script, "script_save"); + if (lua_isfunction(script, -1)) + data->save = luaL_ref(script, LUA_REGISTRYINDEX); + else + data->save = LUA_REFNIL; + + current_lua_script = data; + + lua_getglobal(script, "script_defaults"); + if (lua_isfunction(script, -1)) { + ls_push_libobs_obj(obs_data_t, data->base.settings, false); + if (lua_pcall(script, 1, 0, 0) != 0) { + script_warn(&data->base, "Error calling " + "script_defaults: %s", + lua_tostring(script, -1)); + } + } + + lua_getglobal(script, "script_description"); + if (lua_isfunction(script, -1)) { + if (lua_pcall(script, 0, 1, 0) != 0) { + script_warn(&data->base, "Error calling " + "script_defaults: %s", + lua_tostring(script, -1)); + } else { + const char *desc = lua_tostring(script, -1); + dstr_copy(&data->base.desc, desc); + } + } + + lua_getglobal(script, "script_load"); + if (lua_isfunction(script, -1)) { + ls_push_libobs_obj(obs_data_t, data->base.settings, false); + if (lua_pcall(script, 1, 0, 0) != 0) { + script_warn(&data->base, "Error calling " + "script_load: %s", + lua_tostring(script, -1)); + } + } + + data->script = script; + success = true; + +fail: + if (script) { + lua_settop(script, 0); + pthread_mutex_unlock(&data->mutex); + } + + if (!success) { + lua_close(script); + } + + current_lua_script = NULL; + return success; +} + +/* -------------------------------------------- */ + +THREAD_LOCAL struct lua_obs_callback *current_lua_cb = NULL; +THREAD_LOCAL struct obs_lua_script *current_lua_script = NULL; + +/* -------------------------------------------- */ + +struct lua_obs_timer { + struct lua_obs_timer *next; + struct lua_obs_timer **p_prev_next; + + uint64_t last_ts; + uint64_t interval; +}; + +static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct lua_obs_timer *first_timer = NULL; + +static inline void lua_obs_timer_init(struct lua_obs_timer *timer) +{ + pthread_mutex_lock(&timer_mutex); + + struct lua_obs_timer *next = first_timer; + timer->next = next; + timer->p_prev_next = &first_timer; + if (next) next->p_prev_next = &timer->next; + first_timer = timer; + + pthread_mutex_unlock(&timer_mutex); +} + +static inline void lua_obs_timer_remove(struct lua_obs_timer *timer) +{ + struct lua_obs_timer *next = timer->next; + if (next) next->p_prev_next = timer->p_prev_next; + *timer->p_prev_next = timer->next; +} + +static inline struct lua_obs_callback *lua_obs_timer_cb( + struct lua_obs_timer *timer) +{ + return &((struct lua_obs_callback *)timer)[-1]; +} + +static int timer_remove(lua_State *script) +{ + if (!is_function(script, 1)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void timer_call(struct script_callback *p_cb) +{ + struct lua_obs_callback *cb = (struct lua_obs_callback *)p_cb; + + if (p_cb->removed) + return; + + lock_callback(); + call_func_(cb->script, cb->reg_idx, 0, 0, "timer_cb", __FUNCTION__); + unlock_callback(); +} + +static void defer_timer_init(void *p_cb) +{ + struct lua_obs_callback *cb = p_cb; + struct lua_obs_timer *timer = lua_obs_callback_extra_data(cb); + lua_obs_timer_init(timer); +} + +static int timer_add(lua_State *script) +{ + if (!is_function(script, 1)) + return 0; + int ms = (int)lua_tointeger(script, 2); + if (!ms) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback_extra(script, 1, + sizeof(struct lua_obs_timer)); + struct lua_obs_timer *timer = lua_obs_callback_extra_data(cb); + + timer->interval = (uint64_t)ms * 1000000ULL; + timer->last_ts = obs_get_video_frame_time(); + + defer_call_post(defer_timer_init, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void obs_lua_main_render_callback(void *priv, uint32_t cx, uint32_t cy) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_remove_main_render_callback(obs_lua_main_render_callback, + cb); + return; + } + + lock_callback(); + + lua_pushinteger(script, (lua_Integer)cx); + lua_pushinteger(script, (lua_Integer)cy); + call_func(obs_lua_main_render_callback, 2, 0); + + unlock_callback(); +} + +static int obs_lua_remove_main_render_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_add_render(void *cb) +{ + obs_add_main_render_callback(obs_lua_main_render_callback, cb); +} + +static int obs_lua_add_main_render_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(defer_add_render, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void obs_lua_tick_callback(void *priv, float seconds) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + obs_remove_tick_callback(obs_lua_tick_callback, cb); + return; + } + + lock_callback(); + + lua_pushnumber(script, (lua_Number)seconds); + call_func(obs_lua_tick_callback, 2, 0); + + unlock_callback(); +} + +static int obs_lua_remove_tick_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_add_tick(void *cb) +{ + obs_add_tick_callback(obs_lua_tick_callback, cb); +} + +static int obs_lua_add_tick_callback(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 1); + defer_call_post(defer_add_tick, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback(void *priv, calldata_t *cd) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(); + + ls_push_libobs_obj(calldata_t, cd, false); + call_func(calldata_signal_callback, 1, 0); + + unlock_callback(); +} + +static int obs_lua_signal_handler_disconnect(lua_State *script) +{ + signal_handler_t *handler; + const char *signal; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + signal = lua_tostring(script, 2); + if (!signal) + return 0; + if (!is_function(script, 3)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 3); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + const char *cb_signal = + calldata_string(&cb->base.extra, "signal"); + + if (cb_signal && + strcmp(signal, cb_signal) != 0 && + handler == cb_handler) + break; + + cb = find_next_lua_obs_callback(script, cb, 3); + } + + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_connect(void *p_cb) +{ + struct script_callback *cb = p_cb; + + signal_handler_t *handler = calldata_ptr(&cb->extra, "handler"); + const char *signal = calldata_string(&cb->extra, "signal"); + signal_handler_connect(handler, signal, calldata_signal_callback, cb); +} + +static int obs_lua_signal_handler_connect(lua_State *script) +{ + signal_handler_t *handler; + const char *signal; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + signal = lua_tostring(script, 2); + if (!signal) + return 0; + if (!is_function(script, 3)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 3); + calldata_set_ptr(&cb->base.extra, "handler", handler); + calldata_set_string(&cb->base.extra, "signal", signal); + defer_call_post(defer_connect, cb); + return 0; +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback_global(void *priv, const char *signal, + calldata_t *cd) +{ + struct lua_obs_callback *cb = priv; + lua_State *script = cb->script; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(); + + lua_pushstring(script, signal); + ls_push_libobs_obj(calldata_t, cd, false); + call_func(calldata_signal_callback_global, 2, 0); + + unlock_callback(); +} + +static int obs_lua_signal_handler_disconnect_global(lua_State *script) +{ + signal_handler_t *handler; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + if (!is_function(script, 2)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 3); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + + if (handler == cb_handler) + break; + + cb = find_next_lua_obs_callback(script, cb, 3); + } + + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static void defer_connect_global(void *p_cb) +{ + struct script_callback *cb = p_cb; + + signal_handler_t *handler = calldata_ptr(&cb->extra, "handler"); + signal_handler_connect_global(handler, + calldata_signal_callback_global, cb); +} + +static int obs_lua_signal_handler_connect_global(lua_State *script) +{ + signal_handler_t *handler; + + if (!ls_get_libobs_obj(signal_handler_t, 1, &handler)) + return 0; + if (!is_function(script, 2)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 2); + calldata_set_ptr(&cb->base.extra, "handler", handler); + defer_call_post(defer_connect_global, cb); + return 0; +} + +/* -------------------------------------------- */ + +static bool enum_sources_proc(void *param, obs_source_t *source) +{ + lua_State *script = param; + + obs_source_get_ref(source); + ls_push_libobs_obj(obs_source_t, source, false); + + size_t idx = lua_rawlen(script, -2); + lua_rawseti(script, -2, (int)idx + 1); + return true; +} + +static int enum_sources(lua_State *script) +{ + lua_newtable(script); + obs_enum_sources(enum_sources_proc, script); + return 1; +} + +/* -------------------------------------------- */ + +static bool enum_items_proc(obs_scene_t *scene, obs_sceneitem_t *item, + void *param) +{ + lua_State *script = param; + + UNUSED_PARAMETER(scene); + + obs_sceneitem_addref(item); + ls_push_libobs_obj(obs_sceneitem_t, item, false); + lua_rawseti(script, -2, (int)lua_rawlen(script, -2) + 1); + return true; +} + +static int scene_enum_items(lua_State *script) +{ + obs_scene_t *scene; + if (!ls_get_libobs_obj(obs_scene_t, 1, &scene)) + return 0; + + lua_newtable(script); + obs_scene_enum_items(scene, enum_items_proc, script); + return 1; +} + +/* -------------------------------------------- */ + +static void defer_hotkey_unregister(void *p_cb) +{ + obs_hotkey_unregister((obs_hotkey_id)(uintptr_t)p_cb); +} + +static void on_remove_hotkey(void *p_cb) +{ + struct lua_obs_callback *cb = p_cb; + obs_hotkey_id id = (obs_hotkey_id)calldata_int(&cb->base.extra, "id"); + + if (id != OBS_INVALID_HOTKEY_ID) + defer_call_post(defer_hotkey_unregister, (void*)(uintptr_t)id); +} + +static void hotkey_pressed(void *p_cb, bool pressed) +{ + struct lua_obs_callback *cb = p_cb; + lua_State *script = cb->script; + + if (cb->base.removed) + return; + + lock_callback(); + + lua_pushboolean(script, pressed); + call_func(hotkey_pressed, 1, 0); + + unlock_callback(); +} + +static void defer_hotkey_pressed(void *p_cb) +{ + hotkey_pressed(p_cb, true); +} + +static void defer_hotkey_unpressed(void *p_cb) +{ + hotkey_pressed(p_cb, false); +} + +static void hotkey_callback(void *p_cb, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + struct lua_obs_callback *cb = p_cb; + + if (cb->base.removed) + return; + + if (pressed) + defer_call_post(defer_hotkey_pressed, cb); + else + defer_call_post(defer_hotkey_unpressed, cb); + + UNUSED_PARAMETER(hotkey); + UNUSED_PARAMETER(id); +} + +static int hotkey_unregister(lua_State *script) +{ + if (!verify_args1(script, is_function)) + return 0; + + struct lua_obs_callback *cb = find_lua_obs_callback(script, 1); + if (cb) remove_lua_obs_callback(cb); + return 0; +} + +static int hotkey_register_frontend(lua_State *script) +{ + obs_hotkey_id id; + + const char *name = lua_tostring(script, 1); + if (!name) + return 0; + const char *desc = lua_tostring(script, 2); + if (!desc) + return 0; + if (!is_function(script, 3)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 3); + cb->base.on_remove = on_remove_hotkey; + id = obs_hotkey_register_frontend(name, desc, hotkey_callback, cb); + calldata_set_int(&cb->base.extra, "id", id); + lua_pushinteger(script, (lua_Integer)id); + + if (id == OBS_INVALID_HOTKEY_ID) + remove_lua_obs_callback(cb); + return 1; +} + +/* -------------------------------------------- */ + +static bool button_prop_clicked(obs_properties_t *props, obs_property_t *p, + void *p_cb) +{ + struct lua_obs_callback *cb = p_cb; + lua_State *script = cb->script; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(); + + if (!ls_push_libobs_obj(obs_properties_t, props, false)) + goto fail; + if (!ls_push_libobs_obj(obs_property_t, p, false)) { + lua_pop(script, 1); + goto fail; + } + + call_func(button_prop_clicked, 2, 1); + if (lua_isboolean(script, -1)) + ret = lua_toboolean(script, -1); + +fail: + unlock_callback(); + + return ret; +} + +static int properties_add_button(lua_State *script) +{ + obs_properties_t *props; + obs_property_t *p; + + if (!ls_get_libobs_obj(obs_properties_t, 1, &props)) + return 0; + const char *name = lua_tostring(script, 2); + if (!name) + return 0; + const char *text = lua_tostring(script, 3); + if (!text) + return 0; + if (!is_function(script, 4)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 4); + p = obs_properties_add_button2(props, name, text, button_prop_clicked, + cb); + + if (!p || !ls_push_libobs_obj(obs_property_t, p, false)) + return 0; + return 1; +} + +/* -------------------------------------------- */ + +static bool modified_callback(void *p_cb, obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) +{ + struct lua_obs_callback *cb = p_cb; + lua_State *script = cb->script; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(); + if (!ls_push_libobs_obj(obs_properties_t, props, false)) { + goto fail; + } + if (!ls_push_libobs_obj(obs_property_t, p, false)) { + lua_pop(script, 1); + goto fail; + } + if (!ls_push_libobs_obj(obs_data_t, settings, false)) { + lua_pop(script, 2); + goto fail; + } + + call_func(modified_callback, 3, 1); + if (lua_isboolean(script, -1)) + ret = lua_toboolean(script, -1); + +fail: + unlock_callback(); + return ret; +} + +static int property_set_modified_callback(lua_State *script) +{ + obs_property_t *p; + + if (!ls_get_libobs_obj(obs_property_t, 1, &p)) + return 0; + if (!is_function(script, 2)) + return 0; + + struct lua_obs_callback *cb = add_lua_obs_callback(script, 2); + obs_property_set_modified_callback2(p, modified_callback, cb); + return 0; +} + +/* -------------------------------------------- */ + +static int remove_current_callback(lua_State *script) +{ + UNUSED_PARAMETER(script); + if (current_lua_cb) + remove_lua_obs_callback(current_lua_cb); + return 0; +} + +/* -------------------------------------------- */ + +static int calldata_source(lua_State *script) +{ + calldata_t *cd; + const char *str; + int ret = 0; + + if (!ls_get_libobs_obj(calldata_t, 1, &cd)) + goto fail; + str = lua_tostring(script, 2); + if (!str) + goto fail; + + obs_source_t *source = calldata_ptr(cd, str); + if (ls_push_libobs_obj(obs_source_t, source, false)) + ++ret; + +fail: + return ret; +} + +static int calldata_sceneitem(lua_State *script) +{ + calldata_t *cd; + const char *str; + int ret = 0; + + if (!ls_get_libobs_obj(calldata_t, 1, &cd)) + goto fail; + str = lua_tostring(script, 2); + if (!str) + goto fail; + + obs_sceneitem_t *sceneitem = calldata_ptr(cd, str); + if (ls_push_libobs_obj(obs_sceneitem_t, sceneitem, false)) + ++ret; + +fail: + return ret; +} + +/* -------------------------------------------- */ + +static int source_list_release(lua_State *script) +{ + size_t count = lua_rawlen(script, 1); + for (size_t i = 0; i < count; i++) { + obs_source_t *source; + + lua_rawgeti(script, 1, (int)i + 1); + ls_get_libobs_obj(obs_source_t, -1, &source); + lua_pop(script, 1); + + obs_source_release(source); + } + return 0; +} + +static int sceneitem_list_release(lua_State *script) +{ + size_t count = lua_rawlen(script, 1); + for (size_t i = 0; i < count; i++) { + obs_sceneitem_t *item; + + lua_rawgeti(script, 1, (int)i + 1); + ls_get_libobs_obj(obs_sceneitem_t, -1, &item); + lua_pop(script, 1); + + obs_sceneitem_release(item); + } + return 0; +} + +/* -------------------------------------------- */ + +static int hook_print(lua_State *script) +{ + struct obs_lua_script *data = current_lua_script; + const char *msg = lua_tostring(script, 1); + if (!msg) + return 0; + + script_info(&data->base, "%s", msg); + return 0; +} + +static int hook_error(lua_State *script) +{ + struct obs_lua_script *data = current_lua_script; + const char *msg = lua_tostring(script, 1); + if (!msg) + return 0; + + script_error(&data->base, "%s", msg); + return 0; +} + +/* -------------------------------------------- */ + +static int lua_script_log(lua_State *script) +{ + struct obs_lua_script *data = current_lua_script; + int log_level = (int)lua_tointeger(script, 1); + const char *msg = lua_tostring(script, 2); + + if (!msg) + return 0; + + /* ------------------- */ + + dstr_copy(&data->log_chunk, msg); + + const char *start = data->log_chunk.array; + char *endl = strchr(start, '\n'); + + while (endl) { + *endl = 0; + script_log(&data->base, log_level, "%s", start); + *endl = '\n'; + + start = endl + 1; + endl = strchr(start, '\n'); + } + + if (start && *start) + script_log(&data->base, log_level, "%s", start); + dstr_resize(&data->log_chunk, 0); + + /* ------------------- */ + + return 0; +} + +/* -------------------------------------------- */ + +static void add_hook_functions(lua_State *script) +{ +#define add_func(name, func) \ + do { \ + lua_pushstring(script, name); \ + lua_pushcfunction(script, func); \ + lua_rawset(script, -3); \ + } while (false) + + lua_getglobal(script, "_G"); + + add_func("print", hook_print); + add_func("error", hook_error); + + lua_pop(script, 1); + + /* ------------- */ + + lua_getglobal(script, "obslua"); + + add_func("script_log", lua_script_log); + add_func("timer_remove", timer_remove); + add_func("timer_add", timer_add); + add_func("obs_enum_sources", enum_sources); + add_func("obs_scene_enum_items", scene_enum_items); + add_func("source_list_release", source_list_release); + add_func("sceneitem_list_release", sceneitem_list_release); + add_func("calldata_source", calldata_source); + add_func("calldata_sceneitem", calldata_sceneitem); + add_func("obs_add_main_render_callback", + obs_lua_add_main_render_callback); + add_func("obs_remove_main_render_callback", + obs_lua_remove_main_render_callback); + add_func("obs_add_tick_callback", + obs_lua_add_tick_callback); + add_func("obs_remove_tick_callback", + obs_lua_remove_tick_callback); + add_func("signal_handler_connect", + obs_lua_signal_handler_connect); + add_func("signal_handler_disconnect", + obs_lua_signal_handler_disconnect); + add_func("signal_handler_connect_global", + obs_lua_signal_handler_connect_global); + add_func("signal_handler_disconnect_global", + obs_lua_signal_handler_disconnect_global); + add_func("obs_hotkey_unregister", + hotkey_unregister); + add_func("obs_hotkey_register_frontend", + hotkey_register_frontend); + add_func("obs_properties_add_button", + properties_add_button); + add_func("obs_property_set_modified_callback", + property_set_modified_callback); + add_func("remove_current_callback", + remove_current_callback); + + lua_pop(script, 1); +#undef add_func +} + +/* -------------------------------------------- */ + +static void lua_tick(void *param, float seconds) +{ + struct obs_lua_script *data; + struct lua_obs_timer *timer; + uint64_t ts = obs_get_video_frame_time(); + + /* --------------------------------- */ + /* process script_tick calls */ + + pthread_mutex_lock(&tick_mutex); + data = first_tick_script; + while (data) { + lua_State *script = data->script; + + pthread_mutex_lock(&data->mutex); + + lua_pushnumber(script, (double)seconds); + call_func_(script, data->tick, 1, 0, "tick", __FUNCTION__); + + pthread_mutex_unlock(&data->mutex); + + data = data->next_tick; + } + pthread_mutex_unlock(&tick_mutex); + + /* --------------------------------- */ + /* process timers */ + + pthread_mutex_lock(&timer_mutex); + timer = first_timer; + while (timer) { + struct lua_obs_timer *next = timer->next; + struct lua_obs_callback *cb = lua_obs_timer_cb(timer); + + if (cb->base.removed) { + lua_obs_timer_remove(timer); + } else { + uint64_t elapsed = ts - timer->last_ts; + + if (elapsed >= timer->interval) { + timer_call(&cb->base); + timer->last_ts += timer->interval; + } + } + + timer = next; + } + pthread_mutex_unlock(&timer_mutex); + + UNUSED_PARAMETER(param); +} + +/* -------------------------------------------- */ + +void obs_lua_script_update(obs_script_t *script, obs_data_t *settings); + +bool obs_lua_script_load(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + if (!data->base.loaded) { + data->base.loaded = load_lua_script(data); + if (data->base.loaded) + obs_lua_script_update(s, NULL); + } + + return data->base.loaded; +} + +obs_script_t *obs_lua_script_create(const char *path, obs_data_t *settings) +{ + struct obs_lua_script *data = bzalloc(sizeof(*data)); + + data->base.type = OBS_SCRIPT_LANG_LUA; + data->tick = LUA_REFNIL; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init_value(&data->mutex); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + if (pthread_mutex_init(&data->mutex, &attr) != 0) { + bfree(data); + return NULL; + } + + dstr_copy(&data->base.path, path); + + char *slash = path && *path ? strrchr(path, '/') : NULL; + if (slash) { + slash++; + dstr_copy(&data->base.file, slash); + dstr_left(&data->dir, &data->base.path, slash - path); + } else { + dstr_copy(&data->base.file, path); + } + + data->base.settings = obs_data_create(); + if (settings) + obs_data_apply(data->base.settings, settings); + + obs_lua_script_load((obs_script_t *)data); + return (obs_script_t *)data; +} + +extern void undef_lua_script_sources(struct obs_lua_script *data); + +void obs_lua_script_unload(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + + if (!s->loaded) + return; + + lua_State *script = data->script; + + /* ---------------------------- */ + /* undefine source types */ + + undef_lua_script_sources(data); + + /* ---------------------------- */ + /* unhook tick function */ + + if (data->p_prev_next_tick) { + pthread_mutex_lock(&tick_mutex); + + struct obs_lua_script *next = data->next_tick; + if (next) next->p_prev_next_tick = data->p_prev_next_tick; + *data->p_prev_next_tick = next; + + pthread_mutex_unlock(&tick_mutex); + + data->p_prev_next_tick = NULL; + data->next_tick = NULL; + } + + /* ---------------------------- */ + /* call script_unload */ + + pthread_mutex_lock(&data->mutex); + + lua_getglobal(script, "script_unload"); + lua_pcall(script, 0, 0, 0); + + /* ---------------------------- */ + /* remove all callbacks */ + + struct lua_obs_callback *cb = + (struct lua_obs_callback *)data->first_callback; + while (cb) { + struct lua_obs_callback *next = + (struct lua_obs_callback *)cb->base.next; + remove_lua_obs_callback(cb); + cb = next; + } + + pthread_mutex_unlock(&data->mutex); + + /* ---------------------------- */ + /* close script */ + + lua_close(script); + s->loaded = false; +} + +void obs_lua_script_destroy(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + + if (data) { + pthread_mutex_destroy(&data->mutex); + dstr_free(&data->base.path); + dstr_free(&data->base.file); + dstr_free(&data->base.desc); + obs_data_release(data->base.settings); + dstr_free(&data->log_chunk); + dstr_free(&data->dir); + bfree(data); + } +} + +void obs_lua_script_update(obs_script_t *s, obs_data_t *settings) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + lua_State *script = data->script; + + if (!s->loaded) + return; + if (data->update == LUA_REFNIL) + return; + + if (settings) + obs_data_apply(s->settings, settings); + + current_lua_script = data; + pthread_mutex_lock(&data->mutex); + + ls_push_libobs_obj(obs_data_t, s->settings, false); + call_func_(script, data->update, 1, 0, "script_update", __FUNCTION__); + + pthread_mutex_unlock(&data->mutex); + current_lua_script = NULL; +} + +obs_properties_t *obs_lua_script_get_properties(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + lua_State *script = data->script; + obs_properties_t *props = NULL; + + if (!s->loaded) + return NULL; + if (data->get_properties == LUA_REFNIL) + return NULL; + + current_lua_script = data; + pthread_mutex_lock(&data->mutex); + + call_func_(script, data->get_properties, 0, 1, "script_properties", + __FUNCTION__); + ls_get_libobs_obj(obs_properties_t, -1, &props); + + pthread_mutex_unlock(&data->mutex); + current_lua_script = NULL; + + return props; +} + +void obs_lua_script_save(obs_script_t *s) +{ + struct obs_lua_script *data = (struct obs_lua_script *)s; + lua_State *script = data->script; + + if (!s->loaded) + return; + if (data->save == LUA_REFNIL) + return; + + current_lua_script = data; + pthread_mutex_lock(&data->mutex); + + ls_push_libobs_obj(obs_data_t, s->settings, false); + call_func_(script, data->save, 1, 0, "script_save", __FUNCTION__); + + pthread_mutex_unlock(&data->mutex); + current_lua_script = NULL; +} + +/* -------------------------------------------- */ + +void obs_lua_load(void) +{ + struct dstr dep_paths = {0}; + struct dstr tmp = {0}; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&tick_mutex, NULL); + pthread_mutex_init(&timer_mutex, &attr); + pthread_mutex_init(&lua_source_def_mutex, NULL); + + /* ---------------------------------------------- */ + /* Initialize Lua startup script */ + + dstr_printf(&tmp, startup_script_template, + dep_paths.array); + startup_script = tmp.array; + + dstr_free(&dep_paths); + + obs_add_tick_callback(lua_tick, NULL); +} + +void obs_lua_unload(void) +{ + obs_remove_tick_callback(lua_tick, NULL); + + bfree(startup_script); + pthread_mutex_destroy(&tick_mutex); + pthread_mutex_destroy(&timer_mutex); + pthread_mutex_destroy(&lua_source_def_mutex); +} diff --git a/deps/obs-scripting/obs-scripting-lua.h b/deps/obs-scripting/obs-scripting-lua.h new file mode 100644 index 000000000..91d905786 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-lua.h @@ -0,0 +1,262 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +/* ---------------------------- */ + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) +#pragma warning(disable : 4267) +#endif + +#define SWIG_TYPE_TABLE obslua +#include "swig/swigluarun.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +/* ---------------------------- */ + +#include +#include +#include + +#include "obs-scripting-internal.h" +#include "obs-scripting-callback.h" + +#define do_log(level, format, ...) \ + blog(level, "[Lua] " format, ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +/* ------------------------------------------------------------ */ + +struct obs_lua_script; +struct lua_obs_callback; + +extern THREAD_LOCAL struct lua_obs_callback *current_lua_cb; +extern THREAD_LOCAL struct obs_lua_script *current_lua_script; + +/* ------------------------------------------------------------ */ + +struct lua_obs_callback; + +struct obs_lua_script { + obs_script_t base; + + struct dstr dir; + struct dstr log_chunk; + + pthread_mutex_t mutex; + lua_State *script; + + struct script_callback *first_callback; + + int update; + int get_properties; + int save; + + int tick; + struct obs_lua_script *next_tick; + struct obs_lua_script **p_prev_next_tick; + + bool defined_sources; +}; + +#define lock_callback() \ + struct obs_lua_script *__last_script = current_lua_script; \ + struct lua_obs_callback *__last_callback = current_lua_cb; \ + current_lua_cb = cb; \ + current_lua_script = (struct obs_lua_script *)cb->base.script; \ + pthread_mutex_lock(¤t_lua_script->mutex); +#define unlock_callback() \ + pthread_mutex_unlock(¤t_lua_script->mutex); \ + current_lua_script = __last_script; \ + current_lua_cb = __last_callback; + +/* ------------------------------------------------ */ + +struct lua_obs_callback { + struct script_callback base; + + lua_State *script; + int reg_idx; +}; + +static inline struct lua_obs_callback *add_lua_obs_callback_extra( + lua_State *script, + int stack_idx, + size_t extra_size) +{ + struct obs_lua_script *data = current_lua_script; + struct lua_obs_callback *cb = add_script_callback( + &data->first_callback, + (obs_script_t *)data, + sizeof(*cb) + extra_size); + + lua_pushvalue(script, stack_idx); + cb->reg_idx = luaL_ref(script, LUA_REGISTRYINDEX); + cb->script = script; + return cb; +} + +static inline struct lua_obs_callback *add_lua_obs_callback( + lua_State *script, int stack_idx) +{ + return add_lua_obs_callback_extra(script, stack_idx, 0); +} + +static inline void *lua_obs_callback_extra_data(struct lua_obs_callback *cb) +{ + return (void*)&cb[1]; +} + +static inline struct obs_lua_script *lua_obs_callback_script( + struct lua_obs_callback *cb) +{ + return (struct obs_lua_script *)cb->base.script; +} + +static inline struct lua_obs_callback *find_next_lua_obs_callback( + lua_State *script, struct lua_obs_callback *cb, int stack_idx) +{ + struct obs_lua_script *data = current_lua_script; + + cb = cb ? (struct lua_obs_callback *)cb->base.next + : (struct lua_obs_callback *)data->first_callback; + + while (cb) { + lua_rawgeti(script, LUA_REGISTRYINDEX, cb->reg_idx); + bool match = lua_rawequal(script, -1, stack_idx); + lua_pop(script, 1); + + if (match) + break; + + cb = (struct lua_obs_callback *)cb->base.next; + } + + return cb; +} + +static inline struct lua_obs_callback *find_lua_obs_callback( + lua_State *script, int stack_idx) +{ + return find_next_lua_obs_callback(script, NULL, stack_idx); +} + +static inline void remove_lua_obs_callback(struct lua_obs_callback *cb) +{ + remove_script_callback(&cb->base); + luaL_unref(cb->script, LUA_REGISTRYINDEX, cb->reg_idx); +} + +static inline void just_free_lua_obs_callback(struct lua_obs_callback *cb) +{ + just_free_script_callback(&cb->base); +} + +static inline void free_lua_obs_callback(struct lua_obs_callback *cb) +{ + free_script_callback(&cb->base); +} + +/* ------------------------------------------------ */ + +static int is_ptr(lua_State *script, int idx) +{ + return lua_isuserdata(script, idx) || lua_isnil(script, idx); +} + +static int is_table(lua_State *script, int idx) +{ + return lua_istable(script, idx); +} + +static int is_function(lua_State *script, int idx) +{ + return lua_isfunction(script, idx); +} + +typedef int (*param_cb)(lua_State *script, int idx); + +static inline bool verify_args1_(lua_State *script, + param_cb param1_check, + const char *func) +{ + if (lua_gettop(script) != 1) { + warn("Wrong number of parameters for %s", func); + return false; + } + if (!param1_check(script, 1)) { + warn("Wrong parameter type for parameter %d of %s", 1, func); + return false; + } + + return true; +} + +#define verify_args1(script, param1_check) \ + verify_args1_(script, param1_check, __FUNCTION__) + +static inline bool call_func_(lua_State *script, + int reg_idx, int args, int rets, + const char *func, const char *display_name) +{ + if (reg_idx == LUA_REFNIL) + return false; + + struct obs_lua_script *data = current_lua_script; + + lua_rawgeti(script, LUA_REGISTRYINDEX, reg_idx); + lua_insert(script, -1 - args); + + if (lua_pcall(script, args, rets, 0) != 0) { + script_warn(&data->base, "Failed to call %s for %s: %s", func, + display_name, + lua_tostring(script, -1)); + lua_pop(script, 1); + return false; + } + + return true; +} + +bool ls_get_libobs_obj_(lua_State * script, + const char *type, + int lua_idx, + void * libobs_out, + const char *id, + const char *func, + int line); +bool ls_push_libobs_obj_(lua_State * script, + const char *type, + void * libobs_in, + bool ownership, + const char *id, + const char *func, + int line); + +extern void add_lua_source_functions(lua_State *script); diff --git a/deps/obs-scripting/obs-scripting-python-frontend.c b/deps/obs-scripting/obs-scripting-python-frontend.c new file mode 100644 index 000000000..1c70252c7 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python-frontend.c @@ -0,0 +1,361 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include + +#include "obs-scripting-python.h" + +#define libobs_to_py(type, obs_obj, ownership, py_obj) \ + libobs_to_py_(#type " *", obs_obj, ownership, py_obj, \ + NULL, __func__, __LINE__) +#define py_to_libobs(type, py_obj, libobs_out) \ + py_to_libobs_(#type " *", py_obj, libobs_out, \ + NULL, __func__, __LINE__) + +/* ----------------------------------- */ + +static PyObject *get_scene_names(PyObject *self, PyObject *args) +{ + char **names = obs_frontend_get_scene_names(); + char **name = names; + + PyObject *list = PyList_New(0); + + while (name && *name) { + PyObject *py_name = PyUnicode_FromString(*name); + if (py_name) { + PyList_Append(list, py_name); + Py_DECREF(py_name); + } + name++; + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + bfree(names); + return list; +} + +static PyObject *get_scenes(PyObject *self, PyObject *args) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_scenes(&list); + + PyObject *ret = PyList_New(0); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + PyObject *py_source; + + if (libobs_to_py(obs_source_t, source, false, &py_source)) { + PyList_Append(ret, py_source); + Py_DECREF(py_source); + } + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + da_free(list.sources); + return ret; +} + +static PyObject *get_current_scene(PyObject *self, PyObject *args) +{ + obs_source_t *source = obs_frontend_get_current_scene(); + + PyObject *py_source; + if (!libobs_to_py(obs_source_t, source, false, &py_source)) { + obs_source_release(source); + return python_none(); + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return py_source; +} + +static PyObject *set_current_scene(PyObject *self, PyObject *args) +{ + PyObject *py_source; + obs_source_t *source = NULL; + + if (!parse_args(args, "O", &py_source)) + return python_none(); + if (!py_to_libobs(obs_source_t, py_source, &source)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_scene(source); + return python_none(); +} + +static PyObject *get_transitions(PyObject *self, PyObject *args) +{ + struct obs_frontend_source_list list = {0}; + obs_frontend_get_transitions(&list); + + PyObject *ret = PyList_New(0); + + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + PyObject *py_source; + + if (libobs_to_py(obs_source_t, source, false, &py_source)) { + PyList_Append(ret, py_source); + Py_DECREF(py_source); + } + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + da_free(list.sources); + return ret; +} + +static PyObject *get_current_transition(PyObject *self, PyObject *args) +{ + obs_source_t *source = obs_frontend_get_current_transition(); + + PyObject *py_source; + if (!libobs_to_py(obs_source_t, source, false, &py_source)) { + obs_source_release(source); + return python_none(); + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return py_source; +} + +static PyObject *set_current_transition(PyObject *self, PyObject *args) +{ + PyObject *py_source; + obs_source_t *source = NULL; + + if (!parse_args(args, "O", &py_source)) + return python_none(); + if (!py_to_libobs(obs_source_t, py_source, &source)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_transition(source); + return python_none(); +} + +static PyObject *get_scene_collections(PyObject *self, PyObject *args) +{ + char **names = obs_frontend_get_scene_collections(); + char **name = names; + + PyObject *list = PyList_New(0); + + while (name && *name) { + PyObject *py_name = PyUnicode_FromString(*name); + if (py_name) { + PyList_Append(list, py_name); + Py_DECREF(py_name); + } + name++; + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + bfree(names); + return list; +} + +static PyObject *get_current_scene_collection(PyObject *self, PyObject *args) +{ + char *name = obs_frontend_get_current_scene_collection(); + PyObject *ret = PyUnicode_FromString(name); + bfree(name); + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return ret; +} + +static PyObject *set_current_scene_collection(PyObject *self, PyObject *args) +{ + const char *name; + if (!parse_args(args, "s", &name)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_scene_collection(name); + return python_none(); +} + +static PyObject *get_profiles(PyObject *self, PyObject *args) +{ + char **names = obs_frontend_get_profiles(); + char **name = names; + + PyObject *list = PyList_New(0); + + while (name && *name) { + PyObject *py_name = PyUnicode_FromString(*name); + if (py_name) { + PyList_Append(list, py_name); + Py_DECREF(py_name); + } + name++; + } + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + bfree(names); + return list; +} + +static PyObject *get_current_profile(PyObject *self, PyObject *args) +{ + char *name = obs_frontend_get_current_profile(); + PyObject *ret = PyUnicode_FromString(name); + bfree(name); + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + return ret; +} + +static PyObject *set_current_profile(PyObject *self, PyObject *args) +{ + const char *name; + if (!parse_args(args, "s", &name)) + return python_none(); + + UNUSED_PARAMETER(self); + + obs_frontend_set_current_profile(name); + return python_none(); +} + +/* ----------------------------------- */ + +static void frontend_save_callback(obs_data_t *save_data, bool saving, + void *priv) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + obs_frontend_remove_save_callback(frontend_save_callback, cb); + return; + } + + lock_python(); + + PyObject *py_save_data; + + if (libobs_to_py(obs_data_t, save_data, false, &py_save_data)) { + PyObject *args = Py_BuildValue("(Op)", py_save_data, saving); + + struct python_obs_callback *last_cb = cur_python_cb; + cur_python_cb = cb; + cur_python_script = (struct obs_python_script *)cb->base.script; + + PyObject *py_ret = PyObject_CallObject(cb->func, args); + Py_XDECREF(py_ret); + py_error(); + + cur_python_script = NULL; + cur_python_cb = last_cb; + + Py_XDECREF(args); + Py_XDECREF(py_save_data); + } + + unlock_python(); +} + +static PyObject *remove_save_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static void add_save_callback_defer(void *cb) +{ + obs_frontend_add_save_callback(frontend_save_callback, cb); +} + +static PyObject *add_save_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + defer_call_post(add_save_callback_defer, cb); + return python_none(); +} + +/* ----------------------------------- */ + +void add_python_frontend_funcs(PyObject *module) +{ + static PyMethodDef funcs[] = { +#define DEF_FUNC(c) {"obs_frontend_" #c, c, METH_VARARGS, NULL} + + DEF_FUNC(get_scene_names), + DEF_FUNC(get_scenes), + DEF_FUNC(get_current_scene), + DEF_FUNC(set_current_scene), + DEF_FUNC(get_transitions), + DEF_FUNC(get_current_transition), + DEF_FUNC(set_current_transition), + DEF_FUNC(get_scene_collections), + DEF_FUNC(get_current_scene_collection), + DEF_FUNC(set_current_scene_collection), + DEF_FUNC(get_profiles), + DEF_FUNC(get_current_profile), + DEF_FUNC(set_current_profile), + DEF_FUNC(remove_save_callback), + DEF_FUNC(add_save_callback), + +#undef DEF_FUNC + {0} + }; + + add_functions_to_py_module(module, funcs); +} diff --git a/deps/obs-scripting/obs-scripting-python-import.c b/deps/obs-scripting/obs-scripting-python-import.c new file mode 100644 index 000000000..fa24c408c --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python-import.c @@ -0,0 +1,149 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include + +#define NO_REDEFS +#include "obs-scripting-python-import.h" +#include "obs-scripting-config.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4152) +#endif + +#ifdef _WIN32 +#define SO_EXT ".dll" +#elif __APPLE__ +#define SO_EXT ".dylib" +#endif + +bool import_python(const char *python_path) +{ + struct dstr lib_path; + bool success = false; + void *lib; + + if (!python_path) + python_path = ""; + + dstr_init_copy(&lib_path, python_path); + dstr_replace(&lib_path, "\\", "/"); + if (!dstr_is_empty(&lib_path)) { + dstr_cat(&lib_path, "/"); + } + dstr_cat(&lib_path, PYTHON_LIB SO_EXT); + + lib = os_dlopen(lib_path.array); + if (!lib) { + blog(LOG_WARNING, "[Python] Could not load library: %s", + lib_path.array); + goto fail; + } + +#define IMPORT_FUNC(x) \ + do { \ + Import_##x = os_dlsym(lib, #x); \ + if (!Import_##x) { \ + blog(LOG_WARNING, "[Python] Failed to import: %s", \ + #x); \ + goto fail; \ + } \ + } while (false) + + IMPORT_FUNC(PyType_Ready); + IMPORT_FUNC(PyObject_GenericGetAttr); + IMPORT_FUNC(PyObject_IsTrue); + IMPORT_FUNC(Py_DecRef); + IMPORT_FUNC(PyObject_Malloc); + IMPORT_FUNC(PyObject_Free); + IMPORT_FUNC(PyObject_Init); + IMPORT_FUNC(PyUnicode_FromFormat); + IMPORT_FUNC(PyUnicode_Concat); + IMPORT_FUNC(PyLong_FromVoidPtr); + IMPORT_FUNC(PyBool_FromLong); + IMPORT_FUNC(PyGILState_Ensure); + IMPORT_FUNC(PyGILState_GetThisThreadState); + IMPORT_FUNC(PyErr_SetString); + IMPORT_FUNC(PyErr_Occurred); + IMPORT_FUNC(PyErr_Fetch); + IMPORT_FUNC(PyErr_Restore); + IMPORT_FUNC(PyErr_WriteUnraisable); + IMPORT_FUNC(PyArg_UnpackTuple); + IMPORT_FUNC(Py_BuildValue); + IMPORT_FUNC(PyRun_SimpleStringFlags); + IMPORT_FUNC(PyErr_Print); + IMPORT_FUNC(Py_SetPythonHome); + IMPORT_FUNC(Py_Initialize); + IMPORT_FUNC(Py_Finalize); + IMPORT_FUNC(Py_IsInitialized); + IMPORT_FUNC(PyEval_InitThreads); + IMPORT_FUNC(PyEval_ThreadsInitialized); + IMPORT_FUNC(PyEval_ReleaseThread); + IMPORT_FUNC(PySys_SetArgv); + IMPORT_FUNC(PyImport_ImportModule); + IMPORT_FUNC(PyObject_CallFunctionObjArgs); + IMPORT_FUNC(_Py_NotImplementedStruct); + IMPORT_FUNC(PyExc_TypeError); + IMPORT_FUNC(PyExc_RuntimeError); + IMPORT_FUNC(PyObject_GetAttr); + IMPORT_FUNC(PyUnicode_FromString); + IMPORT_FUNC(PyDict_GetItemString); + IMPORT_FUNC(PyDict_SetItemString); + IMPORT_FUNC(PyCFunction_NewEx); + IMPORT_FUNC(PyModule_GetDict); + IMPORT_FUNC(PyModule_GetNameObject); + IMPORT_FUNC(PyModule_AddObject); + IMPORT_FUNC(PyModule_AddStringConstant); + IMPORT_FUNC(PyImport_Import); + IMPORT_FUNC(PyObject_CallObject); + IMPORT_FUNC(_Py_FalseStruct); + IMPORT_FUNC(_Py_TrueStruct); + IMPORT_FUNC(PyGILState_Release); + IMPORT_FUNC(PyList_Append); + IMPORT_FUNC(PySys_GetObject); + IMPORT_FUNC(PyImport_ReloadModule); + IMPORT_FUNC(PyObject_GetAttrString); + IMPORT_FUNC(PyCapsule_New); + IMPORT_FUNC(PyCapsule_GetPointer); + IMPORT_FUNC(PyArg_ParseTuple); + IMPORT_FUNC(PyFunction_Type); + IMPORT_FUNC(PyObject_SetAttr); + IMPORT_FUNC(_PyObject_New); + IMPORT_FUNC(PyCapsule_Import); + IMPORT_FUNC(PyErr_Clear); + IMPORT_FUNC(PyObject_Call); + IMPORT_FUNC(PyList_New); + IMPORT_FUNC(PyList_Size); + IMPORT_FUNC(PyList_GetItem); + IMPORT_FUNC(PyUnicode_AsUTF8String); + IMPORT_FUNC(PyLong_FromUnsignedLongLong); + IMPORT_FUNC(PyArg_VaParse); + IMPORT_FUNC(_Py_NoneStruct); + +#undef IMPORT_FUNC + + success = true; + +fail: + if (!success && lib) + os_dlclose(lib); + + dstr_free(&lib_path); + + return success; +} diff --git a/deps/obs-scripting/obs-scripting-python-import.h b/deps/obs-scripting/obs-scripting-python-import.h new file mode 100644 index 000000000..941e580db --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python-import.h @@ -0,0 +1,198 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include + +#if defined(_WIN32) || defined(__APPLE__) +#define RUNTIME_LINK 1 +#define Py_NO_ENABLE_SHARED +#else +#define RUNTIME_LINK 0 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4115) +#endif + +#if defined(_WIN32) && defined(_DEBUG) +# undef _DEBUG +# include +# define _DEBUG +#else +# include +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if RUNTIME_LINK + +#ifdef NO_REDEFS +#define PY_EXTERN +#else +#define PY_EXTERN extern +#endif + +PY_EXTERN int (*Import_PyType_Ready)(PyTypeObject *); +PY_EXTERN PyObject *(*Import_PyObject_GenericGetAttr)(PyObject *, PyObject *); +PY_EXTERN int (*Import_PyObject_IsTrue)(PyObject *); +PY_EXTERN void (*Import_Py_DecRef)(PyObject *); +PY_EXTERN void *(*Import_PyObject_Malloc)(size_t size); +PY_EXTERN void (*Import_PyObject_Free)(void *ptr); +PY_EXTERN PyObject *(*Import_PyObject_Init)(PyObject *, PyTypeObject *); +PY_EXTERN PyObject *(*Import_PyUnicode_FromFormat)(const char *format, ...); +PY_EXTERN PyObject *(*Import_PyUnicode_Concat)(PyObject *left, PyObject *right); +PY_EXTERN PyObject *(*Import_PyLong_FromVoidPtr)(void *); +PY_EXTERN PyObject *(*Import_PyBool_FromLong)(long); +PY_EXTERN PyGILState_STATE (*Import_PyGILState_Ensure)(void); +PY_EXTERN PyThreadState *(*Import_PyGILState_GetThisThreadState)(void); +PY_EXTERN void (*Import_PyErr_SetString)(PyObject *exception, const char *string); +PY_EXTERN PyObject *(*Import_PyErr_Occurred)(void); +PY_EXTERN void (*Import_PyErr_Fetch)(PyObject **, PyObject **, PyObject **); +PY_EXTERN void (*Import_PyErr_Restore)(PyObject *, PyObject *, PyObject *); +PY_EXTERN void (*Import_PyErr_WriteUnraisable)(PyObject *); +PY_EXTERN int (*Import_PyArg_UnpackTuple)(PyObject *, const char *, Py_ssize_t, Py_ssize_t, ...); +PY_EXTERN PyObject *(*Import_Py_BuildValue)(const char *, ...); +PY_EXTERN int (*Import_PyRun_SimpleStringFlags)(const char *, PyCompilerFlags *); +PY_EXTERN void (*Import_PyErr_Print)(void); +PY_EXTERN void (*Import_Py_SetPythonHome)(wchar_t *); +PY_EXTERN void (*Import_Py_Initialize)(void); +PY_EXTERN void (*Import_Py_Finalize)(void); +PY_EXTERN int (*Import_Py_IsInitialized)(void); +PY_EXTERN void (*Import_PyEval_InitThreads)(void); +PY_EXTERN int (*Import_PyEval_ThreadsInitialized)(void); +PY_EXTERN void (*Import_PyEval_ReleaseThread)(PyThreadState *tstate); +PY_EXTERN void (*Import_PySys_SetArgv)(int, wchar_t **); +PY_EXTERN PyObject *(*Import_PyImport_ImportModule)(const char *name); +PY_EXTERN PyObject *(*Import_PyObject_CallFunctionObjArgs)(PyObject *callable, ...); +PY_EXTERN PyObject (*Import__Py_NotImplementedStruct); +PY_EXTERN PyObject *(*Import_PyExc_TypeError); +PY_EXTERN PyObject *(*Import_PyExc_RuntimeError); +PY_EXTERN PyObject *(*Import_PyObject_GetAttr)(PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import_PyUnicode_FromString)(const char *u); +PY_EXTERN PyObject *(*Import_PyDict_GetItemString)(PyObject *dp, const char *key); +PY_EXTERN int (*Import_PyDict_SetItemString)(PyObject *dp, const char *key, PyObject *item); +PY_EXTERN PyObject *(*Import_PyCFunction_NewEx)(PyMethodDef *, PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import_PyModule_GetDict)(PyObject *); +PY_EXTERN PyObject *(*Import_PyModule_GetNameObject)(PyObject *); +PY_EXTERN int (*Import_PyModule_AddObject)(PyObject *, const char *, PyObject *); +PY_EXTERN int (*Import_PyModule_AddStringConstant)(PyObject *, const char *, const char *); +PY_EXTERN PyObject *(*Import_PyImport_Import)(PyObject *name); +PY_EXTERN PyObject *(*Import_PyObject_CallObject)(PyObject *callable_object, PyObject *args); +PY_EXTERN struct _longobject (*Import__Py_FalseStruct); +PY_EXTERN struct _longobject (*Import__Py_TrueStruct); +PY_EXTERN void (*Import_PyGILState_Release)(PyGILState_STATE); +PY_EXTERN int (*Import_PyList_Append)(PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import_PySys_GetObject)(const char *); +PY_EXTERN PyObject *(*Import_PyImport_ReloadModule)(PyObject *m); +PY_EXTERN PyObject *(*Import_PyObject_GetAttrString)(PyObject *, const char *); +PY_EXTERN PyObject *(*Import_PyCapsule_New)(void *pointer, const char *name, PyCapsule_Destructor destructor); +PY_EXTERN void *(*Import_PyCapsule_GetPointer)(PyObject *capsule, const char *name); +PY_EXTERN int (*Import_PyArg_ParseTuple)(PyObject *, const char *, ...); +PY_EXTERN PyTypeObject (*Import_PyFunction_Type); +PY_EXTERN int (*Import_PyObject_SetAttr)(PyObject *, PyObject *, PyObject *); +PY_EXTERN PyObject *(*Import__PyObject_New)(PyTypeObject *); +PY_EXTERN void *(*Import_PyCapsule_Import)(const char *name, int no_block); +PY_EXTERN void (*Import_PyErr_Clear)(void); +PY_EXTERN PyObject *(*Import_PyObject_Call)(PyObject *callable_object, PyObject *args, PyObject *kwargs); +PY_EXTERN PyObject *(*Import_PyList_New)(Py_ssize_t size); +PY_EXTERN Py_ssize_t (*Import_PyList_Size)(PyObject *); +PY_EXTERN PyObject *(*Import_PyList_GetItem)(PyObject *, Py_ssize_t); +PY_EXTERN PyObject *(*Import_PyUnicode_AsUTF8String)(PyObject *unicode); +PY_EXTERN PyObject *(*Import_PyLong_FromUnsignedLongLong)(unsigned long long); +PY_EXTERN int (*Import_PyArg_VaParse)(PyObject *, const char *, va_list); +PY_EXTERN PyObject (*Import__Py_NoneStruct); + +extern bool import_python(const char *python_path); + +# ifndef NO_REDEFS +# define PyType_Ready Import_PyType_Ready +# define PyObject_GenericGetAttr Import_PyObject_GenericGetAttr +# define PyObject_IsTrue Import_PyObject_IsTrue +# define Py_DecRef Import_Py_DecRef +# define PyObject_Malloc Import_PyObject_Malloc +# define PyObject_Free Import_PyObject_Free +# define PyObject_Init Import_PyObject_Init +# define PyUnicode_FromFormat Import_PyUnicode_FromFormat +# define PyUnicode_Concat Import_PyUnicode_Concat +# define PyLong_FromVoidPtr Import_PyLong_FromVoidPtr +# define PyBool_FromLong Import_PyBool_FromLong +# define PyGILState_Ensure Import_PyGILState_Ensure +# define PyGILState_GetThisThreadState Import_PyGILState_GetThisThreadState +# define PyErr_SetString Import_PyErr_SetString +# define PyErr_Occurred Import_PyErr_Occurred +# define PyErr_Fetch Import_PyErr_Fetch +# define PyErr_Restore Import_PyErr_Restore +# define PyErr_WriteUnraisable Import_PyErr_WriteUnraisable +# define PyArg_UnpackTuple Import_PyArg_UnpackTuple +# define Py_BuildValue Import_Py_BuildValue +# define PyRun_SimpleStringFlags Import_PyRun_SimpleStringFlags +# define PyErr_Print Import_PyErr_Print +# define Py_SetPythonHome Import_Py_SetPythonHome +# define Py_Initialize Import_Py_Initialize +# define Py_Finalize Import_Py_Finalize +# define Py_IsInitialized Import_Py_IsInitialized +# define PyEval_InitThreads Import_PyEval_InitThreads +# define PyEval_ThreadsInitialized Import_PyEval_ThreadsInitialized +# define PyEval_ReleaseThread Import_PyEval_ReleaseThread +# define PySys_SetArgv Import_PySys_SetArgv +# define PyImport_ImportModule Import_PyImport_ImportModule +# define PyObject_CallFunctionObjArgs Import_PyObject_CallFunctionObjArgs +# define _Py_NotImplementedStruct (*Import__Py_NotImplementedStruct) +# define PyExc_TypeError (*Import_PyExc_TypeError) +# define PyExc_RuntimeError (*Import_PyExc_RuntimeError) +# define PyObject_GetAttr Import_PyObject_GetAttr +# define PyUnicode_FromString Import_PyUnicode_FromString +# define PyDict_GetItemString Import_PyDict_GetItemString +# define PyDict_SetItemString Import_PyDict_SetItemString +# define PyCFunction_NewEx Import_PyCFunction_NewEx +# define PyModule_GetDict Import_PyModule_GetDict +# define PyModule_GetNameObject Import_PyModule_GetNameObject +# define PyModule_AddObject Import_PyModule_AddObject +# define PyModule_AddStringConstant Import_PyModule_AddStringConstant +# define PyImport_Import Import_PyImport_Import +# define PyObject_CallObject Import_PyObject_CallObject +# define _Py_FalseStruct (*Import__Py_FalseStruct) +# define _Py_TrueStruct (*Import__Py_TrueStruct) +# define PyGILState_Release Import_PyGILState_Release +# define PyList_Append Import_PyList_Append +# define PySys_GetObject Import_PySys_GetObject +# define PyImport_ReloadModule Import_PyImport_ReloadModule +# define PyObject_GetAttrString Import_PyObject_GetAttrString +# define PyCapsule_New Import_PyCapsule_New +# define PyCapsule_GetPointer Import_PyCapsule_GetPointer +# define PyArg_ParseTuple Import_PyArg_ParseTuple +# define PyFunction_Type (*Import_PyFunction_Type) +# define PyObject_SetAttr Import_PyObject_SetAttr +# define _PyObject_New Import__PyObject_New +# define PyCapsule_Import Import_PyCapsule_Import +# define PyErr_Clear Import_PyErr_Clear +# define PyObject_Call Import_PyObject_Call +# define PyList_New Import_PyList_New +# define PyList_Size Import_PyList_Size +# define PyList_GetItem Import_PyList_GetItem +# define PyUnicode_AsUTF8String Import_PyUnicode_AsUTF8String +# define PyLong_FromUnsignedLongLong Import_PyLong_FromUnsignedLongLong +# define PyArg_VaParse Import_PyArg_VaParse +# define _Py_NoneStruct (*Import__Py_NoneStruct) +# endif + +#endif diff --git a/deps/obs-scripting/obs-scripting-python.c b/deps/obs-scripting/obs-scripting-python.c new file mode 100644 index 000000000..84bf235f5 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python.c @@ -0,0 +1,1705 @@ +/****************************************************************************** + Copyright (C) 2015 by Andrew Skinner + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-scripting-python.h" +#include "obs-scripting-config.h" +#include +#include +#include +#include + +#include + +/* ========================================================================= */ + +// #define DEBUG_PYTHON_STARTUP + +static const char *startup_script = "\n\ +import sys\n\ +import os\n\ +import obspython\n\ +class stdout_logger(object):\n\ + def write(self, message):\n\ + obspython.script_log(obspython.LOG_INFO, message)\n\ + def flush(self):\n\ + pass\n\ +class stderr_logger(object):\n\ + def write(self, message):\n\ + obspython.script_log(obspython.LOG_ERROR, message)\n\ + def flush(self):\n\ + pass\n\ +os.environ['PYTHONUNBUFFERED'] = '1'\n\ +sys.stdout = stdout_logger()\n\ +sys.stderr = stderr_logger()\n"; + +#if RUNTIME_LINK +static wchar_t home_path[1024] = {0}; +#endif + +DARRAY(char*) python_paths; +static bool python_loaded = false; + +static pthread_mutex_t tick_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct obs_python_script *first_tick_script = NULL; + +static PyObject *py_obspython = NULL; +struct obs_python_script *cur_python_script = NULL; +struct python_obs_callback *cur_python_cb = NULL; + +/* -------------------------------------------- */ + +bool py_to_libobs_(const char *type, + PyObject * py_in, + void * libobs_out, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + int ret = SWIG_ConvertPtr(py_in, libobs_out, info, 0); + if (!SWIG_IsOK(ret)) { + warn("%s:%d: SWIG failed to convert python object to obs " + "object: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + return true; +} + +bool libobs_to_py_(const char *type, + void * libobs_in, + bool ownership, + PyObject ** py_out, + const char *id, + const char *func, + int line) +{ + swig_type_info *info = SWIG_TypeQuery(type); + if (info == NULL) { + warn("%s:%d: SWIG could not find type: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + *py_out = SWIG_NewPointerObj(libobs_in, info, (int)ownership); + if (*py_out == Py_None) { + warn("%s:%d: SWIG failed to convert obs object to python " + "object: %s%s%s", + func, + line, + id ? id : "", + id ? "::" : "", + type); + return false; + } + + return true; +} + + +#define libobs_to_py(type, obs_obj, ownership, py_obj) \ + libobs_to_py_(#type " *", obs_obj, ownership, py_obj, \ + NULL, __func__, __LINE__) +#define py_to_libobs(type, py_obj, libobs_out) \ + py_to_libobs_(#type " *", py_obj, libobs_out, \ + NULL, __func__, __LINE__) + +#define lock_callback(cb) \ + lock_python(); \ + struct obs_python_script *__last_script = cur_python_script; \ + struct python_obs_callback *__last_cb = cur_python_cb; \ + cur_python_script = (struct obs_python_script *)cb->base.script; \ + cur_python_cb = cb +#define unlock_callback() \ + cur_python_cb = __last_cb; \ + cur_python_script = __last_script; \ + unlock_python() + +/* ========================================================================= */ + +void add_functions_to_py_module(PyObject *module, PyMethodDef *method_list) +{ + PyObject *dict = PyModule_GetDict(module); + PyObject *name = PyModule_GetNameObject(module); + if (!dict || !name) { + return; + } + for (PyMethodDef *ml = method_list; ml->ml_name != NULL; ml++) { + PyObject *func = PyCFunction_NewEx(ml, module, name); + if (!func) { + continue; + } + PyDict_SetItemString(dict, ml->ml_name, func); + Py_DECREF(func); + } + Py_DECREF(name); +} + +/* -------------------------------------------- */ + +static PyObject *py_get_current_script_path(PyObject *self, PyObject *args) +{ + UNUSED_PARAMETER(args); + return PyDict_GetItemString(PyModule_GetDict(self), + "__script_dir__"); +} + +static void get_defaults(struct obs_python_script *data, PyObject *get_defs) +{ + PyObject *py_settings; + if (!libobs_to_py(obs_data_t, data->base.settings, false, &py_settings)) + return; + + PyObject *args = Py_BuildValue("(O)", py_settings); + PyObject *py_ret = PyObject_CallObject(get_defs, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_settings); +} + +static bool load_python_script(struct obs_python_script *data) +{ + PyObject *py_file = NULL; + PyObject *py_module = NULL; + PyObject *py_success = NULL; + PyObject *py_tick = NULL; + PyObject *py_load = NULL; + PyObject *py_defaults = NULL; + bool success = false; + int ret; + + cur_python_script = data; + + if (!data->module) { + py_file = PyUnicode_FromString(data->name.array); + py_module = PyImport_Import(py_file); + } else { + py_module = PyImport_ReloadModule(data->module); + } + + if (!py_module) { + py_error(); + goto fail; + } + + Py_XINCREF(py_obspython); + ret = PyModule_AddObject(py_module, "obspython", py_obspython); + if (py_error() || ret != 0) + goto fail; + + ret = PyModule_AddStringConstant(py_module, "__script_dir__", + data->dir.array); + if (py_error() || ret != 0) + goto fail; + + PyObject *py_data = PyCapsule_New(data, NULL, NULL); + ret = PyModule_AddObject(py_module, "__script_data__", py_data); + if (py_error() || ret != 0) + goto fail; + + static PyMethodDef global_funcs[] = { + {"script_path", + py_get_current_script_path, + METH_NOARGS, + "Gets the script path"}, + {0} + }; + + add_functions_to_py_module(py_module, global_funcs); + + data->update = PyObject_GetAttrString(py_module, "script_update"); + if (!data->update) + PyErr_Clear(); + + data->save = PyObject_GetAttrString(py_module, "script_save"); + if (!data->save) + PyErr_Clear(); + + data->get_properties = PyObject_GetAttrString(py_module, + "script_properties"); + if (!data->get_properties) + PyErr_Clear(); + + PyObject *func = PyObject_GetAttrString(py_module, "script_defaults"); + if (func) { + get_defaults(data, func); + Py_DECREF(func); + } else { + PyErr_Clear(); + } + + func = PyObject_GetAttrString(py_module, "script_description"); + if (func) { + PyObject *py_ret = PyObject_CallObject(func, NULL); + py_error(); + PyObject *py_desc = PyUnicode_AsUTF8String(py_ret); + if (py_desc) { + const char *desc = PyBytes_AS_STRING(py_desc); + if (desc) dstr_copy(&data->base.desc, desc); + Py_DECREF(py_desc); + } + Py_XDECREF(py_ret); + Py_DECREF(func); + } else { + PyErr_Clear(); + } + + py_tick = PyObject_GetAttrString(py_module, "script_tick"); + if (py_tick) { + pthread_mutex_lock(&tick_mutex); + + struct obs_python_script *next = first_tick_script; + data->next_tick = next; + data->p_prev_next_tick = &first_tick_script; + if (next) next->p_prev_next_tick = &data->next_tick; + first_tick_script = data; + + data->tick = py_tick; + py_tick = NULL; + + pthread_mutex_unlock(&tick_mutex); + } else { + PyErr_Clear(); + } + + py_load = PyObject_GetAttrString(py_module, "script_load"); + if (py_load) { + PyObject *py_s; + libobs_to_py(obs_data_t, data->base.settings, false, &py_s); + PyObject *args = Py_BuildValue("(O)", py_s); + PyObject *py_ret = PyObject_CallObject(py_load, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_s); + } else { + PyErr_Clear(); + } + + if (data->module) + Py_XDECREF(data->module); + data->module = py_module; + py_module = NULL; + + success = true; + +fail: + Py_XDECREF(py_load); + Py_XDECREF(py_tick); + Py_XDECREF(py_defaults); + Py_XDECREF(py_success); + Py_XDECREF(py_file); + if (!success) + Py_XDECREF(py_module); + cur_python_script = NULL; + return success; +} + +static void unload_python_script(struct obs_python_script *data) +{ + PyObject *py_module = data->module; + PyObject *py_func = NULL; + PyObject *py_ret = NULL; + + cur_python_script = data; + + py_func = PyObject_GetAttrString(py_module, "script_unload"); + if (PyErr_Occurred() || !py_func) { + PyErr_Clear(); + goto fail; + } + + py_ret = PyObject_CallObject(py_func, NULL); + if (py_error()) + goto fail; + +fail: + Py_XDECREF(py_ret); + Py_XDECREF(py_func); + + cur_python_script = NULL; +} + +static void add_to_python_path(const char *path) +{ + PyObject *py_path_str = NULL; + PyObject *py_path = NULL; + int ret; + + if (!path || !*path) + return; + + for (size_t i = 0; i < python_paths.num; i++) { + const char *python_path = python_paths.array[i]; + if (strcmp(path, python_path) == 0) + return; + } + + ret = PyRun_SimpleString("import sys"); + if (py_error() || ret != 0) + goto fail; + + /* borrowed reference here */ + py_path = PySys_GetObject("path"); + if (py_error() || !py_path) + goto fail; + + py_path_str = PyUnicode_FromString(path); + ret = PyList_Append(py_path, py_path_str); + if (py_error() || ret != 0) + goto fail; + + char *new_path = bstrdup(path); + da_push_back(python_paths, &new_path); + +fail: + Py_XDECREF(py_path_str); +} + +/* -------------------------------------------- */ + +struct python_obs_timer { + struct python_obs_timer *next; + struct python_obs_timer **p_prev_next; + + uint64_t last_ts; + uint64_t interval; +}; + +static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct python_obs_timer *first_timer = NULL; + +static inline void python_obs_timer_init(struct python_obs_timer *timer) +{ + pthread_mutex_lock(&timer_mutex); + + struct python_obs_timer *next = first_timer; + timer->next = next; + timer->p_prev_next = &first_timer; + if (next) next->p_prev_next = &timer->next; + first_timer = timer; + + pthread_mutex_unlock(&timer_mutex); +} + +static inline void python_obs_timer_remove(struct python_obs_timer *timer) +{ + struct python_obs_timer *next = timer->next; + if (next) next->p_prev_next = timer->p_prev_next; + *timer->p_prev_next = timer->next; +} + +static inline struct python_obs_callback *python_obs_timer_cb( + struct python_obs_timer *timer) +{ + return &((struct python_obs_callback *)timer)[-1]; +} + +static PyObject *timer_remove(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static void timer_call(struct script_callback *p_cb) +{ + struct python_obs_callback *cb = (struct python_obs_callback *)p_cb; + + if (p_cb->removed) + return; + + lock_callback(cb); + PyObject *py_ret = PyObject_CallObject(cb->func, NULL); + py_error(); + Py_XDECREF(py_ret); + unlock_callback(); +} + +static void defer_timer_init(void *p_cb) +{ + struct python_obs_callback *cb = p_cb; + struct python_obs_timer *timer = python_obs_callback_extra_data(cb); + python_obs_timer_init(timer); +} + +static PyObject *timer_add(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb; + int ms; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "Oi", &py_cb, &ms)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback_extra( + script, py_cb, sizeof(struct python_obs_timer)); + struct python_obs_timer *timer = python_obs_callback_extra_data(cb); + + timer->interval = (uint64_t)ms * 1000000ULL; + timer->last_ts = obs_get_video_frame_time(); + + defer_call_post(defer_timer_init, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void obs_python_tick_callback(void *priv, float seconds) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + obs_remove_tick_callback(obs_python_tick_callback, cb); + return; + } + + lock_callback(cb); + + PyObject *args = Py_BuildValue("(f)", seconds); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + + unlock_callback(); +} + +static PyObject *obs_python_remove_tick_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static PyObject *obs_python_add_tick_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + obs_add_tick_callback(obs_python_tick_callback, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback(void *priv, calldata_t *cd) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(cb); + + PyObject *py_cd; + + if (libobs_to_py(calldata_t, cd, false, &py_cd)) { + PyObject *args = Py_BuildValue("(O)", py_cd); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_cd); + } + + unlock_callback(); +} + +static PyObject *obs_python_signal_handler_disconnect( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + const char *signal; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OsO", &py_sh, &signal, &py_cb)) + return python_none(); + + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + const char *cb_signal = + calldata_string(&cb->base.extra, "signal"); + + if (cb_signal && + strcmp(signal, cb_signal) != 0 && + handler == cb_handler) + break; + + cb = find_next_python_obs_callback(script, cb, py_cb); + } + + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static PyObject *obs_python_signal_handler_connect( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + const char *signal; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OsO", &py_sh, &signal, &py_cb)) + return python_none(); + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + calldata_set_ptr(&cb->base.extra, "handler", handler); + calldata_set_string(&cb->base.extra, "signal", signal); + signal_handler_connect(handler, signal, calldata_signal_callback, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void calldata_signal_callback_global(void *priv, const char *signal, + calldata_t *cd) +{ + struct python_obs_callback *cb = priv; + + if (cb->base.removed) { + signal_handler_remove_current(); + return; + } + + lock_callback(cb); + + PyObject *py_cd; + + if (libobs_to_py(calldata_t, cd, false, &py_cd)) { + PyObject *args = Py_BuildValue("(sO)", signal, py_cd); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_cd); + } + + unlock_callback(); +} + +static PyObject *obs_python_signal_handler_disconnect_global( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OO", &py_sh, &py_cb)) + return python_none(); + + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + while (cb) { + signal_handler_t *cb_handler = + calldata_ptr(&cb->base.extra, "handler"); + + if (handler == cb_handler) + break; + + cb = find_next_python_obs_callback(script, cb, py_cb); + } + + if (cb) remove_python_obs_callback(cb); + return python_none(); +} + +static PyObject *obs_python_signal_handler_connect_global( + PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_sh = NULL; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + UNUSED_PARAMETER(self); + + signal_handler_t *handler; + + if (!parse_args(args, "OO", &py_sh, &py_cb)) + return python_none(); + + if (!py_to_libobs(signal_handler_t, py_sh, &handler)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + calldata_set_ptr(&cb->base.extra, "handler", handler); + signal_handler_connect_global(handler, + calldata_signal_callback_global, cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static void defer_hotkey_unregister(void *p_cb) +{ + obs_hotkey_unregister((obs_hotkey_id)(uintptr_t)p_cb); +} + +static void on_remove_hotkey(void *p_cb) +{ + struct python_obs_callback *cb = p_cb; + obs_hotkey_id id = (obs_hotkey_id)calldata_int(&cb->base.extra, "id"); + + if (id != OBS_INVALID_HOTKEY_ID) + defer_call_post(defer_hotkey_unregister, (void*)(uintptr_t)id); +} + +static void hotkey_pressed(void *p_cb, bool pressed) +{ + struct python_obs_callback *cb = p_cb; + + if (cb->base.removed) + return; + + lock_callback(cb); + + PyObject *py_pressed = PyBool_FromLong(pressed); + PyObject *args = Py_BuildValue("(O)", py_pressed); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + py_error(); + Py_XDECREF(py_ret); + Py_XDECREF(args); + Py_XDECREF(py_pressed); + + unlock_callback(); +} + +static void defer_hotkey_pressed(void *p_cb) +{ + hotkey_pressed(p_cb, true); +} + +static void defer_hotkey_unpressed(void *p_cb) +{ + hotkey_pressed(p_cb, false); +} + +static inline PyObject *py_invalid_hotkey_id() +{ + return PyLong_FromUnsignedLongLong(OBS_INVALID_HOTKEY_ID); +} + +static void hotkey_callback(void *p_cb, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + struct python_obs_callback *cb = p_cb; + + if (cb->base.removed) + return; + + if (pressed) + defer_call_post(defer_hotkey_pressed, cb); + else + defer_call_post(defer_hotkey_unpressed, cb); + + UNUSED_PARAMETER(hotkey); + UNUSED_PARAMETER(id); +} + +static PyObject *hotkey_unregister(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_cb = NULL; + + if (!script) { + PyErr_SetString(PyExc_RuntimeError, + "No active script, report this to Jim"); + return NULL; + } + + if (!parse_args(args, "O", &py_cb)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = find_python_obs_callback(script, py_cb); + if (cb) remove_python_obs_callback(cb); + + UNUSED_PARAMETER(self); + return python_none(); +} + +static PyObject *hotkey_register_frontend(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + const char *name; + const char *desc; + obs_hotkey_id id; + PyObject *py_cb; + + if (!parse_args(args, "ssO", &name, &desc, &py_cb)) + return py_invalid_hotkey_id(); + if (!py_cb || !PyFunction_Check(py_cb)) + return py_invalid_hotkey_id(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + cb->base.on_remove = on_remove_hotkey; + id = obs_hotkey_register_frontend(name, desc, hotkey_callback, cb); + calldata_set_int(&cb->base.extra, "id", id); + + if (id == OBS_INVALID_HOTKEY_ID) + remove_python_obs_callback(cb); + + UNUSED_PARAMETER(self); + return PyLong_FromUnsignedLongLong(id); +} + +/* -------------------------------------------- */ + +static bool button_prop_clicked(obs_properties_t *props, obs_property_t *p, + void *p_cb) +{ + struct python_obs_callback *cb = p_cb; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(cb); + + PyObject *py_props = NULL; + PyObject *py_p = NULL; + + if (libobs_to_py(obs_properties_t, props, false, &py_props) && + libobs_to_py(obs_property_t, p, false, &py_p)) { + + PyObject *args = Py_BuildValue("(OO)", py_props, py_p); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + if (!py_error()) + ret = py_ret == Py_True; + Py_XDECREF(py_ret); + Py_XDECREF(args); + } + + Py_XDECREF(py_p); + Py_XDECREF(py_props); + + unlock_callback(); + + return ret; +} + +static PyObject *properties_add_button(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + obs_properties_t *props; + obs_property_t *p; + PyObject *py_props; + PyObject *py_ret; + const char *name; + const char *text; + PyObject *py_cb; + + if (!parse_args(args, "OssO", &py_props, &name, &text, &py_cb)) + return python_none(); + if (!py_to_libobs(obs_properties_t, py_props, &props)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + p = obs_properties_add_button2(props, name, text, button_prop_clicked, + cb); + + if (!p || !libobs_to_py(obs_property_t, p, false, &py_ret)) + return python_none(); + + UNUSED_PARAMETER(self); + return py_ret; +} + +/* -------------------------------------------- */ + +static bool modified_callback(void *p_cb, obs_properties_t *props, + obs_property_t *p, obs_data_t *settings) +{ + struct python_obs_callback *cb = p_cb; + bool ret = false; + + if (cb->base.removed) + return false; + + lock_callback(cb); + + PyObject *py_props = NULL; + PyObject *py_p = NULL; + PyObject *py_settings = NULL; + + if (libobs_to_py(obs_properties_t, props, false, &py_props) && + libobs_to_py(obs_property_t, p, false, &py_p) && + libobs_to_py(obs_data_t, settings, false, &py_settings)) { + + PyObject *args = Py_BuildValue("(OOO)", py_props, py_p, + py_settings); + PyObject *py_ret = PyObject_CallObject(cb->func, args); + if (!py_error()) + ret = py_ret == Py_True; + Py_XDECREF(py_ret); + Py_XDECREF(args); + } + + Py_XDECREF(py_settings); + Py_XDECREF(py_p); + Py_XDECREF(py_props); + + unlock_callback(); + + return ret; +} + +static PyObject *property_set_modified_callback(PyObject *self, PyObject *args) +{ + struct obs_python_script *script = cur_python_script; + PyObject *py_p; + PyObject *py_cb; + obs_property_t *p; + + if (!parse_args(args, "OO", &py_p, &py_cb)) + return python_none(); + if (!py_to_libobs(obs_property_t, py_p, &p)) + return python_none(); + if (!py_cb || !PyFunction_Check(py_cb)) + return python_none(); + + struct python_obs_callback *cb = add_python_obs_callback(script, py_cb); + obs_property_set_modified_callback2(p, modified_callback, cb); + + UNUSED_PARAMETER(self); + return python_none(); +} + +/* -------------------------------------------- */ + +static PyObject *remove_current_callback(PyObject *self, PyObject *args) +{ + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + if (cur_python_cb) + remove_python_obs_callback(cur_python_cb); + return python_none(); +} + +/* -------------------------------------------- */ + +static PyObject *calldata_source(PyObject *self, PyObject *args) +{ + PyObject *py_ret = NULL; + PyObject *py_cd = NULL; + + calldata_t *cd; + const char *name; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "Os", &py_cd, &name)) + goto fail; + if (!py_to_libobs(calldata_t, py_cd, &cd)) + goto fail; + + obs_source_t *source = calldata_ptr(cd, name); + libobs_to_py(obs_source_t, source, false, &py_ret); + +fail: + return py_ret; +} + +static PyObject *calldata_sceneitem(PyObject *self, PyObject *args) +{ + PyObject *py_ret = NULL; + PyObject *py_cd = NULL; + + calldata_t *cd; + const char *name; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "Os", &py_cd, &name)) + goto fail; + if (!py_to_libobs(calldata_t, py_cd, &cd)) + goto fail; + + obs_sceneitem_t *item = calldata_ptr(cd, name); + libobs_to_py(obs_sceneitem_t, item, false, &py_ret); + +fail: + return py_ret; +} + +/* -------------------------------------------- */ + +static bool enum_sources_proc(void *param, obs_source_t *source) +{ + PyObject *list = param; + PyObject *py_source; + + if (libobs_to_py(obs_source_t, source, false, &py_source)) { + obs_source_get_ref(source); + PyList_Append(list, py_source); + Py_DECREF(py_source); + } + return true; +} + +static PyObject *enum_sources(PyObject *self, PyObject *args) +{ + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(args); + + PyObject *list = PyList_New(0); + obs_enum_sources(enum_sources_proc, list); + return list; +} + +/* -------------------------------------------- */ + +static bool enum_items_proc(obs_scene_t *scene, obs_sceneitem_t *item, + void *param) +{ + PyObject *list = param; + PyObject *py_item; + + UNUSED_PARAMETER(scene); + + if (libobs_to_py(obs_sceneitem_t, item, false, &py_item)) { + obs_sceneitem_addref(item); + PyList_Append(list, py_item); + Py_DECREF(py_item); + } + return true; +} + +static PyObject *scene_enum_items(PyObject *self, PyObject *args) +{ + PyObject *py_scene; + obs_scene_t *scene; + + UNUSED_PARAMETER(self); + + if (!parse_args(args, "O", &py_scene)) + return python_none(); + if (!py_to_libobs(obs_scene_t, py_scene, &scene)) + return python_none(); + + PyObject *list = PyList_New(0); + obs_scene_enum_items(scene, enum_items_proc, list); + return list; +} + +/* -------------------------------------------- */ + +static PyObject *source_list_release(PyObject *self, PyObject *args) +{ + PyObject *list; + if (!parse_args(args, "O", &list)) + return python_none(); + + Py_ssize_t count = PyList_Size(list); + for (Py_ssize_t i = 0; i < count; i++) { + PyObject *py_source = PyList_GetItem(list, i); + obs_source_t *source; + + if (py_to_libobs(obs_source_t, py_source, &source)) { + obs_source_release(source); + } + } + + UNUSED_PARAMETER(self); + return python_none(); +} + +static PyObject *sceneitem_list_release(PyObject *self, PyObject *args) +{ + PyObject *list; + if (!parse_args(args, "O", &list)) + return python_none(); + + Py_ssize_t count = PyList_Size(list); + for (Py_ssize_t i = 0; i < count; i++) { + PyObject *py_item = PyList_GetItem(list, i); + obs_sceneitem_t *item; + + if (py_to_libobs(obs_sceneitem_t, py_item, &item)) { + obs_sceneitem_release(item); + } + } + + UNUSED_PARAMETER(self); + return python_none(); +} + +/* -------------------------------------------- */ + +struct dstr cur_py_log_chunk = {0}; + +static PyObject *py_script_log(PyObject *self, PyObject *args) +{ + static bool calling_self = false; + int log_level; + const char *msg; + + UNUSED_PARAMETER(self); + + if (calling_self) + return python_none(); + calling_self = true; + + /* ------------------- */ + + if (!parse_args(args, "is", &log_level, &msg)) + goto fail; + if (!msg || !*msg) + goto fail; + + dstr_cat(&cur_py_log_chunk, msg); + + const char *start = cur_py_log_chunk.array; + char *endl = strchr(start, '\n'); + + while (endl) { + *endl = 0; + script_log(&cur_python_script->base, log_level, "%s", start); + *endl = '\n'; + + start = endl + 1; + endl = strchr(start, '\n'); + } + + if (start) { + size_t len = strlen(start); + if (len) memmove(cur_py_log_chunk.array, start, len); + dstr_resize(&cur_py_log_chunk, len); + } + + /* ------------------- */ + +fail: + calling_self = false; + return python_none(); +} + +/* -------------------------------------------- */ + +static void add_hook_functions(PyObject *module) +{ + static PyMethodDef funcs[] = { +#define DEF_FUNC(n, c) {n, c, METH_VARARGS, NULL} + + DEF_FUNC("script_log", py_script_log), + DEF_FUNC("timer_remove", timer_remove), + DEF_FUNC("timer_add", timer_add), + DEF_FUNC("calldata_source", calldata_source), + DEF_FUNC("calldata_sceneitem", calldata_sceneitem), + DEF_FUNC("source_list_release", source_list_release), + DEF_FUNC("sceneitem_list_release", sceneitem_list_release), + DEF_FUNC("obs_enum_sources", enum_sources), + DEF_FUNC("obs_scene_enum_items", scene_enum_items), + DEF_FUNC("obs_remove_tick_callback", + obs_python_remove_tick_callback), + DEF_FUNC("obs_add_tick_callback", + obs_python_add_tick_callback), + DEF_FUNC("signal_handler_disconnect", + obs_python_signal_handler_disconnect), + DEF_FUNC("signal_handler_connect", + obs_python_signal_handler_connect), + DEF_FUNC("signal_handler_disconnect_global", + obs_python_signal_handler_disconnect_global), + DEF_FUNC("signal_handler_connect_global", + obs_python_signal_handler_connect_global), + DEF_FUNC("obs_hotkey_unregister", + hotkey_unregister), + DEF_FUNC("obs_hotkey_register_frontend", + hotkey_register_frontend), + DEF_FUNC("obs_properties_add_button", + properties_add_button), + DEF_FUNC("obs_property_set_modified_callback", + property_set_modified_callback), + DEF_FUNC("remove_current_callback", + remove_current_callback), + +#undef DEF_FUNC + {0} + }; + + add_functions_to_py_module(module, funcs); +} + +/* -------------------------------------------- */ + +void obs_python_script_update(obs_script_t *script, obs_data_t *settings); + +bool obs_python_script_load(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + if (python_loaded && !data->base.loaded) { + lock_python(); + if (!data->module) + add_to_python_path(data->dir.array); + data->base.loaded = load_python_script(data); + unlock_python(); + + if (data->base.loaded) + obs_python_script_update(s, NULL); + } + + return data->base.loaded; +} + +obs_script_t *obs_python_script_create(const char *path, obs_data_t *settings) +{ + struct obs_python_script *data = bzalloc(sizeof(*data)); + + data->base.type = OBS_SCRIPT_LANG_PYTHON; + + dstr_copy(&data->base.path, path); + dstr_replace(&data->base.path, "\\", "/"); + path = data->base.path.array; + + const char *slash = path && *path ? strrchr(path, '/') : NULL; + if (slash) { + slash++; + dstr_copy(&data->base.file, slash); + dstr_left(&data->dir, &data->base.path, slash - path); + } else { + dstr_copy(&data->base.file, path); + } + + path = data->base.file.array; + dstr_copy_dstr(&data->name, &data->base.file); + + const char *ext = strstr(path, ".py"); + if (ext) + dstr_resize(&data->name, ext - path); + + data->base.settings = obs_data_create(); + if (settings) + obs_data_apply(data->base.settings, settings); + + if (!python_loaded) + return (obs_script_t *)data; + + lock_python(); + add_to_python_path(data->dir.array); + data->base.loaded = load_python_script(data); + if (data->base.loaded) { + cur_python_script = data; + obs_python_script_update(&data->base, NULL); + cur_python_script = NULL; + } + unlock_python(); + + return (obs_script_t *)data; +} + +void obs_python_script_unload(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (!s->loaded || !python_loaded) + return; + + /* ---------------------------- */ + /* unhook tick function */ + + if (data->p_prev_next_tick) { + pthread_mutex_lock(&tick_mutex); + + struct obs_python_script *next = data->next_tick; + if (next) next->p_prev_next_tick = data->p_prev_next_tick; + *data->p_prev_next_tick = next; + + pthread_mutex_unlock(&tick_mutex); + + data->p_prev_next_tick = NULL; + data->next_tick = NULL; + } + + lock_python(); + + Py_XDECREF(data->tick); + Py_XDECREF(data->save); + Py_XDECREF(data->update); + Py_XDECREF(data->get_properties); + data->tick = NULL; + data->save = NULL; + data->update = NULL; + data->get_properties = NULL; + + /* ---------------------------- */ + /* remove all callbacks */ + + struct script_callback *cb = data->first_callback; + while (cb) { + struct script_callback *next = cb->next; + remove_script_callback(cb); + cb = next; + } + + /* ---------------------------- */ + /* unload */ + + unload_python_script(data); + unlock_python(); + + s->loaded = false; +} + +void obs_python_script_destroy(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (data) { + if (python_loaded) { + lock_python(); + Py_XDECREF(data->module); + unlock_python(); + } + + dstr_free(&data->base.path); + dstr_free(&data->base.file); + dstr_free(&data->base.desc); + obs_data_release(data->base.settings); + dstr_free(&data->dir); + dstr_free(&data->name); + bfree(data); + } +} + +void obs_python_script_update(obs_script_t *s, obs_data_t *settings) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (!s->loaded || !python_loaded) + return; + if (!data->update) + return; + + if (settings) + obs_data_apply(s->settings, settings); + + lock_python(); + cur_python_script = data; + + PyObject *py_settings; + if (libobs_to_py(obs_data_t, s->settings, false, &py_settings)) { + PyObject *args = Py_BuildValue("(O)", py_settings); + PyObject *ret = PyObject_CallObject(data->update, args); + py_error(); + + Py_XDECREF(ret); + Py_XDECREF(args); + Py_XDECREF(py_settings); + } + + cur_python_script = NULL; + unlock_python(); +} + +obs_properties_t *obs_python_script_get_properties(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + obs_properties_t *props = NULL; + + if (!s->loaded || !python_loaded) + return NULL; + if (!data->get_properties) + return NULL; + + lock_python(); + cur_python_script = data; + + PyObject *ret = PyObject_CallObject(data->get_properties, NULL); + if (!py_error()) + py_to_libobs(obs_properties_t, ret, &props); + Py_XDECREF(ret); + + cur_python_script = NULL; + unlock_python(); + + return props; +} + +void obs_python_script_save(obs_script_t *s) +{ + struct obs_python_script *data = (struct obs_python_script *)s; + + if (!s->loaded || !python_loaded) + return; + if (!data->save) + return; + + lock_python(); + cur_python_script = data; + + PyObject *py_settings; + if (libobs_to_py(obs_data_t, s->settings, false, &py_settings)) { + PyObject *args = Py_BuildValue("(O)", py_settings); + PyObject *ret = PyObject_CallObject(data->save, args); + py_error(); + Py_XDECREF(ret); + Py_XDECREF(args); + Py_XDECREF(py_settings); + } + + cur_python_script = NULL; + unlock_python(); +} + +/* -------------------------------------------- */ + +static void python_tick(void *param, float seconds) +{ + struct obs_python_script *data; + bool valid; + uint64_t ts = obs_get_video_frame_time(); + + pthread_mutex_lock(&tick_mutex); + valid = !!first_tick_script; + pthread_mutex_unlock(&tick_mutex); + + /* --------------------------------- */ + /* process script_tick calls */ + + if (valid) { + lock_python(); + + PyObject *args = Py_BuildValue("(f)", seconds); + + pthread_mutex_lock(&tick_mutex); + data = first_tick_script; + while (data) { + cur_python_script = data; + + PyObject *py_ret = PyObject_CallObject(data->tick, args); + Py_XDECREF(py_ret); + py_error(); + + data = data->next_tick; + } + + cur_python_script = NULL; + + pthread_mutex_unlock(&tick_mutex); + + Py_XDECREF(args); + + unlock_python(); + } + + /* --------------------------------- */ + /* process timers */ + + pthread_mutex_lock(&timer_mutex); + struct python_obs_timer *timer = first_timer; + while (timer) { + struct python_obs_timer *next = timer->next; + struct python_obs_callback *cb = python_obs_timer_cb(timer); + + if (cb->base.removed) { + python_obs_timer_remove(timer); + } else { + uint64_t elapsed = ts - timer->last_ts; + + if (elapsed >= timer->interval) { + lock_python(); + timer_call(&cb->base); + unlock_python(); + + timer->last_ts += timer->interval; + } + } + + timer = next; + } + pthread_mutex_unlock(&timer_mutex); + + UNUSED_PARAMETER(param); +} + +/* -------------------------------------------- */ + +void obs_python_unload(void); + +bool obs_scripting_python_runtime_linked(void) +{ + return (bool)RUNTIME_LINK; +} + +bool obs_scripting_python_loaded(void) +{ + return python_loaded; +} + +void obs_python_load(void) +{ + da_init(python_paths); + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&tick_mutex, NULL); + pthread_mutex_init(&timer_mutex, &attr); + +} + +extern void add_python_frontend_funcs(PyObject *module); + +bool obs_scripting_load_python(const char *python_path) +{ + if (python_loaded) + return true; + + /* Use external python on windows and mac */ +#if RUNTIME_LINK +# if 0 + struct dstr old_path = {0}; + struct dstr new_path = {0}; +# endif + + if (!import_python(python_path)) + return false; + + if (python_path && *python_path) { + os_utf8_to_wcs(python_path, 0, home_path, 1024); + Py_SetPythonHome(home_path); +# if 0 + dstr_copy(&old_path, getenv("PATH")); + _putenv("PYTHONPATH="); + _putenv("PATH="); +# endif + } +#else + UNUSED_PARAMETER(python_path); +#endif + + Py_Initialize(); + if (!Py_IsInitialized()) + return false; + +#if 0 +# ifdef _DEBUG + if (pythondir && *pythondir) { + dstr_printf(&new_path, "PATH=%s", old_path.array); + _putenv(new_path.array); + } +# endif + + bfree(pythondir); + dstr_free(&new_path); + dstr_free(&old_path); +#endif + + PyEval_InitThreads(); + if (!PyEval_ThreadsInitialized()) + return false; + + /* ---------------------------------------------- */ + /* Must set arguments for guis to work */ + + wchar_t *argv[] = {L"", NULL}; + int argc = sizeof(argv) / sizeof(wchar_t*) - 1; + + PySys_SetArgv(argc, argv); + +#ifdef DEBUG_PYTHON_STARTUP + /* ---------------------------------------------- */ + /* Debug logging to file if startup is failing */ + + PyRun_SimpleString("import os"); + PyRun_SimpleString("import sys"); + PyRun_SimpleString("os.environ['PYTHONUNBUFFERED'] = '1'"); + PyRun_SimpleString("sys.stdout = open('./stdOut.txt','w',1)"); + PyRun_SimpleString("sys.stderr = open('./stdErr.txt','w',1)"); + PyRun_SimpleString("print(sys.version)"); +#endif + + /* ---------------------------------------------- */ + /* Load main interface module */ + + py_obspython = PyImport_ImportModule("obspython"); + bool success = !py_error(); + if (!success) { + warn("Error importing obspython.py', unloading obs-python"); + goto out; + } + + python_loaded = PyRun_SimpleString(startup_script) == 0; + py_error(); + + add_hook_functions(py_obspython); + py_error(); + + add_python_frontend_funcs(py_obspython); + py_error(); + +out: + /* ---------------------------------------------- */ + /* Free data */ + + PyEval_ReleaseThread(PyGILState_GetThisThreadState()); + + if (!success) { + warn("Failed to load python plugin"); + obs_python_unload(); + } + + if (python_loaded) + obs_add_tick_callback(python_tick, NULL); + + return python_loaded; +} + +void obs_python_unload(void) +{ + if (python_loaded && Py_IsInitialized()) { + PyGILState_Ensure(); + + Py_XDECREF(py_obspython); + Py_Finalize(); + } + + /* ---------------------- */ + + obs_remove_tick_callback(python_tick, NULL); + + for (size_t i = 0; i < python_paths.num; i++) + bfree(python_paths.array[i]); + da_free(python_paths); + + pthread_mutex_destroy(&tick_mutex); + pthread_mutex_destroy(&timer_mutex); + dstr_free(&cur_py_log_chunk); +} diff --git a/deps/obs-scripting/obs-scripting-python.h b/deps/obs-scripting/obs-scripting-python.h new file mode 100644 index 000000000..15bb286c2 --- /dev/null +++ b/deps/obs-scripting/obs-scripting-python.h @@ -0,0 +1,244 @@ +/****************************************************************************** + Copyright (C) 2015 by Andrew Skinner + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +/* ---------------------------- */ + +#define SWIG_TYPE_TABLE obspython +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4115) +#pragma warning(disable : 4204) +#endif + +#include "obs-scripting-python-import.h" + +#include +#include "swig/swigpyrun.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/* ---------------------------- */ + +#include "obs-scripting-internal.h" +#include "obs-scripting-callback.h" + +#ifdef _WIN32 +#define __func__ __FUNCTION__ +#else +#include +#endif + +#include +#include +#include + +#define do_log(level, format, ...) \ + blog(level, "[Python] " format, ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) +#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) + +/* ------------------------------------------------------------ */ + +struct python_obs_callback; + +struct obs_python_script { + obs_script_t base; + + struct dstr dir; + struct dstr name; + + PyObject *module; + + PyObject *save; + PyObject *update; + PyObject *get_properties; + + struct script_callback *first_callback; + + PyObject *tick; + struct obs_python_script *next_tick; + struct obs_python_script **p_prev_next_tick; +}; + +/* ------------------------------------------------------------ */ + +struct python_obs_callback { + struct script_callback base; + + PyObject *func; +}; + +static inline struct python_obs_callback *add_python_obs_callback_extra( + struct obs_python_script *script, + PyObject *func, + size_t extra_size) +{ + struct python_obs_callback *cb = add_script_callback( + &script->first_callback, + (obs_script_t *)script, + sizeof(*cb) + extra_size); + + Py_XINCREF(func); + cb->func = func; + return cb; +} + +static inline struct python_obs_callback *add_python_obs_callback( + struct obs_python_script *script, + PyObject *func) +{ + return add_python_obs_callback_extra(script, func, 0); +} + +static inline void *python_obs_callback_extra_data( + struct python_obs_callback *cb) +{ + return (void*)&cb[1]; +} + +static inline struct obs_python_script *python_obs_callback_script( + struct python_obs_callback *cb) +{ + return (struct obs_python_script *)cb->base.script; +} + +static inline struct python_obs_callback *find_next_python_obs_callback( + struct obs_python_script *script, + struct python_obs_callback *cb, PyObject *func) +{ + cb = cb ? (struct python_obs_callback *)cb->base.next + : (struct python_obs_callback *)script->first_callback; + + while (cb) { + if (cb->func == func) + break; + cb = (struct python_obs_callback *)cb->base.next; + } + + return cb; +} + +static inline struct python_obs_callback *find_python_obs_callback( + struct obs_python_script *script, + PyObject *func) +{ + return find_next_python_obs_callback(script, NULL, func); +} + +static inline void remove_python_obs_callback(struct python_obs_callback *cb) +{ + remove_script_callback(&cb->base); + + Py_XDECREF(cb->func); + cb->func = NULL; +} + +static inline void just_free_python_obs_callback(struct python_obs_callback *cb) +{ + just_free_script_callback(&cb->base); +} + +static inline void free_python_obs_callback(struct python_obs_callback *cb) +{ + free_script_callback(&cb->base); +} + +/* ------------------------------------------------------------ */ + +static int parse_args_(PyObject *args, const char *func, const char *format, ...) +{ + char new_format[128]; + va_list va_args; + int ret; + + snprintf(new_format, sizeof(new_format), "%s:%s", format, func); + + va_start(va_args, format); + ret = PyArg_VaParse(args, new_format, va_args); + va_end(va_args); + + return ret; +} + +#define parse_args(args, format, ...) \ + parse_args_(args, __FUNCTION__, format, ##__VA_ARGS__) + +static inline bool py_error_(const char *func, int line) +{ + if (PyErr_Occurred()) { + warn("Python failure in %s:%d:", func, line); + PyErr_Print(); + return true; + } + return false; +} + +#define py_error() py_error_(__FUNCTION__, __LINE__) + +#define lock_python() \ + PyGILState_STATE gstate = PyGILState_Ensure() +#define unlock_python() \ + PyGILState_Release(gstate) + +struct py_source; +typedef struct py_source py_source_t; + +extern PyObject* py_libobs; +extern struct python_obs_callback *cur_python_cb; +extern struct obs_python_script *cur_python_script; + +extern void py_to_obs_source_info(py_source_t *py_info); +extern PyObject *py_obs_register_source(PyObject *self, PyObject *args); +extern PyObject *py_obs_get_script_config_path(PyObject *self, PyObject *args); +extern void add_functions_to_py_module(PyObject *module, + PyMethodDef *method_list); + +/* ------------------------------------------------------------ */ +/* Warning: the following functions expect python to be locked! */ + +extern bool py_to_libobs_(const char *type, + PyObject * py_in, + void * libobs_out, + const char *id, + const char *func, + int line); + +extern bool libobs_to_py_(const char *type, + void * libobs_in, + bool ownership, + PyObject ** py_out, + const char *id, + const char *func, + int line); + +extern bool py_call(PyObject *call, PyObject **ret, const char *arg_def, ...); +extern bool py_import_script(const char *name); + +static inline PyObject *python_none(void) +{ + PyObject *none = Py_None; + Py_INCREF(none); + return none; +} diff --git a/deps/obs-scripting/obs-scripting.c b/deps/obs-scripting/obs-scripting.c new file mode 100644 index 000000000..7f1a36d23 --- /dev/null +++ b/deps/obs-scripting/obs-scripting.c @@ -0,0 +1,455 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include +#include +#include +#include + +#include "obs-scripting-internal.h" +#include "obs-scripting-callback.h" +#include "obs-scripting-config.h" + +#if COMPILE_LUA +extern obs_script_t *obs_lua_script_create(const char *path, + obs_data_t *settings); +extern bool obs_lua_script_load(obs_script_t *s); +extern void obs_lua_script_unload(obs_script_t *s); +extern void obs_lua_script_destroy(obs_script_t *s); +extern void obs_lua_load(void); +extern void obs_lua_unload(void); + +extern obs_properties_t *obs_lua_script_get_properties(obs_script_t *script); +extern void obs_lua_script_update(obs_script_t *script, obs_data_t *settings); +extern void obs_lua_script_save(obs_script_t *script); +#endif + +#if COMPILE_PYTHON +extern obs_script_t *obs_python_script_create(const char *path, + obs_data_t *settings); +extern bool obs_python_script_load(obs_script_t *s); +extern void obs_python_script_unload(obs_script_t *s); +extern void obs_python_script_destroy(obs_script_t *s); +extern void obs_python_load(void); +extern void obs_python_unload(void); + +extern obs_properties_t *obs_python_script_get_properties(obs_script_t *script); +extern void obs_python_script_update(obs_script_t *script, obs_data_t *settings); +extern void obs_python_script_save(obs_script_t *script); +#endif + +pthread_mutex_t detach_mutex; +struct script_callback *detached_callbacks; + +static struct dstr file_filter = {0}; +static bool scripting_loaded = false; + +static const char *supported_formats[] = { +#if COMPILE_LUA + "lua", +#endif +#if COMPILE_PYTHON + "py", +#endif + NULL +}; + +/* -------------------------------------------- */ + +static pthread_mutex_t defer_call_mutex; +static struct circlebuf defer_call_queue; +static bool defer_call_exit = false; +static os_sem_t *defer_call_semaphore; +static pthread_t defer_call_thread; + +struct defer_call { + defer_call_cb call; + void *cb; +}; + +static void *defer_thread(void *unused) +{ + UNUSED_PARAMETER(unused); + + while (os_sem_wait(defer_call_semaphore) == 0) { + struct defer_call info; + + pthread_mutex_lock(&defer_call_mutex); + if (defer_call_exit) { + pthread_mutex_unlock(&defer_call_mutex); + return NULL; + } + + circlebuf_pop_front(&defer_call_queue, &info, sizeof(info)); + pthread_mutex_unlock(&defer_call_mutex); + + info.call(info.cb); + } + + return NULL; +} + +void defer_call_post(defer_call_cb call, void *cb) +{ + struct defer_call info; + info.call = call; + info.cb = cb; + + pthread_mutex_lock(&defer_call_mutex); + if (!defer_call_exit) + circlebuf_push_back(&defer_call_queue, &info, sizeof(info)); + pthread_mutex_unlock(&defer_call_mutex); + + os_sem_post(defer_call_semaphore); +} + +/* -------------------------------------------- */ + +bool obs_scripting_load(void) +{ + circlebuf_init(&defer_call_queue); + + if (pthread_mutex_init(&detach_mutex, NULL) != 0) { + return false; + } + if (pthread_mutex_init(&defer_call_mutex, NULL) != 0) { + pthread_mutex_destroy(&detach_mutex); + return false; + } + if (os_sem_init(&defer_call_semaphore, 0) != 0) { + pthread_mutex_destroy(&defer_call_mutex); + pthread_mutex_destroy(&detach_mutex); + return false; + } + + if (pthread_create(&defer_call_thread, NULL, defer_thread, NULL) != 0) { + os_sem_destroy(defer_call_semaphore); + pthread_mutex_destroy(&defer_call_mutex); + pthread_mutex_destroy(&detach_mutex); + return false; + } + +#if COMPILE_LUA + obs_lua_load(); +#endif + +#if COMPILE_PYTHON + obs_python_load(); + obs_scripting_load_python(NULL); +#endif + + scripting_loaded = true; + return true; +} + +void obs_scripting_unload(void) +{ + if (!scripting_loaded) + return; + + /* ---------------------- */ + +#if COMPILE_LUA + obs_lua_unload(); +#endif + +#if COMPILE_PYTHON + obs_python_unload(); +#endif + + dstr_free(&file_filter); + + /* ---------------------- */ + + int total_detached = 0; + + pthread_mutex_lock(&detach_mutex); + + struct script_callback *cur = detached_callbacks; + while (cur) { + struct script_callback *next = cur->next; + just_free_script_callback(cur); + cur = next; + + ++total_detached; + } + + pthread_mutex_unlock(&detach_mutex); + pthread_mutex_destroy(&detach_mutex); + + blog(LOG_INFO, "[Scripting] Total detached callbacks: %d", + total_detached); + + /* ---------------------- */ + + pthread_mutex_lock(&defer_call_mutex); + + /* TODO */ + + defer_call_exit = true; + circlebuf_free(&defer_call_queue); + + pthread_mutex_unlock(&defer_call_mutex); + + os_sem_post(defer_call_semaphore); + pthread_join(defer_call_thread, NULL); + + pthread_mutex_destroy(&defer_call_mutex); + os_sem_destroy(defer_call_semaphore); +} + +const char **obs_scripting_supported_formats(void) +{ + return supported_formats; +} + +static inline bool pointer_valid(const void *x, const char *name, + const char *func) +{ + if (!x) { + blog(LOG_WARNING, "obs-scripting: [%s] %s is null", + func, name); + return false; + } + + return true; +} + +#define ptr_valid(x) pointer_valid(x, #x, __FUNCTION__) + +obs_script_t *obs_script_create(const char *path, obs_data_t *settings) +{ + obs_script_t *script = NULL; + const char *ext; + + if (!scripting_loaded) + return NULL; + if (!ptr_valid(path)) + return NULL; + + ext = strrchr(path, '.'); + if (!ext) + return NULL; + +#if COMPILE_LUA + if (strcmp(ext, ".lua") == 0) { + script = obs_lua_script_create(path, settings); + } else +#endif +#if COMPILE_PYTHON + if (strcmp(ext, ".py") == 0) { + script = obs_python_script_create(path, settings); + } else +#endif + { + blog(LOG_WARNING, "Unsupported/unknown script type: %s", path); + } + + return script; +} + +const char *obs_script_get_description(const obs_script_t *script) +{ + return ptr_valid(script) ? script->desc.array : NULL; +} + +const char *obs_script_get_path(const obs_script_t *script) +{ + const char *path = ptr_valid(script) ? script->path.array : ""; + return path ? path : ""; +} + +const char *obs_script_get_file(const obs_script_t *script) +{ + const char *file = ptr_valid(script) ? script->file.array : ""; + return file ? file : ""; +} + +enum obs_script_lang obs_script_get_lang(const obs_script_t *script) +{ + return ptr_valid(script) ? script->type : OBS_SCRIPT_LANG_UNKNOWN; +} + +obs_data_t *obs_script_get_settings(obs_script_t *script) +{ + obs_data_t *settings; + + if (!ptr_valid(script)) + return NULL; + + settings = script->settings; + obs_data_addref(settings); + return settings; +} + +obs_properties_t *obs_script_get_properties(obs_script_t *script) +{ + obs_properties_t *props = NULL; + + if (!ptr_valid(script)) + return NULL; +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + props = obs_lua_script_get_properties(script); + goto out; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + props = obs_python_script_get_properties(script); + goto out; + } +#endif + +out: + if (!props) + props = obs_properties_create(); + return props; +} + +obs_data_t *obs_script_save(obs_script_t *script) +{ + obs_data_t *settings; + + if (!ptr_valid(script)) + return NULL; + +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_save(script); + goto out; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_save(script); + goto out; + } +#endif + +out: + settings = script->settings; + obs_data_addref(settings); + return settings; +} + +static void clear_queue_signal(void *p_event) +{ + os_event_t *event = p_event; + os_event_signal(event); +} + +static void clear_call_queue(void) +{ + os_event_t *event; + if (os_event_init(&event, OS_EVENT_TYPE_AUTO) != 0) + return; + + defer_call_post(clear_queue_signal, event); + + os_event_wait(event); + os_event_destroy(event); +} + +void obs_script_update(obs_script_t *script, obs_data_t *settings) +{ + if (!ptr_valid(script)) + return; +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_update(script, settings); + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_update(script, settings); + } +#endif +} + +bool obs_script_reload(obs_script_t *script) +{ + if (!scripting_loaded) + return false; + if (!ptr_valid(script)) + return false; + +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_unload(script); + clear_call_queue(); + obs_lua_script_load(script); + goto out; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_unload(script); + clear_call_queue(); + obs_python_script_load(script); + goto out; + } +#endif + +out: + return script->loaded; +} + +bool obs_script_loaded(const obs_script_t *script) +{ + return ptr_valid(script) ? script->loaded : false; +} + +void obs_script_destroy(obs_script_t *script) +{ + if (!script) + return; + +#if COMPILE_LUA + if (script->type == OBS_SCRIPT_LANG_LUA) { + obs_lua_script_unload(script); + obs_lua_script_destroy(script); + return; + } +#endif +#if COMPILE_PYTHON + if (script->type == OBS_SCRIPT_LANG_PYTHON) { + obs_python_script_unload(script); + obs_python_script_destroy(script); + return; + } +#endif +} + +#if !COMPILE_PYTHON +bool obs_scripting_load_python(const char *python_path) +{ + UNUSED_PARAMETER(python_path); + return false; +} + +bool obs_scripting_python_loaded(void) +{ + return false; +} + +bool obs_scripting_python_runtime_linked(void) +{ + return (bool)true; +} +#endif diff --git a/deps/obs-scripting/obs-scripting.h b/deps/obs-scripting/obs-scripting.h new file mode 100644 index 000000000..d0e0b9021 --- /dev/null +++ b/deps/obs-scripting/obs-scripting.h @@ -0,0 +1,73 @@ +/****************************************************************************** + Copyright (C) 2017 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct obs_script; +typedef struct obs_script obs_script_t; + +enum obs_script_lang { + OBS_SCRIPT_LANG_UNKNOWN, + OBS_SCRIPT_LANG_LUA, + OBS_SCRIPT_LANG_PYTHON +}; + +EXPORT bool obs_scripting_load(void); +EXPORT void obs_scripting_unload(void); +EXPORT const char **obs_scripting_supported_formats(void); + +typedef void (*scripting_log_handler_t)( + void *p, + obs_script_t *script, + int lvl, + const char *msg); + +EXPORT void obs_scripting_set_log_callback( + scripting_log_handler_t handler, void *param); + +EXPORT bool obs_scripting_python_runtime_linked(void); +EXPORT bool obs_scripting_python_loaded(void); +EXPORT bool obs_scripting_load_python(const char *python_path); + +EXPORT obs_script_t *obs_script_create(const char *path, obs_data_t *settings); +EXPORT void obs_script_destroy(obs_script_t *script); + +EXPORT const char *obs_script_get_description(const obs_script_t *script); +EXPORT const char *obs_script_get_path(const obs_script_t *script); +EXPORT const char *obs_script_get_file(const obs_script_t *script); +EXPORT enum obs_script_lang obs_script_get_lang(const obs_script_t *script); + +EXPORT obs_properties_t *obs_script_get_properties(obs_script_t *script); +EXPORT obs_data_t *obs_script_save(obs_script_t *script); +EXPORT obs_data_t *obs_script_get_settings(obs_script_t *script); +EXPORT void obs_script_update(obs_script_t *script, obs_data_t *settings); + +EXPORT bool obs_script_loaded(const obs_script_t *script); +EXPORT bool obs_script_reload(obs_script_t *script); + +#ifdef __cplusplus +} +#endif diff --git a/deps/obs-scripting/obslua/CMakeLists.txt b/deps/obs-scripting/obslua/CMakeLists.txt new file mode 100644 index 000000000..3024233b2 --- /dev/null +++ b/deps/obs-scripting/obslua/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 2.8) +project(obslua) + +find_package(SWIG 2 REQUIRED) +include(${SWIG_USE_FILE}) + +add_definitions(-DSWIG_TYPE_TABLE=obslua -DSWIG_LUA_INTERPRETER_NO_DEBUG) + +if(MSVC) + add_compile_options("/wd4054") + add_compile_options("/wd4197") + add_compile_options("/wd4244") + add_compile_options("/wd4267") +endif() + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +SWIG_ADD_MODULE(obslua lua obslua.i ../cstrcache.cpp ../cstrcache.h) +SWIG_LINK_LIBRARIES(obslua obs-scripting libobs ${LUA_LIBRARIES} ${EXTRA_LIBS}) + +function(install_plugin_bin_swig target additional_target) + if(APPLE) + set(_bit_suffix "") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_bit_suffix "64bit/") + else() + set(_bit_suffix "32bit/") + endif() + + set_target_properties(${additional_target} PROPERTIES + PREFIX "") + + add_custom_command(TARGET ${additional_target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + "${OBS_OUTPUT_DIR}/$/bin/${_bit_suffix}$" + VERBATIM) +endfunction() + +install_plugin_bin_swig(obs-scripting obslua) diff --git a/deps/obs-scripting/obslua/obslua.i b/deps/obs-scripting/obslua/obslua.i new file mode 100644 index 000000000..ba8b15037 --- /dev/null +++ b/deps/obs-scripting/obslua/obslua.i @@ -0,0 +1,99 @@ +%module obslua +%{ +#define SWIG_FILE_WITH_INIT +#define DEPRECATED_START +#define DEPRECATED_END +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cstrcache.h" +#include "obs-scripting-config.h" + +#if UI_ENABLED +#include "obs-frontend-api.h" +#endif + +%} + +#define DEPRECATED_START +#define DEPRECATED_END +#define EXPORT + +%rename(blog) wrap_blog; +%inline %{ +static inline void wrap_blog(int log_level, const char *message) +{ + blog(log_level, "%s", message); +} +%} + +%include "stdint.i" + +/* Used to free when using %newobject functions. E.G.: + * %newobject obs_module_get_config_path; */ +%typemap(newfree) char * "bfree($1);"; + +%ignore blog; +%ignore blogva; +%ignore bcrash; +%ignore obs_source_info; +%ignore obs_register_source_s(const struct obs_source_info *info, size_t size); +%ignore obs_output_set_video(obs_output_t *output, video_t *video); +%ignore obs_output_video(const obs_output_t *output); +%ignore obs_add_tick_callback; +%ignore obs_remove_tick_callback; +%ignore obs_add_main_render_callback; +%ignore obs_remove_main_render_callback; +%ignore obs_enum_sources; +%ignore obs_properties_add_button; +%ignore obs_property_set_modified_callback; +%ignore signal_handler_connect; +%ignore signal_handler_disconnect; +%ignore signal_handler_connect_global; +%ignore signal_handler_disconnect_global; +%ignore signal_handler_remove_current; +%ignore obs_hotkey_register_frontend; +%ignore obs_hotkey_register_encoder; +%ignore obs_hotkey_register_output; +%ignore obs_hotkey_register_service; +%ignore obs_hotkey_register_source; +%ignore obs_hotkey_pair_register_frontend; +%ignore obs_hotkey_pair_register_encoder; +%ignore obs_hotkey_pair_register_output; +%ignore obs_hotkey_pair_register_service; +%ignore obs_hotkey_pair_register_source; + +%include "graphics/graphics.h" +%include "graphics/vec4.h" +%include "graphics/vec3.h" +%include "graphics/vec2.h" +%include "graphics/quat.h" +%include "obs-data.h" +%include "obs-source.h" +%include "obs-properties.h" +%include "obs-interaction.h" +%include "obs-hotkey.h" +%include "obs.h" +%include "callback/calldata.h" +%include "callback/proc.h" +%include "callback/signal.h" +%include "util/bmem.h" +%include "util/base.h" +%include "obs-scripting-config.h" + +#if UI_ENABLED +%include "obs-frontend-api.h" +#endif diff --git a/deps/obs-scripting/obspython/CMakeLists.txt b/deps/obs-scripting/obspython/CMakeLists.txt new file mode 100644 index 000000000..263a4b95a --- /dev/null +++ b/deps/obs-scripting/obspython/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 2.8) +project(obspython) + +find_package(SWIG 2 REQUIRED) +include(${SWIG_USE_FILE}) + +add_definitions(-DSWIG_TYPE_TABLE=obspython -DMS_NO_COREDLL -DPy_ENABLE_SHARED=1 -DSWIG_PYTHON_INTERPRETER_NO_DEBUG) + +if(MSVC) + add_compile_options("/wd4054") + add_compile_options("/wd4100") + add_compile_options("/wd4115") + add_compile_options("/wd4197") + add_compile_options("/wd4701") +endif() + +include_directories(${PYTHON_INCLUDE_DIR}) +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +#add_definitions( -DSWIG_TYPE_TABLE=libobs ) +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-modern") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-builtin") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-modernargs") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-includeall") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-importall") +SET_SOURCE_FILES_PROPERTIES(obspython.i PROPERTIES SWIG_FLAGS "-py3") + +if(WIN32) + string(REGEX REPLACE "_d" "" PYTHON_LIBRARIES "${PYTHON_LIBRARIES}") +endif() + +SWIG_ADD_MODULE(obspython python obspython.i ../cstrcache.cpp ../cstrcache.h) +SWIG_LINK_LIBRARIES(obspython obs-scripting libobs ${PYTHON_LIBRARIES}) + +function(install_plugin_bin_swig target additional_target) + if(APPLE) + set(_bit_suffix "") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_bit_suffix "64bit/") + else() + set(_bit_suffix "32bit/") + endif() + + set_target_properties(${additional_target} PROPERTIES + PREFIX "") + + add_custom_command(TARGET ${additional_target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_CURRENT_BINARY_DIR}/obspython.py" + "${OBS_OUTPUT_DIR}/$/bin/${_bit_suffix}/obspython.py" + VERBATIM) + add_custom_command(TARGET ${additional_target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$" + "${OBS_OUTPUT_DIR}/$/bin/${_bit_suffix}$" + VERBATIM) +endfunction() + +install_plugin_bin_swig(obs-scripting _obspython) diff --git a/deps/obs-scripting/obspython/obspython.i b/deps/obs-scripting/obspython/obspython.i new file mode 100644 index 000000000..3c71413da --- /dev/null +++ b/deps/obs-scripting/obspython/obspython.i @@ -0,0 +1,106 @@ +%module(threads="1") obspython +%nothread; +%{ +#define SWIG_FILE_WITH_INIT +#define DEPRECATED_START +#define DEPRECATED_END +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "obs-scripting-config.h" + +#if UI_ENABLED +#include "obs-frontend-api.h" +#endif + +%} + +#define DEPRECATED_START +#define DEPRECATED_END +#define EXPORT + +%rename(blog) wrap_blog; +%inline %{ +static inline void wrap_blog(int log_level, const char *message) +{ + blog(log_level, "%s", message); +} +%} + +%include "stdint.i" + +/* Used to free when using %newobject functions. E.G.: + * %newobject obs_module_get_config_path; */ +%typemap(newfree) char * "bfree($1);"; + +%ignore blog; +%ignore blogva; +%ignore bcrash; +%ignore obs_source_info; +%ignore obs_register_source_s(const struct obs_source_info *info, size_t size); +%ignore obs_output_set_video(obs_output_t *output, video_t *video); +%ignore obs_output_video(const obs_output_t *output); +%ignore obs_add_tick_callback; +%ignore obs_remove_tick_callback; +%ignore obs_add_main_render_callback; +%ignore obs_remove_main_render_callback; +%ignore obs_enum_sources; +%ignore obs_properties_add_button; +%ignore obs_property_set_modified_callback; +%ignore signal_handler_connect; +%ignore signal_handler_disconnect; +%ignore signal_handler_connect_global; +%ignore signal_handler_disconnect_global; +%ignore signal_handler_remove_current; +%ignore obs_hotkey_register_frontend; +%ignore obs_hotkey_register_encoder; +%ignore obs_hotkey_register_output; +%ignore obs_hotkey_register_service; +%ignore obs_hotkey_register_source; +%ignore obs_hotkey_pair_register_frontend; +%ignore obs_hotkey_pair_register_encoder; +%ignore obs_hotkey_pair_register_output; +%ignore obs_hotkey_pair_register_service; +%ignore obs_hotkey_pair_register_source; + +%include "graphics/graphics.h" +%include "graphics/vec4.h" +%include "graphics/vec3.h" +%include "graphics/vec2.h" +%include "graphics/quat.h" +%include "obs-data.h" +%include "obs-source.h" +%include "obs-properties.h" +%include "obs-interaction.h" +%include "obs-hotkey.h" +%include "obs.h" +%include "callback/calldata.h" +%include "callback/proc.h" +%include "callback/signal.h" +%include "util/bmem.h" +%include "util/base.h" +%include "obs-scripting-config.h" + +#if UI_ENABLED +%include "obs-frontend-api.h" +#endif + +/* declare these manually because mutex + GIL = deadlocks */ +%thread; +void obs_enter_graphics(void); //Should only block on entering mutex +%nothread; +%include "obs.h"