451 lines · 13.6 KB · C
RawDownload
/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv.  If not, see <http://www.gnu.org/licenses/>. */ #include <string.h>#include <sys/types.h>#include <math.h>#include <assert.h> #include "config.h" #include "osdep/io.h"#include "osdep/subprocess.h"#include "osdep/threads.h" #include "common/common.h"#include "common/msg.h"#include "input/input.h"#include "options/m_config.h"#include "options/parse_configfile.h"#include "options/path.h"#include "misc/bstr.h"#include "core.h"#include "client.h"#include "libmpv/client.h"#include "libmpv/render.h"#include "libmpv/stream_cb.h" extern const struct mp_scripting mp_scripting_lua;extern const struct mp_scripting mp_scripting_cplugin;extern const struct mp_scripting mp_scripting_js;extern const struct mp_scripting mp_scripting_run; static const struct mp_scripting *const scripting_backends[] = {#if HAVE_LUA    &mp_scripting_lua,#endif#if HAVE_CPLUGINS    &mp_scripting_cplugin,#endif#if HAVE_JAVASCRIPT    &mp_scripting_js,#endif    &mp_scripting_run,    NULL}; static char *script_name_from_filename(void *talloc_ctx, const char *fname){    fname = mp_basename(fname);    if (fname[0] == '@')        fname += 1;    char *name = talloc_strdup(talloc_ctx, fname);    // Drop file extension    char *dot = strrchr(name, '.');    if (dot)        *dot = '\0';    // Turn it into a safe identifier - this is used with e.g. dispatching    // input via: "send scriptname ..."    for (int n = 0; name[n]; n++) {        char c = name[n];        if (!(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z') &&            !(c >= '0' && c <= '9'))            name[n] = '_';    }    return talloc_asprintf(talloc_ctx, "%s", name);} static void run_script(struct mp_script_args *arg){    char *name = talloc_asprintf(NULL, "%s/%s", arg->backend->name,                                 mpv_client_name(arg->client));    mp_thread_set_name(name);    talloc_free(name);     if (arg->backend->load(arg) < 0)        MP_ERR(arg, "Could not load %s script %s\n", arg->backend->name, arg->filename);     mpv_destroy(arg->client);    talloc_free(arg);} static MP_THREAD_VOID script_thread(void *p){    struct mp_script_args *arg = p;    run_script(arg);     MP_THREAD_RETURN();} static int64_t mp_load_script(struct MPContext *mpctx, const char *fname){    char *ext = mp_splitext(fname, NULL);    if (ext && strcasecmp(ext, "disable") == 0)        return 0;     void *tmp = talloc_new(NULL);     const char *path = NULL;    char *script_name = NULL;    const struct mp_scripting *backend = NULL;     struct stat s;    if (!stat(fname, &s) && S_ISDIR(s.st_mode)) {        path = fname;        fname = NULL;         for (int n = 0; scripting_backends[n]; n++) {            const struct mp_scripting *b = scripting_backends[n];            char *filename = mp_tprintf(80, "main.%s", b->file_ext);            fname = mp_path_join(tmp, path, filename);            if (!stat(fname, &s) && S_ISREG(s.st_mode)) {                backend = b;                break;            }            talloc_free((void *)fname);            fname = NULL;        }         if (!fname) {            MP_ERR(mpctx, "Cannot find main.* for any supported scripting "                   "backend in: %s\n", path);            talloc_free(tmp);            return -1;        }         script_name = talloc_strdup(tmp, path);        mp_path_strip_trailing_separator(script_name);        script_name = mp_basename(script_name);    } else {        for (int n = 0; scripting_backends[n]; n++) {            const struct mp_scripting *b = scripting_backends[n];            if (ext && strcasecmp(ext, b->file_ext) == 0) {                backend = b;                break;            }        }        script_name = script_name_from_filename(tmp, fname);    }     if (!backend) {        MP_ERR(mpctx, "Can't load unknown script: %s\n", fname);        talloc_free(tmp);        return -1;    }     struct mp_script_args *arg = talloc_ptrtype(NULL, arg);    *arg = (struct mp_script_args){        .mpctx = mpctx,        .filename = talloc_strdup(arg, fname),        .path = talloc_strdup(arg, path),        .backend = backend,        // Create the client before creating the thread; otherwise a race        // condition could happen, where MPContext is destroyed while the        // thread tries to create the client.        .client = mp_new_client(mpctx->clients, script_name),    };     talloc_free(tmp);    fname = NULL; // might have been freed so don't touch anymore     if (!arg->client) {        MP_ERR(mpctx, "Failed to create client for script: %s\n", arg->filename);        talloc_free(arg);        return -1;    }     mp_client_set_weak(arg->client);    arg->log = mp_client_get_log(arg->client);    int64_t id = mpv_client_id(arg->client);     MP_DBG(arg, "Loading %s script %s...\n", backend->name, arg->filename);     if (backend->no_thread) {        run_script(arg);    } else {        mp_thread thread;        if (mp_thread_create(&thread, script_thread, arg)) {            mpv_destroy(arg->client);            talloc_free(arg);            return -1;        }        mp_thread_detach(thread);    }     return id;} int64_t mp_load_user_script(struct MPContext *mpctx, const char *fname){    char *path = mp_get_user_path(NULL, mpctx->global, fname);    int64_t ret = mp_load_script(mpctx, path);    talloc_free(path);    return ret;} static int compare_filename(const void *pa, const void *pb){    char *a = (char *)pa;    char *b = (char *)pb;    return strcmp(a, b);} static char **list_script_files(void *talloc_ctx, char *path){    char **files = NULL;    int count = 0;    DIR *dp = opendir(path);    if (!dp)        return NULL;    struct dirent *ep;    while ((ep = readdir(dp))) {        if (ep->d_name[0] != '.') {            char *fname = mp_path_join(talloc_ctx, path, ep->d_name);            struct stat s;            if (!stat(fname, &s) && (S_ISREG(s.st_mode) || S_ISDIR(s.st_mode)))                MP_TARRAY_APPEND(talloc_ctx, files, count, fname);        }    }    closedir(dp);    if (files)        qsort(files, count, sizeof(char *), compare_filename);    MP_TARRAY_APPEND(talloc_ctx, files, count, NULL);    return files;} static void load_builtin_script(struct MPContext *mpctx, int slot, bool enable,                                const char *fname){    assert(slot < MP_ARRAY_SIZE(mpctx->builtin_script_ids));    int64_t *pid = &mpctx->builtin_script_ids[slot];    if (*pid > 0 && !mp_client_id_exists(mpctx, *pid))        *pid = 0; // died    if ((*pid > 0) != enable) {        if (enable) {            *pid = mp_load_script(mpctx, fname);        } else {            char *name = mp_tprintf(22, "@%"PRIi64, *pid);            mp_client_send_event(mpctx, name, 0, MPV_EVENT_SHUTDOWN, NULL);        }    }} void mp_load_builtin_scripts(struct MPContext *mpctx){    load_builtin_script(mpctx, 0, mpctx->opts->lua_load_osc, "@osc.lua");    load_builtin_script(mpctx, 1, mpctx->opts->lua_load_ytdl, "@ytdl_hook.lua");    load_builtin_script(mpctx, 2, mpctx->opts->lua_load_stats, "@stats.lua");    load_builtin_script(mpctx, 3, mpctx->opts->lua_load_console, "@console.lua");    load_builtin_script(mpctx, 4, mpctx->opts->lua_load_auto_profiles,                        "@auto_profiles.lua");    load_builtin_script(mpctx, 5, mpctx->opts->lua_load_select, "@select.lua");} bool mp_load_scripts(struct MPContext *mpctx){    bool ok = true;     // Load scripts from options    char **files = mpctx->opts->script_files;    for (int n = 0; files && files[n]; n++) {        if (files[n][0])            ok &= mp_load_user_script(mpctx, files[n]) >= 0;    }    if (!mpctx->opts->auto_load_scripts)        return ok;     // Load all scripts    void *tmp = talloc_new(NULL);    char **scriptsdir = mp_find_all_config_files(tmp, mpctx->global, "scripts");    for (int i = 0; scriptsdir && scriptsdir[i]; i++) {        files = list_script_files(tmp, scriptsdir[i]);        for (int n = 0; files && files[n]; n++)            ok &= mp_load_script(mpctx, files[n]) >= 0;    }    talloc_free(tmp);     return ok;} #if HAVE_CPLUGINS #ifndef _WIN32#include <dlfcn.h>#endif #define MPV_DLOPEN_FN "mpv_open_cplugin"typedef int (*mpv_open_cplugin)(mpv_handle *handle); static void init_sym_table(struct mp_script_args *args, void *lib) {#define INIT_SYM(name)                                                         \    {                                                                          \        void **sym = (void **)dlsym(lib, "pfn_" #name);                        \        if (sym) {                                                             \            if (*sym && *sym != &name)                                         \                MP_ERR(args, "Overriding already set function " #name "\n");   \            *sym = &name;                                                      \        }                                                                      \    }     INIT_SYM(mpv_client_api_version);    INIT_SYM(mpv_error_string);    INIT_SYM(mpv_free);    INIT_SYM(mpv_client_name);    INIT_SYM(mpv_client_id);    INIT_SYM(mpv_create);    INIT_SYM(mpv_initialize);    INIT_SYM(mpv_destroy);    INIT_SYM(mpv_terminate_destroy);    INIT_SYM(mpv_create_client);    INIT_SYM(mpv_create_weak_client);    INIT_SYM(mpv_load_config_file);    INIT_SYM(mpv_get_time_us);    INIT_SYM(mpv_free_node_contents);    INIT_SYM(mpv_set_option);    INIT_SYM(mpv_set_option_string);    INIT_SYM(mpv_command);    INIT_SYM(mpv_command_node);    INIT_SYM(mpv_command_ret);    INIT_SYM(mpv_command_string);    INIT_SYM(mpv_command_async);    INIT_SYM(mpv_command_node_async);    INIT_SYM(mpv_abort_async_command);    INIT_SYM(mpv_set_property);    INIT_SYM(mpv_set_property_string);    INIT_SYM(mpv_del_property);    INIT_SYM(mpv_set_property_async);    INIT_SYM(mpv_get_property);    INIT_SYM(mpv_get_property_string);    INIT_SYM(mpv_get_property_osd_string);    INIT_SYM(mpv_get_property_async);    INIT_SYM(mpv_observe_property);    INIT_SYM(mpv_unobserve_property);    INIT_SYM(mpv_event_name);    INIT_SYM(mpv_event_to_node);    INIT_SYM(mpv_request_event);    INIT_SYM(mpv_request_log_messages);    INIT_SYM(mpv_wait_event);    INIT_SYM(mpv_wakeup);    INIT_SYM(mpv_set_wakeup_callback);    INIT_SYM(mpv_wait_async_requests);    INIT_SYM(mpv_hook_add);    INIT_SYM(mpv_hook_continue);    INIT_SYM(mpv_get_wakeup_pipe);     INIT_SYM(mpv_render_context_create);    INIT_SYM(mpv_render_context_set_parameter);    INIT_SYM(mpv_render_context_get_info);    INIT_SYM(mpv_render_context_set_update_callback);    INIT_SYM(mpv_render_context_update);    INIT_SYM(mpv_render_context_render);    INIT_SYM(mpv_render_context_report_swap);    INIT_SYM(mpv_render_context_free);     INIT_SYM(mpv_stream_cb_add_ro); #undef INIT_SYM} static int load_cplugin(struct mp_script_args *args){    void *lib = dlopen(args->filename, RTLD_NOW | RTLD_LOCAL);    if (!lib)        goto error;    // Note: once loaded, we never unload, as unloading the libraries linked to    //       the plugin can cause random serious problems.    mpv_open_cplugin sym = (mpv_open_cplugin)dlsym(lib, MPV_DLOPEN_FN);    if (!sym)        goto error;     init_sym_table(args, lib);     return sym(args->client) ? -1 : 0;error: ;    char *err = dlerror();    if (err)        MP_ERR(args, "C plugin error: '%s'\n", err);    return -1;} const struct mp_scripting mp_scripting_cplugin = {    .name = "cplugin",    #ifdef _WIN32    .file_ext = "dll",    #else    .file_ext = "so",    #endif    .load = load_cplugin,}; #endif static int load_run(struct mp_script_args *args){    // The arg->client object might die and with it args->log, so duplicate it.    args->log = mp_log_new(args, args->log, NULL);     int fds[2];    if (!mp_ipc_start_anon_client(args->mpctx->ipc_ctx, args->client, fds))        return -1;    args->client = NULL; // ownership lost     char *fdopt = fds[1] >= 0 ? mp_tprintf(80, "--mpv-ipc-fd=%d:%d", fds[0], fds[1])                              : mp_tprintf(80, "--mpv-ipc-fd=%d", fds[0]);     struct mp_subprocess_opts opts = {        .exe = (char *)args->filename,        .args = (char *[]){(char *)args->filename, fdopt, NULL},        .fds = {            // Keep terminal stuff            {.fd = 0, .src_fd = 0,},            {.fd = 1, .src_fd = 1,},            {.fd = 2, .src_fd = 2,},            // Just hope these don't step over each other (e.g. fds[1] could be            // below 4, if the std FDs are missing).            {.fd = fds[0], .src_fd = fds[0], },            {.fd = fds[1], .src_fd = fds[1], },        },        .num_fds = fds[1] >= 0 ? 5 : 4,        .detach = true,    };    struct mp_subprocess_result res;    mp_subprocess(args->log, &opts, &res);     // Closing these will (probably) make the client exit, if it really died.    close(fds[0]);    if (fds[1] >= 0)        close(fds[1]);     return res.error;} const struct mp_scripting mp_scripting_run = {    .name = "ipc",    .file_ext = "run",    .no_thread = true,    .load = load_run,};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t