Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • c24dc852da4a929f5c25ddeb5c19e71f158b76e9
  • master par défaut
  • script
  • new-devel
  • devel
  • timingView-edit
  • fix-mpv
7 résultats

VivyApplication.cc

Blame
  • module_sdl2.c 16,48 Kio
    #define _POSIX_C_SOURCE 200809L
    
    #define __LKT_MODULE_MAIN_SOURCE__
    #include <lektor/lktmodule.h>
    
    #include "mpv.h"
    #include "thread.h"
    
    #include <lektor/icon.xpm>
    
    #include <sqlite3.h>
    #include <sched.h>
    #include <string.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <stdarg.h>
    #include <SDL.h>
    #include <SDL_image.h>
    #include <mpv/render_gl.h>
    #include <mpv/client.h>
    
    #define WIDTH  400
    #define HEIGHT 200
    
    static volatile Uint32 wakeup_on_mpv_render_update, wakeup_on_mpv_events;
    
    struct module_sdl2_window {
        /* Related to SDL2 */
        volatile SDL_Window *window;
        volatile SDL_GLContext glcontext;
        int is_fullscreen;
    
        /* Mpv related */
        volatile mpv_handle *mpv;
        volatile mpv_render_context *mpv_gl;
        volatile int mpv_time_pos; /* Don't write it in the database       */
        volatile int mpv_duration; /* Because don't need to be persistent  */
        volatile int state;
        volatile int hinib;
        volatile int set_seek;
    
        /* Thread related */
        struct poller_thread self;
        volatile int launched; /* SDL you sucks */
    
        /* Things from the server */
        struct queue *queue;
        volatile sqlite3 *db;
    };
    
    /************************
     * Function definitions *
     ************************/
    
    static bool module_sdl2_get_elapsed(struct module_sdl2_window *, int *);
    static bool module_sdl2_get_duration(struct module_sdl2_window *, int *);
    static bool module_sdl2_set_paussed(struct module_sdl2_window *, int);
    static bool module_sdl2_set_volume(struct module_sdl2_window *, int);
    static bool module_sdl2_set_position(struct module_sdl2_window *, int);
    static bool module_sdl2_load_file(struct module_sdl2_window *, const char *);
    static bool module_sdl2_toggle_pause(struct module_sdl2_window *);
    static void module_sdl2_free(struct module_sdl2_window *);
    static void module_sdl2_close(struct module_sdl2_window *);
    static bool module_sdl2_new(struct module_sdl2_window **, struct queue *, volatile sqlite3 *);
    
    /*********************
     * Private functions *
     *********************/
    
    static inline void *
    get_proc_address_mpv(void UNUSED *fn_ctx, const char *name)
    {
        return SDL_GL_GetProcAddress(name);
    }
    
    static inline void
    on_mpv_events(void UNUSED *ctx)
    {
        SDL_Event event = { .type = wakeup_on_mpv_events };
        SDL_PushEvent(&event);
    }
    
    static inline void
    on_mpv_render_update(void UNUSED *ctx)
    {
        SDL_Event event = { .type = wakeup_on_mpv_render_update };
        SDL_PushEvent(&event);
    }
    
    static inline bool
    init_mpv__(mpv_handle **ctx, volatile sqlite3 *db)
    {
        *ctx = lmpv_prepare(db);
        int status;
        RETURN_IF((status = mpv_initialize(*ctx)) < 0, mpv_error_string(status), 1);
        RETURN_UNLESS(lmpv_observe_properties(*ctx), "Observe prop failed", 1);
        return 0;
    }
    
    static inline bool
    init_mpv_gl__(mpv_handle *mpv, mpv_render_context **mpv_gl, mpv_render_param *params)
    {
        int status;
        RETURN_IF((status = mpv_render_context_create(mpv_gl, mpv, params)) < 0, mpv_error_string(status), 1);
    
        wakeup_on_mpv_render_update = SDL_RegisterEvents(1);
        wakeup_on_mpv_events        = SDL_RegisterEvents(1);
        if (wakeup_on_mpv_render_update == (Uint32)-1 || wakeup_on_mpv_events == (Uint32)-1) {
            LOG_ERROR("WINDOW", "Failed to register event");
            return 1;
        }
    
        mpv_set_wakeup_callback(mpv, on_mpv_events, NULL);
        mpv_render_context_set_update_callback(*mpv_gl, on_mpv_render_update, NULL);
        return 0;
    }
    
    /********************************
     * va_list version of functions *
     ********************************/
    
    static int
    mod_new(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win                  = (struct module_sdl2_window **)va_arg(copy, void **);
        struct queue *queue  = va_arg(copy, struct queue *);
        volatile sqlite3 *db = va_arg(copy, volatile sqlite3 *);
        bool ret             = module_sdl2_new(win, queue, db);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_close(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win = (struct module_sdl2_window **)va_arg(copy, void **);
        module_sdl2_close(*win);
        va_end(copy);
        return 0;
    }
    
    static int
    mod_free(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win = (struct module_sdl2_window **)va_arg(copy, void **);
        module_sdl2_free(*win);
        va_end(copy);
        return 0;
    }
    
    static int
    mod_toggle_pause(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win      = (struct module_sdl2_window **)va_arg(copy, void **);
        bool ret = module_sdl2_toggle_pause(*win);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_load_file(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win              = (struct module_sdl2_window **)va_arg(copy, void **);
        const char *file = va_arg(copy, const char *);
        bool ret         = module_sdl2_load_file(*win, file);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_set_volume(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win        = (struct module_sdl2_window **)va_arg(copy, void **);
        int volume = va_arg(copy, int);
        bool ret   = module_sdl2_set_volume(*win, volume);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_set_paused(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win       = (struct module_sdl2_window **)va_arg(copy, void **);
        int state = va_arg(copy, int);
        bool ret  = module_sdl2_set_paussed(*win, state);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_set_position(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win         = (struct module_sdl2_window **)va_arg(copy, void **);
        int seconds = va_arg(copy, int);
        bool ret    = module_sdl2_set_position(*win, seconds);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_get_duration(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win           = (struct module_sdl2_window **)va_arg(copy, void **);
        int *duration = va_arg(copy, int *);
        bool ret      = module_sdl2_get_duration(*win, duration);
        va_end(copy);
        return !ret;
    }
    
    static int
    mod_get_elapsed(va_list *va)
    {
        va_list copy;
        struct module_sdl2_window **win;
        va_copy(copy, *va);
        win          = (struct module_sdl2_window **)va_arg(copy, void **);
        int *elapsed = va_arg(copy, int *);
        bool ret     = module_sdl2_get_elapsed(*win, elapsed);
        va_end(copy);
        return !ret;
    }
    
    /********************
     * The module stuff *
     ********************/
    
    REG_BEGIN(sdl2_reg)
    REG_ADD_NAMED("new", mod_new)
    REG_ADD_NAMED("free", mod_free)
    REG_ADD_NAMED("close", mod_close)
    REG_ADD_NAMED("toggle", mod_toggle_pause)
    REG_ADD_NAMED("load", mod_load_file)
    REG_ADD_NAMED("set_paused", mod_set_paused)
    REG_ADD_NAMED("set_position", mod_set_position)
    REG_ADD_NAMED("set_volume", mod_set_volume)
    REG_ADD_NAMED("get_duration", mod_get_duration)
    REG_ADD_NAMED("get_elapsed", mod_get_elapsed)
    REG_END()
    #if !defined(LKT_STATIC_MODULE)
    REG_EXPORT(sdl2_reg)
    #endif
    
    /***************************
     * Function implementation *
     ***************************/
    
    static void *
    sdl_thread__(struct poller_thread_arg *arg)
    {
        struct module_sdl2_window *sdl2 = arg->args;
        SDL_Event event;
        uint64_t flags;
        int w, h, redraw = 0;
        bool ctrl = false;
        const Uint8 *state;
        free(arg);
        struct lkt_module module = {
            .handle = NULL,
            .reg    = sdl2_reg,
            .data   = sdl2,
        };
    
        /* Init the SDL window
           Yeah, SDL you sucks */
    
        SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "no"); /* It seems that sdl sucks. */
        RETURN_IF(SDL_Init(SDL_INIT_VIDEO) < 0, "Failed to init SDL", NULL);
        RETURN_IF(IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG) < 0, "Failed to init", NULL)
    
        sdl2->window = SDL_CreateWindow("lektord", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT,
                                        SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
        RETURN_UNLESS(sdl2->window, "Failed to create the window", NULL);
        sdl2->glcontext = SDL_GL_CreateContext((SDL_Window *)sdl2->window);
        RETURN_UNLESS(sdl2->glcontext, "Failed to create the SDL context", NULL);
    
        /* Set window icon */
        SDL_Surface *window_icon = IMG_ReadXPMFromArray(icon);
        if (NULL == window_icon)
            LOG_ERROR("WINDOW", "Failed to load xpm icon: %s", IMG_GetError());
    
        else {
            SDL_SetWindowIcon((SDL_Window *)sdl2->window, window_icon);
            SDL_FreeSurface(window_icon);
        }
    
        /* Init mpv here */
    
        RETURN_IF(init_mpv__((mpv_handle **)&sdl2->mpv, sdl2->db), "Failed to init mpv", false);
    
        mpv_render_param params[] = { { MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL },
                                      { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS,
                                        &(mpv_opengl_init_params){
                                            .get_proc_address = get_proc_address_mpv,
                                        } },
                                      { MPV_RENDER_PARAM_ADVANCED_CONTROL, &(int){ 1 } },
                                      { 0 } };
    
        RETURN_IF(init_mpv_gl__((mpv_handle *)sdl2->mpv, (mpv_render_context **)&sdl2->mpv_gl, params),
                  "Failed to init mpv_gl", false);
    
        while (!sdl2->launched)
            sched_yield();
    
        LOG_INFO("WINDOW", "Started SDL thread");
    
    loop:
        SDL_WaitEvent(&event);
        if (sdl2->launched == 2)
            goto end;
    
        switch (event.type) {
        case SDL_QUIT:
            break;
    
        case SDL_WINDOWEVENT:
            if (event.window.event == SDL_WINDOWEVENT_EXPOSED) {
                lkt_queue_make_available(sdl2->queue, lkt_event_play);
                lkt_queue_make_available(sdl2->queue, lkt_event_prop);
                redraw = 1;
            }
            break;
    
        case SDL_KEYDOWN:
            if (event.key.keysym.sym == SDLK_LEFT && ctrl) {
                const char *cmd_seek[] = { "seek", "-5", "relative", NULL };
                mpv_command_async((mpv_handle *)sdl2->mpv, 0, cmd_seek);
            } else if (event.key.keysym.sym == SDLK_RIGHT && ctrl) {
                const char *cmd_seek[] = { "seek", "+5", "relative", NULL };
                mpv_command_async((mpv_handle *)sdl2->mpv, 0, cmd_seek);
            }
            break;
    
        case SDL_KEYUP:
            state = SDL_GetKeyboardState(NULL);
            ctrl  = state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL];
    
            if (ctrl && event.key.keysym.sym == SDLK_SPACE)
                lkt_queue_send(sdl2->queue, lkt_event_play_toggle, LKT_PLAY_TOGGLE);
    
            else if (event.key.keysym.sym == SDLK_n && ctrl)
                lkt_queue_send(sdl2->queue, lkt_event_play_next, NULL);
    
            else if (event.key.keysym.sym == SDLK_p && ctrl)
                lkt_queue_send(sdl2->queue, lkt_event_play_prev, NULL);
    
            else if ((event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_RETURN2 ||
                      event.key.keysym.sym == SDLK_KP_ENTER) &&
                     ctrl)
                lkt_queue_send(sdl2->queue, lkt_event_play_next, NULL);
    
            else if (event.key.keysym.sym == SDLK_F11) {
                if (sdl2->is_fullscreen)
                    SDL_SetWindowFullscreen((SDL_Window *)sdl2->window, 0);
                else
                    SDL_SetWindowFullscreen((SDL_Window *)sdl2->window, SDL_WINDOW_FULLSCREEN);
                /* May use SDL_WINDOW_FULLSCREEN_DESKTOP, need to check. */
                sdl2->is_fullscreen = 1 - sdl2->is_fullscreen;
            }
            break;
    
        case SDL_TEXTINPUT:
            break;
    
        default:
            if (event.type == wakeup_on_mpv_render_update) {
                flags = mpv_render_context_update((mpv_render_context *)sdl2->mpv_gl);
                if (flags & MPV_RENDER_UPDATE_FRAME)
                    redraw = 1;
            }
    
            if (event.type == wakeup_on_mpv_events) {
                lmpv_handle(&module, (mpv_handle *)sdl2->mpv, sdl2->queue, &sdl2->mpv_time_pos, &sdl2->mpv_duration,
                            &sdl2->state, &sdl2->hinib, &sdl2->set_seek);
            }
        }
    
        if (redraw) {
            SDL_GetWindowSize((SDL_Window *)sdl2->window, &w, &h);
            /* Specify the default framebuffer (0) as target. This will
               render onto the entire screen. If you want to show the video
               in a smaller rectangle or apply fancy transformations, you'll
               need to render into a separate FBO and draw it manually. */
            mpv_render_param params[] = { { MPV_RENDER_PARAM_OPENGL_FBO,
                                            &(mpv_opengl_fbo){
                                                .fbo = 0,
                                                .w   = w,
                                                .h   = h,
                                            } },
                                          { MPV_RENDER_PARAM_FLIP_Y, &(int){ 1 } },
                                          { 0 } };
            mpv_render_context_render((mpv_render_context *)sdl2->mpv_gl, params);
            SDL_GL_SwapWindow((SDL_Window *)sdl2->window);
            redraw = 0;
        }
    
        sched_yield();
        goto loop; /* A loop without indentation. */
    end:
        sdl2->launched = 0;
        return NULL;
    }
    
    static bool
    module_sdl2_new(struct module_sdl2_window **win, struct queue *queue, volatile sqlite3 *db)
    {
        RETURN_UNLESS(win, "Invalid arguments", false);
        struct poller_thread_arg *arg;
    
        if (*win == NULL) {
            *win = calloc(1, sizeof(struct module_sdl2_window));
            RETURN_UNLESS(*win, "Out of memory", false);
            memset(*win, 0, sizeof(struct module_sdl2_window));
    
            (*win)->queue    = queue;
            (*win)->db       = db;
            (*win)->set_seek = -1;
    
            /* Start the SDL thread */
            arg = safe_malloc(sizeof(struct poller_thread_arg));
            RETURN_UNLESS(arg, "Out of memory", false);
            arg->args = *win;
            RETURN_IF(poller_new(&(*win)->self, sdl_thread__, arg), "Failed to launch the SDL thread", false);
        } else
            LOG_DEBUG("WINDOW", "SDL window already created");
    
        /* Finish */
        SDL_SetWindowTitle((SDL_Window *)(*win)->window, "Lektord");
        SDL_DisableScreenSaver();
        (*win)->launched = 1;
        return true;
    }
    
    static void
    module_sdl2_close(struct module_sdl2_window *win)
    {
        RETURN_UNLESS(win && win->window, "Invalid arguments", NOTHING);
        win->mpv_time_pos = win->mpv_duration = 0;
        SET_STATE_FLAG(win->state, STOP);
        RETURN_UNLESS(win->mpv, "Missing mpv ctx", NOTHING);
        static const char *cmd[] = { "stop", NULL };
        mpv_command_async((mpv_handle *)win->mpv, 0, cmd);
        LOG_INFO("WINDOW", "Module closed");
        /* TODO: make the SDL window background be black. */
    }
    
    static void
    module_sdl2_free(struct module_sdl2_window *win)
    {
        RETURN_UNLESS(win, "Invalid arguments", NOTHING);
        module_sdl2_close(win);
        LOG_INFO("WINDOW", "Waiting for window thread");
        win->launched = 2;
        while (win->launched)
            sched_yield();
        poller_join(&win->self, NULL);
        LOG_INFO("WINDOW", "Module terminated");
    }
    
    static bool
    module_sdl2_toggle_pause(struct module_sdl2_window *win)
    {
        RETURN_UNLESS(win, "Invalid arguments", false);
        return !lmpv_toggle_pause((mpv_handle *)win->mpv);
    }
    
    static bool
    module_sdl2_load_file(struct module_sdl2_window *win, const char *filepath)
    {
        RETURN_UNLESS(win, "Invalid arguments", false);
        bool ret          = !lmpv_load_file((mpv_handle *)win->mpv, filepath);
        win->mpv_duration = 0;
        win->mpv_time_pos = 0;
        win->hinib        = true;
        LOG_DEBUG("WINDOW", "Hinib flag at %d", win->hinib);
        return ret;
    }
    
    static bool
    module_sdl2_set_paussed(struct module_sdl2_window *win, int paused)
    {
        RETURN_UNLESS(win && win->window, "Invalid arguments", false);
        if (((!paused) && win->state == STATE_PAUSE) || (win->state == STATE_PLAY && paused)) {
            return !lmpv_toggle_pause((struct mpv_handle *)win->mpv);
        } else {
            LOG_DEBUG("WINDOW", "No need to toggle paused state, already what was asked");
            return true;
        }
    }
    
    static bool
    module_sdl2_set_volume(struct module_sdl2_window *win, int vol)
    {
        RETURN_UNLESS(win && win->window, "Invalid arguments", false);
        return !lmpv_set_volume((mpv_handle *)win->mpv, vol);
    }
    
    static bool
    module_sdl2_set_position(struct module_sdl2_window *win, int sec)
    {
        RETURN_UNLESS(win && win->window, "Invalid arguments", false);
        win->set_seek = sec;
        return true;
    }
    
    static bool
    module_sdl2_get_duration(struct module_sdl2_window *win, int *dur_sec)
    {
        RETURN_UNLESS(win, "Invalid arguments", false);
        *dur_sec = win->mpv_duration;
        return true;
    }
    
    static bool
    module_sdl2_get_elapsed(struct module_sdl2_window *win, int *elapsed_sec)
    {
        RETURN_UNLESS(win, "Invalid arguments", false);
        *elapsed_sec = win->mpv_time_pos;
        return true;
    }