diff --git a/inc/lektor/module/module_sdl2.h b/inc/lektor/module/module_sdl2.h new file mode 100644 index 0000000000000000000000000000000000000000..18cdee5c44f7d59e3b6418be5db25b2dda9cfa3c --- /dev/null +++ b/inc/lektor/module/module_sdl2.h @@ -0,0 +1,28 @@ +#pragma once + +#include <lektor/defines.h> +#include <stdbool.h> +#include <sqlite3.h> +#include <lektor/window.h> + +/* The only function with a setted filename */ +int module_set_function(void *mod, void *handle); + +/* Below there are needed functions for a window module. + * Names are not fixed but they follow a convention (to get a code easy + * to read). They should not be used in lektor! They will be loaded into + * the struct lkt_win by the module_set_function. + */ +bool module_sdl2_new(struct lkt_win *const win); +void module_sdl2_close(struct lkt_win *const win); +void module_sdl2_free(struct lkt_win *const win); + +bool module_sdl2_toggle_pause(struct lkt_win *const win, bool *new_paused); +bool module_sdl2_load_file(struct lkt_win *const win, const char *filepath); +bool module_sdl2_set_volume(struct lkt_win *const win, int vol); + +bool module_sdl2_is_paused(struct lkt_win *const win, bool *ret); +bool module_sdl2_get_duration(struct lkt_win *const win, int *dur_sec); +bool module_sdl2_get_elapsed(struct lkt_win *const win, int *elapsed_sec); + +bool module_sdl2_handle_events(struct lkt_win *const win, sqlite3 *db, enum mpd_idle_flag *mpd_idle_events); diff --git a/inc/lektor/module/mpv.h b/inc/lektor/module/mpv.h new file mode 100644 index 0000000000000000000000000000000000000000..1626d47f2d351652ce08f28a7cb0cb9a722eb3f0 --- /dev/null +++ b/inc/lektor/module/mpv.h @@ -0,0 +1,6 @@ +#pragma once + +#include <mpv/client.h> + +void lmpv_free(mpv_handle **ctx); +mpv_handle *lmpv_new(unsigned long int wid); diff --git a/meson.build b/meson.build index d2a9b13198dc8a6f0fe4b556dec2eaa97ccba990..09a39d454cb5c67fc4e263707cbf00ab8a945cb1 100644 --- a/meson.build +++ b/meson.build @@ -19,6 +19,7 @@ cc = meson.get_compiler('c') libdl = cc.find_library('dl') dep_x11 = dependency('x11', required : false) dep_mpv = dependency('mpv', required : false) +dep_sdl = dependency('sdl2', required : false) # Sources @@ -88,9 +89,19 @@ lkt = executable( 'lkt' # X11 window module if dep_x11.found() and dep_mpv.found() lib_mod_x11 = shared_library ( '_module_x11' - , files(['src/module/module_x11.c']) + , files(['src/module/module_x11.c', 'src/module/mpv.c']) , include_directories : includes , dependencies : [ dep_x11, dep_mpv ] , link_with : lib ) endif + +# SQL2 window module +if dep_sdl.found() and dep_mpv.found() + lib_mod_sdl = shared_library ( '_module_sdl2' + , files(['src/module/module_sdl2.c', 'src/module/mpv.c']) + , include_directories : includes + , dependencies : [ dep_sdl, dep_mpv ] + , link_with : lib + ) +endif diff --git a/src/module/module_sdl2.c b/src/module/module_sdl2.c new file mode 100644 index 0000000000000000000000000000000000000000..cdda96b2e4ba854ddb05aca03c1db1e7b504ed8a --- /dev/null +++ b/src/module/module_sdl2.c @@ -0,0 +1,447 @@ +#include <lektor/module/module_sdl2.h> +#include <lektor/module/mpv.h> +#include <lektor/macro.h> +#include <lektor/defines.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <SDL.h> +#include <mpv/render_gl.h> +#include <mpv/client.h> + +#define WIDTH 400 +#define HEIGHT 200 + +struct module_sdl2_window { + /* Related to SDL2 */ + SDL_Window *window; + SDL_GLContext glcontext; + + /* Mpv related */ + mpv_handle *mpv; + volatile int mpv_time_pos; // Don't write it in the database // + volatile int mpv_duration; // Because don't need to be persistent // +}; + +/* Private functions. */ + +static Uint32 wakeup_on_mpv_render_update, wakeup_on_mpv_events; + +static void +die(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + exit(1); +} + +static void * +get_proc_address_mpv(void *fn_ctx, const char *name) +{ + (void) fn_ctx; + return SDL_GL_GetProcAddress(name); +} + +static void +on_mpv_events(void *ctx) +{ + (void) ctx; + SDL_Event event = { .type = wakeup_on_mpv_events }; + SDL_PushEvent(&event); +} + +static void +on_mpv_render_update(void *ctx) +{ + (void) ctx; + SDL_Event event = { .type = wakeup_on_mpv_render_update }; + SDL_PushEvent(&event); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) + die("pass a single media file as argument"); + + mpv_handle *mpv = mpv_create(); + if (!mpv) + die("context init failed"); + + // Some minor options can only be set before mpv_initialize(). + if (mpv_initialize(mpv) < 0) + die("mpv init failed"); + + // Jesus Christ SDL, you suck! + SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "no"); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + die("SDL init failed"); + + SDL_Window *window = + SDL_CreateWindow("hi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 1000, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | + SDL_WINDOW_RESIZABLE); + if (!window) + die("failed to create SDL window"); + + SDL_GLContext glcontext = SDL_GL_CreateContext(window); + if (!glcontext) + die("failed to create SDL GL context"); + + 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, + } + }, + // Tell libmpv that you will call mpv_render_context_update() on render + // context update callbacks, and that you will _not_ block on the core + // ever (see <libmpv/render.h> "Threading" section for what libmpv + // functions you can call at all when this is active). + // In particular, this means you must call e.g. mpv_command_async() + // instead of mpv_command(). + // If you want to use synchronous calls, either make them on a separate + // thread, or remove the option below (this will disable features like + // DR and is not recommended anyway). + { + MPV_RENDER_PARAM_ADVANCED_CONTROL, &(int) + { + 1 + } + }, + {0} + }; + + // This makes mpv use the currently set GL context. It will use the callback + // (passed via params) to resolve GL builtin functions, as well as extensions. + mpv_render_context *mpv_gl; + if (mpv_render_context_create(&mpv_gl, mpv, params) < 0) + die("failed to initialize mpv GL context"); + + // We use events for thread-safe notification of the SDL main loop. + // Generally, the wakeup callbacks (set further below) should do as least + // work as possible, and merely wake up another thread to do actual work. + // On SDL, waking up the mainloop is the ideal course of action. SDL's + // SDL_PushEvent() is thread-safe, so we use that. + 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) + die("could not register events"); + + // When normal mpv events are available. + mpv_set_wakeup_callback(mpv, on_mpv_events, NULL); + + // When there is a need to call mpv_render_context_update(), which can + // request a new frame to be rendered. + // (Separate from the normal event handling mechanism for the sake of + // users which run OpenGL on a different thread.) + mpv_render_context_set_update_callback(mpv_gl, on_mpv_render_update, NULL); + + // Play this file. + const char *cmd[] = {"loadfile", argv[1], NULL}; + mpv_command_async(mpv, 0, cmd); + + while (1) { + SDL_Event event; + if (SDL_WaitEvent(&event) != 1) + die("event loop error"); + int redraw = 0; + switch (event.type) { + case SDL_QUIT: + goto done; + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_EXPOSED) + redraw = 1; + break; + case SDL_KEYDOWN: + if (event.key.keysym.sym == SDLK_SPACE) { + const char *cmd_pause[] = {"cycle", "pause", NULL}; + mpv_command_async(mpv, 0, cmd_pause); + } + if (event.key.keysym.sym == SDLK_s) { + // Also requires MPV_RENDER_PARAM_ADVANCED_CONTROL if you want + // screenshots to be rendered on GPU (like --vo=gpu would do). + const char *cmd_scr[] = {"screenshot-to-file", + "screenshot.png", + "window", + NULL + }; + printf("attempting to save screenshot to %s\n", cmd_scr[1]); + mpv_command_async(mpv, 0, cmd_scr); + } + break; + default: + // Happens when there is new work for the render thread (such as + // rendering a new video frame or redrawing it). + if (event.type == wakeup_on_mpv_render_update) { + uint64_t flags = mpv_render_context_update(mpv_gl); + if (flags & MPV_RENDER_UPDATE_FRAME) + redraw = 1; + } + // Happens when at least 1 new event is in the mpv event queue. + if (event.type == wakeup_on_mpv_events) { + // Handle all remaining mpv events. + while (1) { + mpv_event *mp_event = mpv_wait_event(mpv, 0); + if (mp_event->event_id == MPV_EVENT_NONE) + break; + printf("event: %s\n", mpv_event_name(mp_event->event_id)); + } + } + } + if (redraw) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + mpv_render_param params[] = { + // 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_OPENGL_FBO, &(mpv_opengl_fbo) + { + .fbo = 0, + .w = w, + .h = h, + } + }, + { + MPV_RENDER_PARAM_FLIP_Y, &(int) + { + 1 + } + }, + {0} + }; + mpv_render_context_render(mpv_gl, params); + SDL_GL_SwapWindow(window); + } + } +done: + + // Destroy the GL renderer and all of the GL objects it allocated. If video + // is still running, the video track will be deselected. + mpv_render_context_free(mpv_gl); + + mpv_detach_destroy(mpv); + + printf("properly terminated\n"); + return 0; +} + +/* Exported functions */ + +extern int +module_set_function(void *arg__, void *handle__) +{ + if (NULL == arg__ || handle__ == NULL) + return 1; + + struct lkt_win *win = (struct lkt_win *) arg__; + + win->new = module_sdl2_new; + win->close = module_sdl2_close; + win->free = module_sdl2_free; + win->toggle_pause = module_sdl2_toggle_pause; + win->load_file = module_sdl2_load_file; + win->set_volume = module_sdl2_set_volume; + win->is_paused = module_sdl2_is_paused; + win->get_duration = module_sdl2_get_duration; + win->get_elapsed = module_sdl2_get_elapsed; + win->handle_events = module_sdl2_handle_events; + win->handle = handle__; + + return 0; +} + +bool +module_sdl2_new(struct lkt_win *const win) +{ + if (win == NULL) + return false; + + struct module_sdl2_window *sdl2 = win->window; + + if (sdl2 == NULL) { + sdl2 = calloc(1, sizeof(struct module_sdl2_window)); + memset(sdl2, 0, sizeof(struct module_sdl2_window)); + + if (sdl2 == NULL) { + fprintf(stderr, " ! module_sdl2_new: failed to allocate window, no memory\n"); + return false; + } + + /* CREATE THE SDL2 WINDOW TODO */ + + fprintf(stderr, " . module_sdl2_new: successfully created the X11 window\n"); + } + + if (sdl2->mpv == NULL) { + sdl2->mpv = lmpv_new(0); + if (sdl2->mpv == NULL) + return false; + } + + win->window = sdl2; + return true; +} + +void +module_sdl2_close(struct lkt_win *const win) +{ + if (win == NULL || win->window == NULL) + return; + + struct module_sdl2_window *const sdl2 = win->window; + lmpv_free(&sdl2->mpv); +} + +void +module_sdl2_free(struct lkt_win *const win) +{ + module_sdl2_close(win); +} + +bool +module_sdl2_toggle_pause(struct lkt_win *const win, bool *new_paused) +{ + if (win == NULL || win->window == NULL) + return false; + + mpv_handle *ctx = ((struct module_sdl2_window *) win->window)->mpv; + + if (!ctx) { + fprintf(stderr, " ! module_sdl2_toggle_pause: failed due to missing mpv ctx\n"); + return false; + } + + const char *cmd[] = {"cycle", "pause", "up", NULL}; + int status; + + if ((status = mpv_command(ctx, cmd)) < 0) { + fprintf(stderr, "module_sdl2_toggle_pause: Failed issue command: %s\n", + mpv_error_string(status)); + return false; + } + + return module_sdl2_is_paused(win, new_paused); +} + +bool +module_sdl2_load_file(struct lkt_win *const win, const char *filepath) +{ + if (win == NULL || win->window == NULL) + return false; + + struct module_sdl2_window *sdl2 = win->window; + mpv_handle *ctx = sdl2->mpv; + + if (!ctx) { + fprintf(stderr, " ! module_sdl2_load_file: failed due to missing mpv ctx\n"); + return false; + } + + if (access(filepath, R_OK)) { + fprintf(stderr, " ! module_sdl2_load_file: Faild to read file: %s\n", filepath); + return false; + } + + const char *cmd[] = {"loadfile", filepath, "replace", NULL}; + int status; + + if ((status = mpv_command(ctx, cmd)) < 0) { + fprintf(stderr, " ! module_sdl2_load_file: Failed to add '%s': %s\n", + filepath, mpv_error_string(status)); + return false; + } + + sdl2->mpv_duration = 0; + sdl2->mpv_time_pos = 0; + + fprintf(stderr, " . module_x11_load_file: added file %s\n", filepath); + return true; +} + +bool +module_sdl2_set_volume(struct lkt_win *const win, int vol) +{ + if (win == NULL || win->window == NULL) + return false; + + mpv_handle *ctx = ((struct module_sdl2_window *) win->window)->mpv; + + if (!ctx) { + fprintf(stderr, " ! module_sdl2_set_volume: failed due to missing mpv ctx\n"); + return false; + } + + int status; + char str[5]; + memset(str, 0, 5); + snprintf(str, 4, "%d", vol); + const char *cmd[] = {"set", "ao-volume", str, NULL}; + + if ((status = mpv_command(ctx, cmd)) < 0) { + fprintf(stderr, " ! module_x11_set_volume: Failed to execute command: %s\n", + mpv_error_string(status)); + return false; + } + + return true; +} + +bool +module_sdl2_is_paused(struct lkt_win *const win, bool *ret) +{ + if (win == NULL || win->window == NULL) + return false; + + mpv_handle *ctx = ((struct module_sdl2_window *) win->window)->mpv; + int status; + + if (!ctx) { + fprintf(stderr, " ! module_sdl2_is_paused: failed due to missing mpv ctx\n"); + return false; + } + + if ((status = mpv_get_property(ctx, "pause", MPV_FORMAT_FLAG, &ret)) < 0) { + fprintf(stderr, "module_sdl2_is_paused: Failed to get pause property: %s\n", + mpv_error_string(status)); + return false; + } + + return true; +} + +bool +module_sdl2_get_duration(struct lkt_win *const win, int *dur_sec) +{ + if (win == NULL || win->window == NULL) + return false; + + struct module_sdl2_window *sdl2 = win->window; + *dur_sec = sdl2->mpv_duration; + + return true; +} + +bool +module_sdl2_get_elapsed(struct lkt_win *const win, int *elapsed_sec) +{ + if (win == NULL || win->window == NULL) + return false; + + struct module_sdl2_window *sdl2 = win->window; + *elapsed_sec = sdl2->mpv_time_pos; + + return true; +} + +bool +module_sdl2_handle_events(struct lkt_win *const win, sqlite3 *db, enum mpd_idle_flag *mpd_idle_events) +{ +} diff --git a/src/module/module_x11.c b/src/module/module_x11.c index 49a2e8132527b9ea154975bfc6fecbd86dcc34b7..a7885660af5606bd897432bcc8906b4c8e09a16e 100644 --- a/src/module/module_x11.c +++ b/src/module/module_x11.c @@ -1,4 +1,5 @@ #include <lektor/module/module_x11.h> +#include <lektor/module/mpv.h> #include <lektor/database.h> #include <lektor/commands.h> @@ -211,21 +212,6 @@ lx11_handle(struct module_x11_window *win) * */ -static inline void -lmpv_free(mpv_handle **ctx) -{ - if (!*ctx) { - fprintf(stderr, " ! lmpv_free: failed due to missing mpv ctx\n"); - return; - } - - static const char *cmd[] = {"quit", NULL}; - mpv_command(*ctx, cmd); - mpv_destroy(*ctx); - *ctx = NULL; - fprintf(stderr, " * lmpv_free: mpv context destroyed\n"); -} - static inline bool lmpv_handle(struct lkt_win *win, sqlite3 *db, enum mpd_idle_flag *mpd_idle_events) { @@ -301,90 +287,6 @@ end: return sta; } -static mpv_handle * -lmpv_new(unsigned long int wid) -{ - mpv_handle *ctx = mpv_create(); - int status; - - if (!ctx) { - fprintf(stderr, " ! lmpv_new: failed creating context\n"); - return NULL; - } - - if ((status = mpv_set_option_string(ctx, "input-default-bindings", "yes")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set input-default-bindings: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_option_string(ctx, "input-vo-keyboard", "yes")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set input-vo-keyboard: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_option_string(ctx, "replaygain", "track")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set replaygain: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_option_string(ctx, "gpu-context", "x11")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set gpu-context: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_property(ctx, "wid", MPV_FORMAT_INT64, &wid)) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set wid: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_option_string(ctx, "demuxer-max-bytes", "10M")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set demuxer-max-bytes: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_option_string(ctx, "profile", "low-latency")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set demuxer-max-bytes: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_set_property_string(ctx, "hwdec", "yes")) < 0) { - fprintf(stderr, " ! lmpv_new: failed to set hwdec to no: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_initialize(ctx)) < 0) { - fprintf(stderr, " ! lmpv_new: failed to initialize mpv: %s\n", - mpv_error_string(status)); - return NULL; - } - - if ((status = mpv_observe_property(ctx, 0, "ao-volume", MPV_FORMAT_INT64)) < 0) - goto error; - - if ((status = mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_INT64)) < 0) - goto error; - - if ((status = mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_INT64)) < 0) - goto error; - - if ((status = mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG)) < 0) - goto error; - - fprintf(stderr, " * lmpv_new: successfully created a mpv context\n"); - return ctx; -error: - lmpv_free(&ctx); - return NULL; -} - /* * * NOW: THE SETTED FUNCTIONS FOR THE WINDOW MODULE :NOW diff --git a/src/module/mpv.c b/src/module/mpv.c new file mode 100644 index 0000000000000000000000000000000000000000..b81c1c47433a564cc5013a1d5760a0746dc89635 --- /dev/null +++ b/src/module/mpv.c @@ -0,0 +1,112 @@ +#include <lektor/module/mpv.h> +#include <stdio.h> + +void +lmpv_free(mpv_handle **ctx) +{ + if (!*ctx) { + fprintf(stderr, " ! lmpv_free: failed due to missing mpv ctx\n"); + return; + } + + static const char *cmd[] = {"quit", NULL}; + mpv_command(*ctx, cmd); + mpv_destroy(*ctx); + *ctx = NULL; + fprintf(stderr, " * lmpv_free: mpv context destroyed\n"); +} + +void * +lmpv_prepare(void) +{ + mpv_handle *ctx = mpv_create(); + int status; + + if (!ctx) { + fprintf(stderr, " ! lmpv_new: failed creating context\n"); + return NULL; + } + + if ((status = mpv_set_option_string(ctx, "input-default-bindings", "yes")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set input-default-bindings: %s\n", + mpv_error_string(status)); + return NULL; + } + + if ((status = mpv_set_option_string(ctx, "input-vo-keyboard", "yes")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set input-vo-keyboard: %s\n", + mpv_error_string(status)); + return NULL; + } + + if ((status = mpv_set_option_string(ctx, "replaygain", "track")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set replaygain: %s\n", + mpv_error_string(status)); + return NULL; + } + + if ((status = mpv_set_option_string(ctx, "gpu-context", "x11")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set gpu-context: %s\n", + mpv_error_string(status)); + return NULL; + } + + if ((status = mpv_set_option_string(ctx, "demuxer-max-bytes", "10M")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set demuxer-max-bytes: %s\n", + mpv_error_string(status)); + return NULL; + } + + if ((status = mpv_set_option_string(ctx, "profile", "low-latency")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set demuxer-max-bytes: %s\n", + mpv_error_string(status)); + return NULL; + } + + if ((status = mpv_set_property_string(ctx, "hwdec", "yes")) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set hwdec to no: %s\n", + mpv_error_string(status)); + return NULL; + } + + return ctx; +} + +int +lmpv_observe_properties(mpv_handle *ctx) +{ + return (mpv_observe_property(ctx, 0, "ao-volume", MPV_FORMAT_INT64) < 0) && + (mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_INT64) < 0) && + (mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_INT64) < 0) && + (mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG) < 0); +} + +mpv_handle * +lmpv_new(unsigned long int wid) +{ + mpv_handle *ctx = lmpv_prepare(); + int status; + + if ((status = mpv_set_property(ctx, "wid", MPV_FORMAT_INT64, &wid)) < 0) { + fprintf(stderr, " ! lmpv_new: failed to set wid: %s\n", + mpv_error_string(status)); + goto error; + } + + if ((status = mpv_initialize(ctx)) < 0) { + fprintf(stderr, " ! lmpv_new: failed to initialize mpv: %s\n", + mpv_error_string(status)); + goto error; + } + + if (!lmpv_observe_properties(ctx)) { + fprintf(stderr, " * lmpv_new: failed to observe properties\n"); + goto error; + } + + fprintf(stderr, " * lmpv_new: successfully created a mpv context\n"); + return ctx; +error: + lmpv_free(&ctx); + return NULL; +}