diff options
author | GhostNaN <GhostNaN@protonmail.com> | 2020-11-23 14:24:18 -0500 |
---|---|---|
committer | GhostNaN <GhostNaN@protonmail.com> | 2020-11-23 14:24:18 -0500 |
commit | 2905b10655aafb0ed0341b38b92f692be3907aae (patch) | |
tree | 074a7018ad236341d6ed29ad4fcad2128f35b6e2 | |
parent | 00e08d4de44c9ac265d617bb72fe126c9dab145e (diff) |
Added resource saving options
This commit adds 2 new options "--auto-pause/-p" and "--auto-stop/-s"
- By using a Wayland feature called "surface frame callback"
mpvpaper can detect when the wallpaper is not visible.
- With this feature mpvpaper can pause/stop automagically
while the wallpaper can't be seen.
The pause option will literally just pause mpv.
- In order to save processing power more seamlessly.
While the stop option will sort of "suspend" mpvpaper
to a separate holder program call "mpvpaper-holder"
- This reduces not just processing power, but also RAM usage
at the cost of being at bit more obvious when it does stop.
Along with the automagical ways to pause/stop.
There also now a "pauselist" and "stoplist".
Which will pause/stop if a program is found running in it's list.
- To use, simply create these text based lists in "~/.config/mpvpaper/"
with the file name "pauselist" and/or "stoplist" respectively.
- Then add programs that can be found with the "pidof" command.
- Make sure the programs are separated by either spaces or newlines.
Like so: "wine firefox steam"
This commit also includes some minor fixes to many too name.
Such as converting some ints to bools
and changing the order of loading configs.
-rw-r--r-- | meson.build | 8 | ||||
-rw-r--r-- | src/holder.c | 407 | ||||
-rw-r--r-- | src/main.c | 408 |
3 files changed, 773 insertions, 50 deletions
diff --git a/meson.build b/meson.build index ee66845..37ffbdb 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,7 @@ wl_client=dependency('wayland-client') wl_egl=dependency('wayland-egl') egl=dependency('egl') mpv=dependency('mpv') +threads=dependency('threads') scanner=find_program('wayland-scanner') scanner_private_code=generator(scanner,output: '@BASENAME@-protocol.c',arguments: ['private-code','@INPUT@','@OUTPUT@']) @@ -28,4 +29,9 @@ protocols_dep=declare_dependency(link_with: lib_protocols,sources: protocols_hea executable(meson.project_name(), ['src/main.c', 'src/glad.c', 'src/cflogprinter.c'], include_directories : ['inc'], -dependencies: [dl_dep, wl_client, wl_egl, egl, mpv, protocols_dep], install: true) +dependencies: [dl_dep, wl_client, wl_egl, egl, mpv, threads, protocols_dep], install: true) + +shm_dep = cc.find_library('rt', required : false) +executable(meson.project_name() + '-holder', ['src/holder.c'], +include_directories : ['inc'], +dependencies: [dl_dep, wl_client, shm_dep, protocols_dep], install: true) diff --git a/src/holder.c b/src/holder.c new file mode 100644 index 0000000..09d425e --- /dev/null +++ b/src/holder.c @@ -0,0 +1,407 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <unistd.h> +#include <time.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <stdbool.h> + +#include <wayland-client.h> +#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" + +struct wl_state { + struct wl_display *display; + struct wl_compositor *compositor; + struct wl_shm *shm; + struct zwlr_layer_shell_v1 *layer_shell; + struct zxdg_output_manager_v1 *xdg_output_manager; + struct wl_list outputs; // struct display_output::link + char* monitor; // User selected output + int run_display; +}; + +struct display_output { + uint32_t wl_name; + struct wl_output *wl_output; + struct zxdg_output_v1 *xdg_output; + char *name; + + struct wl_state *state; + struct wl_buffer *buffer; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + + uint32_t width, height; + + struct wl_list link; +}; + +static struct { + char **argv_copy; + char **stoplist; + + bool auto_stop; + + int start_time; +} halt_info = {NULL, NULL, 0, 0}; + +static void nop() {} + +static void revive_mpvpaper() { + // Get the "real" cwd + char exe_dir[1024]; + int cut_point = readlink("/proc/self/exe", exe_dir, sizeof(exe_dir)); + for(uint i=cut_point; i > 1; i--) { + if (exe_dir[i] == '/') { + exe_dir[i+1] = '\0'; + break; + } + } + + execv(strcat(exe_dir, "mpvpaper"), halt_info.argv_copy); +} + +static void check_stoplist() { + for (uint i=0; halt_info.stoplist[i] != NULL; i++) { + char pid_name[512] = {0}; + strcpy(pid_name, "pidof "); + strcat(pid_name, halt_info.stoplist[i]); + strcat(pid_name, " > /dev/null"); + + while (!system(pid_name)) + usleep(100000); // 0.1 sec + } + if (!halt_info.auto_stop) + revive_mpvpaper(); +} + +static struct wl_buffer *create_dummy_buffer(struct display_output *output) { + const int WIDTH = 1, HEIGHT = 1; + + int stride = WIDTH * 4; // 4 bytes per pixel + int size = stride * HEIGHT; + + // Create shm + char name[] = "/wl_shm-dummy"; + shm_unlink(name); + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + shm_unlink(name); + ftruncate(fd, size); + + struct wl_shm_pool *pool = wl_shm_create_pool(output->state->shm, fd, size); + struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, WIDTH, HEIGHT, stride, WL_SHM_FORMAT_XRGB8888); + + wl_shm_pool_destroy(pool); + close(fd); + return buffer; +} + +const static struct wl_callback_listener wl_surface_frame_listener; + +static void create_surface_frame(struct display_output *output) { + output->buffer = create_dummy_buffer(output); + // Callback new frame + struct wl_callback *callback = wl_surface_frame(output->surface); + wl_callback_add_listener(callback, &wl_surface_frame_listener, output); + wl_surface_attach(output->surface, output->buffer, 0, 0); + wl_surface_damage(output->surface, 0, 0, output->width, output->height); + wl_surface_commit(output->surface); +} + +static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t time) { + wl_callback_destroy(callback); + + if (halt_info.stoplist) { + check_stoplist(); // If checking stoplist took longer than a sec + if (time - halt_info.start_time < 1000) + revive_mpvpaper(); + } + else + revive_mpvpaper(); + + halt_info.start_time = time; + create_surface_frame(data); +} + +const static struct wl_callback_listener wl_surface_frame_listener = { + .done = frame_handle_done, +}; + +static void destroy_display_output(struct display_output *output) { + if (!output) { + return; + } + wl_list_remove(&output->link); + if (output->layer_surface != NULL) { + zwlr_layer_surface_v1_destroy(output->layer_surface); + } + if (output->surface != NULL) { + wl_surface_destroy(output->surface); + } + zxdg_output_v1_destroy(output->xdg_output); + wl_output_destroy(output->wl_output); + + free(output->name); + free(output); +} + +static void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + struct display_output *output = data; + output->width = width; + output->height = height; + zwlr_layer_surface_v1_ack_configure(surface, serial); + + if (halt_info.stoplist) + check_stoplist(); + if (halt_info.auto_stop) + create_surface_frame(output); +} + +static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { + (void) surface; + destroy_display_output(data); +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void create_layer_surface(struct display_output *output) { + output->surface = wl_compositor_create_surface(output->state->compositor); + + // Empty input region + struct wl_region *input_region = wl_compositor_create_region(output->state->compositor); + wl_surface_set_input_region(output->surface, input_region); + wl_region_destroy(input_region); + + output->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + output->state->layer_shell, output->surface, output->wl_output, + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "mpvpaper"); + + zwlr_layer_surface_v1_set_size(output->layer_surface, 0, 0); + zwlr_layer_surface_v1_set_anchor(output->layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); + zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); + zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output); + wl_surface_commit(output->surface); + +} + +static void xdg_output_handle_name(void *data, + struct zxdg_output_v1 *xdg_output, const char *name) { + (void) xdg_output; + + struct display_output *output = data; + output->name = strdup(name); +} + +static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { + (void) xdg_output; + + struct display_output *output = data; + + if (strcmp(output->name, output->state->monitor) == 0 && !output->layer_surface) { + create_layer_surface(output); + } + else { + destroy_display_output(output); + } +} + +static const struct zxdg_output_v1_listener xdg_output_listener = { + .logical_position = nop, + .logical_size = nop, + .name = xdg_output_handle_name, + .description = nop, + .done = xdg_output_handle_done, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + (void) version; + + struct wl_state *state = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } + else if (strcmp(interface, wl_shm_interface.name) == 0) { + state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } + else if (strcmp(interface, wl_output_interface.name) == 0) { + struct display_output *output = calloc(1, sizeof(struct display_output)); + output->state = state; + output->wl_name = name; + output->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 3); + + wl_list_insert(&state->outputs, &output->link); + + if (state->run_display) { + output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + state->xdg_output_manager, output->wl_output); + zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); + } + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + state->layer_shell = wl_registry_bind(registry, name, + &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + state->xdg_output_manager = wl_registry_bind(registry, name, + &zxdg_output_manager_v1_interface, 2); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { + (void) registry; + + struct wl_state *state = data; + struct display_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &state->outputs, link) { + if (output->wl_name == name) { + destroy_display_output(output); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +static void set_stop_list() { + + char *stop_path = calloc(strlen(getenv("HOME"))+1 + strlen("/.config/mpvpaper/stoplist")+1, sizeof(char)); + strcpy(stop_path, getenv("HOME")); + strcat(stop_path, "/.config/mpvpaper/stoplist"); + + FILE *file = fopen(stop_path, "r"); + if (file) { + // Get alloc size + fseek(file, 0L, SEEK_END); + halt_info.stoplist = calloc(ftell(file)+1, sizeof(char)); + rewind(file); + + // Read lines + char app[512]; + for (uint i=0; fscanf(file, "%s", app) == 1; i++) { + halt_info.stoplist[i] = strdup(app); + } + + free(stop_path); + fclose(file); + } +} + +static void parse_command_line(int argc, char **argv, struct wl_state *state) { + + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, NULL, 'v'}, + {"fork", no_argument, NULL, 'f'}, + {"auto-pause", no_argument, NULL, 'p'}, + {"auto-stop", no_argument, NULL, 's'}, + {"layer", required_argument, NULL, 'l'}, + {"mpv-options", required_argument, NULL, 'o'}, + {0, 0, 0, 0} + }; + + const char *usage = + "Usage: mpvpaper-holder <mpvpaper options>\n" + "Descrition:\n" + "mpvpaper-holder acts as a lean gate keeper before mpvpaper can run\n" + "\n" + "It's sole purpose is to check if there is:\n" + "Any program is that running from the stoplist file\n" + "- Set in \"~/.config/mpvpaper/stoplist\"\n" + "If the wallpaper needs to be seen when drawn\n" + "- Set with \"-s\" or \"--auto-stop\" mpvpaper option\n"; + + if(argc > 2) { + char opt; + while((opt = getopt_long(argc, argv, "hvfpsl:o:Z:", long_options, NULL)) != -1) { + + switch (opt) { + case 'h': + fprintf(stdout, "%s", usage); + exit(EXIT_SUCCESS); + case 's': + halt_info.auto_stop = 1; + break; + } + } + if(optind + 1 >= argc) { + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + state->monitor = strdup(argv[optind]); + } + else { + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char **argv) { + struct wl_state state = {0}; + wl_list_init(&state.outputs); + + parse_command_line(argc, argv, &state); + set_stop_list(); + + // Copy argv + int argv_alloc_size = 0; + for(int i=0; argv[i] != NULL; i++) { + argv_alloc_size += strlen(argv[i])+1; + } + halt_info.argv_copy = calloc(argv_alloc_size+1, sizeof(char)); + for(int i=0; i < argc; i++) { + halt_info.argv_copy[i] = strdup(argv[i]); + } + + state.display = wl_display_connect(NULL); + if (!state.display) { + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(state.display); + wl_registry_add_listener(registry, ®istry_listener, &state); + wl_display_roundtrip(state.display); + if (state.compositor == NULL || state.layer_shell == NULL || + state.xdg_output_manager == NULL) { + + return EXIT_FAILURE; + } + + struct display_output *output; + wl_list_for_each(output, &state.outputs, link) { + output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + state.xdg_output_manager, output->wl_output); + zxdg_output_v1_add_listener(output->xdg_output, + &xdg_output_listener, output); + } + + // Check outputs + wl_display_roundtrip(state.display); + if (wl_list_empty(&state.outputs)) { + return EXIT_FAILURE; + } + + state.run_display = 1; + while (wl_display_dispatch(state.display) != -1) { + // NOP + } + + struct display_output *tmp_output; + wl_list_for_each_safe(output, tmp_output, &state.outputs, link) { + destroy_display_output(output); + } + + return EXIT_SUCCESS; +} @@ -3,6 +3,10 @@ #include <string.h> #include <getopt.h> #include <unistd.h> +#include <pthread.h> +#include <time.h> +#include <stdbool.h> +#include <signal.h> #include <wayland-client.h> #include <wayland-egl.h> @@ -26,7 +30,7 @@ struct wl_state { struct wl_list outputs; // struct display_output::link char* monitor; // User selected output int surface_layer; - int run_display; + bool run_display; }; struct display_output { @@ -50,22 +54,52 @@ static EGLDisplay *egl_display; static EGLContext *egl_context; static EGLSurface *egl_surface; -static mpv_handle* mpv; +static mpv_handle *mpv; static mpv_render_context *mpv_glcontext; -static char* video_path; +static char *video_path; -static int VERBOSE = 0; +static struct { + char **pauselist; + char **stoplist; + char **argv_copy; + char *save_info; + + bool auto_pause; + bool auto_stop; + + bool frame_ready; + +} halt_info = {NULL, NULL, NULL, NULL, 0, 0, 0}; + +static bool VERBOSE = 0; static void nop() {} +static void exit_mpvpaper(int reason) { + + if (mpv_glcontext) + mpv_render_context_free(mpv_glcontext); + if (mpv) + mpv_terminate_destroy(mpv); + + if (egl_surface) + eglDestroySurface(egl_display, egl_surface); + if (egl_context) + eglDestroyContext(egl_display, egl_context); + if (egl_window) + wl_egl_window_destroy(egl_window); + + exit(reason); +} + +static void handle_signal(int signum) { + (void) signum; + exit_mpvpaper(1); +} + const static struct wl_callback_listener wl_surface_frame_listener; static void render(struct display_output *output) { - mpv_event* event = mpv_wait_event(mpv, 0); - if (event->event_id == MPV_EVENT_SHUTDOWN || event->event_id == MPV_EVENT_IDLE) { - exit(EXIT_SUCCESS); - } - mpv_render_param render_params[] = { {MPV_RENDER_PARAM_OPENGL_FBO, &(mpv_opengl_fbo){ .fbo = 0, @@ -83,14 +117,23 @@ static void render(struct display_output *output) { wl_callback_add_listener(callback, &wl_surface_frame_listener, output); // Display frame - if (!eglSwapBuffers(egl_display, egl_surface)) { + if (!eglSwapBuffers(egl_display, egl_surface)) cflp_error("Failed to swap egl buffers 0x%X", eglGetError()); - } } static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t time) { (void) time; wl_callback_destroy(callback); + + // Reset deadman switch timer + halt_info.frame_ready = 1; + + // Shutdown when done + mpv_event* event = mpv_wait_event(mpv, 0); + if (event->event_id == MPV_EVENT_SHUTDOWN || event->event_id == MPV_EVENT_IDLE) + exit_mpvpaper(0); + + // Render next frame render(data); } @@ -98,6 +141,167 @@ const static struct wl_callback_listener wl_surface_frame_listener = { .done = frame_handle_done, }; +static void stop_mpvpaper() { + + // Save video position to arg -Z + const char* time_pos = mpv_get_property_string(mpv, "time-pos"); + const char* playlist_pos = mpv_get_property_string(mpv, "playlist-pos"); + + char save_info[30]; + sprintf(save_info, "%s %s", time_pos, playlist_pos); + + int argv_alloc_size = strlen("-Z")+1 + strlen(save_info)+1; + for(uint i=0; halt_info.argv_copy[i] != NULL; i++) { + argv_alloc_size += strlen(halt_info.argv_copy[i])+1; + } + char **argv = calloc(argv_alloc_size+1, sizeof(char)); + + uint i = 0; + for(i=0; halt_info.argv_copy[i] != NULL; i++) { + argv[i] = strdup(halt_info.argv_copy[i]); + } + argv[i] = "-Z"; + argv[i+1] = save_info; + argv[i+2] = NULL; + + // Get the "real" cwd + char exe_dir[1024]; + int cut_point = readlink("/proc/self/exe", exe_dir, sizeof(exe_dir)); + for(uint i=cut_point; i > 1; i--) { + if (exe_dir[i] == '/') { + exe_dir[i+1] = '\0'; + break; + } + } + // Start holder script + execv(strcat(exe_dir, "mpvpaper-holder"), argv); + + cflp_warning("Failed to stop mpvpaper"); + halt_info.auto_stop = 0; + halt_info.stoplist = NULL; +} + +static char *check_halt_list(char ***list); + +static void pause_for_halt_list(const char *app) { + + mpv_command_async(mpv, 0, (const char*[]) {"set", "pause", "yes", NULL}); + + char pid_name[512] = {0}; + strcpy(pid_name, "pidof "); + strcat(pid_name, app); + strcat(pid_name, " > /dev/null"); + + while(!system(pid_name)) { + sleep(1); + // Priorize stop halt list + if (halt_info.stoplist) { + char *app = check_halt_list(&halt_info.stoplist); + if (app) { + if (VERBOSE) + cflp_info("Stopping for %s", app); + stop_mpvpaper(); + } + } + } + mpv_command_async(mpv, 0, (const char*[]) {"set", "pause", "no", NULL}); +} + +static char *check_halt_list(char ***list) { + char pid_name[512] = {0}; + + for (uint i=0; (*list)[i] != NULL; i++) { + strcpy(pid_name, "pidof "); + strcat(pid_name, (*list)[i]); + strcat(pid_name, " > /dev/null"); + + // Stop if program is open + if (!system(pid_name)) { + return (*list)[i]; + } + } + return NULL; +} + +static void *handle_halt_lists() { + pthread_detach(pthread_self()); + + while (1) { + if (halt_info.pauselist) { // Check if already paused + if (strcmp(mpv_get_property_string(mpv, "pause"), "no") == 0) { + char *app = check_halt_list(&halt_info.pauselist); + if (app) { + if (VERBOSE) + cflp_info("Pausing for %s", app); + pause_for_halt_list(app); + } + } + } + + if (halt_info.stoplist) { + char *app = check_halt_list(&halt_info.stoplist); + if (app) { + if (VERBOSE) + cflp_info("Stopping for %s", app); + stop_mpvpaper(); + } + } + sleep(1); + } + pthread_exit(NULL); +} + +static void *handle_auto_pause() { + pthread_detach(pthread_self()); + + while (halt_info.auto_pause) { + if (strcmp(mpv_get_property_string(mpv, "pause"), "no") == 0) { // Check if already paused + time_t start_time = time(NULL); + bool is_paused = 0; + + // Set deadman switch timer + halt_info.frame_ready = 0; + while(!halt_info.frame_ready) { + if ((time(NULL) - start_time) > 1 && !is_paused) { + if (VERBOSE) + cflp_info("Pausing because mpvpaper is not visible"); + + mpv_command_async(mpv, 0, (const char*[]) {"set", "pause", "yes", NULL}); + is_paused = 1; + } + usleep(10000); + } + if (is_paused) + mpv_command_async(mpv, 0, (const char*[]) {"set", "pause", "no", NULL}); + } + + sleep(1); + } + pthread_exit(NULL); +} + +static void *handle_auto_stop() { + pthread_detach(pthread_self()); + + while (halt_info.auto_stop) { + time_t start_time = time(NULL); + + // Set deadman switch timer + halt_info.frame_ready = 0; + while(!halt_info.frame_ready) { + if ((time(NULL) - start_time) > 1) { + if (VERBOSE) + cflp_info("Stopping because mpvpaper is not visible"); + stop_mpvpaper(); + break; + } + usleep(10000); + } + sleep(1); + } + pthread_exit(NULL); +} + static void *get_proc_address_mpv(void *ctx, const char *name){ (void) ctx; return eglGetProcAddress(name); @@ -108,32 +312,34 @@ static void init_mpv(struct display_output *output) { mpv = mpv_create(); if (!mpv) { cflp_error("Failed creating mpv context"); - exit(EXIT_FAILURE); + exit_mpvpaper(1); } + // Enable user control through terminal by default + mpv_set_option_string(mpv, "input-default-bindings", "yes"); + mpv_set_option_string(mpv, "input-terminal", "yes"); + mpv_set_option_string(mpv, "terminal", "yes"); + // Load user configs - const char *homedir = getenv("HOME"); - char *configcat = (char *) malloc(sizeof(homedir) + 30); + const char *home_dir = getenv("HOME"); + char *config_path = calloc(strlen(home_dir)+1 + 30, sizeof(char)); + strcpy(config_path, home_dir); char loaded_configs[50] = ""; - strcpy(configcat, homedir); - if (mpv_load_config_file(mpv, strcat(configcat, "/.config/mpv/mpv.conf")) == 0) + strcpy(config_path+strlen(home_dir), "/.config/mpv/mpv.conf"); + if (mpv_load_config_file(mpv, config_path) == 0) strcat(loaded_configs, "mpv.conf "); - strcpy(configcat, homedir); - if (mpv_load_config_file(mpv, strcat(configcat, "/.config/mpv/input.conf")) == 0) + strcpy(config_path+strlen(home_dir), "/.config/mpv/input.conf"); + if (mpv_load_config_file(mpv, config_path) == 0) strcat(loaded_configs, "input.conf "); - strcpy(configcat, homedir); - if(mpv_load_config_file(mpv, strcat(configcat, "/.config/mpv/fonts.conf")) == 0) + strcpy(config_path+strlen(home_dir), "/.config/mpv/fonts.conf"); + if(mpv_load_config_file(mpv, config_path) == 0) strcat(loaded_configs, "fonts.conf "); + free(config_path); if (VERBOSE && strcmp(loaded_configs, "")) - cflp_info("Loaded [ %s] user configs from \"~/.config\"", loaded_configs); - - // Enable user control through terminal - mpv_set_option_string(mpv, "input-default-bindings", "yes"); - mpv_set_option_string(mpv, "input-terminal", "yes"); - mpv_set_option_string(mpv, "terminal", "yes"); + cflp_info("Loaded [ %s] user configs from \"~/.config/mpv/\"", loaded_configs); // Set mpv_options passed mpv_load_config_file(mpv, "/tmp/mpvpaper.conf"); @@ -141,7 +347,7 @@ static void init_mpv(struct display_output *output) { if (mpv_initialize(mpv) < 0) { cflp_error("mpv init failed"); - exit(EXIT_FAILURE); + exit_mpvpaper(1); } // Have mpv render onto egl context mpv_render_param params[] = { @@ -154,16 +360,33 @@ static void init_mpv(struct display_output *output) { if (mpv_render_context_create(&mpv_glcontext, mpv, params) < 0) cflp_error("Failed to initialize mpv GL context"); - const char* cmd[] = {"loadfile", video_path, NULL}; - mpv_command_async(mpv, 0, cmd); + // Restore video position after auto stop event + char* default_start = NULL; + if (halt_info.save_info) { + char time_pos[10]; + char playlist_pos[10]; + sscanf(halt_info.save_info, "%s %s", time_pos, playlist_pos); + + // Save default start pos + default_start = mpv_get_property_string(mpv, "start"); + // Restore video position + mpv_command_async(mpv, 0, (const char*[]) {"set", "start", time_pos, NULL}); + // Recover playlist pos, that is if it's not shuffled... + mpv_command_async(mpv, 0, (const char*[]) {"set", "playlist-start", playlist_pos, NULL}); + } + + mpv_command_async(mpv, 0, (const char*[]) {"loadfile", video_path, NULL}); mpv_event* event = mpv_wait_event(mpv, 1); while (event->event_id != MPV_EVENT_FILE_LOADED){ event = mpv_wait_event(mpv, 1); } - if (VERBOSE) { + if (VERBOSE) cflp_info("Loaded %s", video_path); - } + + // Return start pos to default + if (default_start) + mpv_command_async(mpv, 0, (const char*[]) {"set", "start", default_start, NULL}); } static void init_egl(struct display_output *output) { @@ -195,7 +418,7 @@ static void init_egl(struct display_output *output) { {0, 0} }; egl_context = NULL; - for (int i = 0; gl_versions[i].major > 0; i++) { + for (uint i = 0; gl_versions[i].major > 0; i++) { const EGLint ctx_attrib[] = { EGL_CONTEXT_MAJOR_VERSION, gl_versions[i].major, EGL_CONTEXT_MINOR_VERSION, gl_versions[i].major, @@ -211,7 +434,7 @@ static void init_egl(struct display_output *output) { } if (!egl_context) { cflp_error("Failed to create EGL context"); - exit(EXIT_FAILURE); + exit_mpvpaper(1); } } @@ -259,7 +482,19 @@ static void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *su init_egl(output); if (!mpv) { init_mpv(output); + + // Thread for monitoring if mpvpaper is visible + pthread_t visibility_thread; + if (halt_info.auto_pause) + pthread_create(&visibility_thread, NULL, handle_auto_pause, NULL); + else if (halt_info.auto_stop) + pthread_create(&visibility_thread, NULL, handle_auto_stop, NULL); + // Thread for monitoring halt lists + pthread_t halt_thread; + if (halt_info.pauselist || halt_info.stoplist) + pthread_create(&halt_thread, NULL, handle_halt_lists, NULL); } + if (egl_display && mpv_glcontext) { // Start render loop render(output); @@ -285,7 +520,6 @@ static void create_layer_surface(struct display_output *output) { // Empty input region struct wl_region *input_region = wl_compositor_create_region(output->state->compositor); - wl_surface_set_input_region(output->surface, input_region); wl_region_destroy(input_region); @@ -302,7 +536,6 @@ static void create_layer_surface(struct display_output *output) { zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output); wl_surface_commit(output->surface); - } static void xdg_output_handle_name(void *data, @@ -324,7 +557,7 @@ static void xdg_output_handle_description(void *data, char *paren = strrchr(description, '('); if (paren) { size_t length = paren - description; - output->identifier = malloc(length); + output->identifier = calloc(length, sizeof(char)); if (!output->identifier) { cflp_warning("Failed to allocate output identifier"); return; @@ -407,12 +640,51 @@ static const struct wl_registry_listener registry_listener = { .global_remove = handle_global_remove, }; +static char **get_halt_list(char *path_name) { + + FILE *file = fopen(path_name, "r"); + if (file) { + // Get alloc size + fseek(file, 0L, SEEK_END); + char **list = calloc(ftell(file) + 1, sizeof(char)); + rewind(file); + + // Read lines + char app[512]; + for (uint i=0; fscanf(file, "%s", app) == 1; i++) { + list[i] = strdup(app); + } + + fclose(file); + return list; + } + return NULL; +} + +static void set_halt_lists() { + const char *home_dir = getenv("HOME"); + + char *pause_path = calloc(strlen(home_dir)+1 + strlen("/.config/mpvpaper/pauselist")+1, sizeof(char)); + strcpy(pause_path, home_dir); + strcat(pause_path, "/.config/mpvpaper/pauselist"); + halt_info.pauselist = get_halt_list(pause_path); + free(pause_path); + + char *stop_path = calloc(strlen(home_dir)+1 + strlen("/.config/mpvpaper/stoplist")+1, sizeof(char)); + strcpy(stop_path, home_dir); + strcat(stop_path, "/.config/mpvpaper/stoplist"); + halt_info.stoplist = get_halt_list(stop_path); + free(stop_path); +} + static void parse_command_line(int argc, char **argv, struct wl_state *state) { static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"verbose", no_argument, NULL, 'v'}, {"fork", no_argument, NULL, 'f'}, + {"auto-pause", no_argument, NULL, 'p'}, + {"auto-stop", no_argument, NULL, 's'}, {"layer", required_argument, NULL, 'l'}, {"mpv-options", required_argument, NULL, 'o'}, {0, 0, 0, 0} @@ -421,19 +693,23 @@ static void parse_command_line(int argc, char **argv, struct wl_state *state) { const char *usage = "Usage: mpvpaper [options] <output> <url|path filename>\n" "Options:\n" - "--help\t\t-h\tDisplays this help message\n" - "--verbose\t-v\tBe more verbose\n" - "--fork\t\t-f\tForks mpvpaper so you can close the terminal\n" - "--layer\t\t-l\tSpecifies shell surface layer to run on (background by default)\n" - "--mpv-options\t-o\tForwards mpv options (Must be within quotes\"\")\n"; + "--help -h Displays this help message\n" + "--verbose -v Be more verbose\n" + "--fork -f Forks mpvpaper so you can close the terminal\n" + "--auto-pause -p Automagically pause mpv when the wallpaper is hidden\n" + " This saves CPU usage, more or less, seamlessly\n" + "--auto-stop -s Automagically stop mpv when the wallpaper is hidden\n" + " This saves CPU/RAM usage, although more abruptly\n" + "--layer -l Specifies shell surface layer to run on (background by default)\n" + "--mpv-options -o Forwards mpv options (Must be within quotes\"\")\n"; if(argc > 2) { - char* layer_name; - char* mpv_options; + char *layer_name; + char *mpv_options; char opt; - while((opt = getopt_long(argc, argv, "hvfl:o:", long_options, NULL)) != -1) { + while((opt = getopt_long(argc, argv, "hvfpsl:o:Z:", long_options, NULL)) != -1) { switch (opt) { case 'h': @@ -450,6 +726,14 @@ static void parse_command_line(int argc, char **argv, struct wl_state *state) { fclose(stderr); fclose(stdin); break; + case 'p': + halt_info.auto_pause = 1; + halt_info.auto_stop = 0; + break; + case 's': + halt_info.auto_stop = 1; + halt_info.auto_pause = 0; + break; case 'l': layer_name = strdup(optarg); if(layer_name == NULL) { @@ -463,20 +747,26 @@ static void parse_command_line(int argc, char **argv, struct wl_state *state) { } else if(strcasecmp(layer_name, "overlay") == 0) { state->surface_layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; } else { - cflp_error("%s is not a shell surface layer", layer_name); + cflp_error("%s is not a shell surface layer\n" + "Your options are: top, bottom, background and overlay", layer_name); exit(EXIT_FAILURE); } break; case 'o': - mpv_options = optarg; + mpv_options = strdup(optarg); // Forward to a tmp file so mpv can parse options for (int i = 0; i < (int)strlen(mpv_options); i++) { - if (mpv_options[i] == ' ') mpv_options[i] = '\n'; + if (mpv_options[i] == ' ') + mpv_options[i] = '\n'; } FILE* file = fopen("/tmp/mpvpaper.conf", "w"); fputs(mpv_options, file); fclose(file); break; + + case 'Z': // Hidden option to recover video pos after stopping + halt_info.save_info = strdup(optarg); + break; } } if(optind + 1 >= argc) { @@ -495,14 +785,34 @@ static void parse_command_line(int argc, char **argv, struct wl_state *state) { fprintf(stderr, "%s", usage); exit(EXIT_FAILURE); } - } + int main(int argc, char **argv) { + signal(SIGINT, handle_signal); struct wl_state state = {0}; wl_list_init(&state.outputs); parse_command_line(argc, argv, &state); + set_halt_lists(); + + // Copy argv + int argv_alloc_size = 0; + for(int i=0; argv[i] != NULL; i++) { + argv_alloc_size += strlen(argv[i])+1; + } + halt_info.argv_copy = calloc(argv_alloc_size, sizeof(char)); + + int j = 0; + for(int i=0; i < argc; i++) { + if (strcmp(argv[i], "-Z") == 0) { // Remove hidden opt + i++; // Skip optind + } + else { + halt_info.argv_copy[j] = strdup(argv[i]); + j++; + } + } state.display = wl_display_connect(NULL); if (!state.display) { @@ -537,7 +847,7 @@ int main(int argc, char **argv) { } state.run_display = 1; - while (wl_display_dispatch(state.display) != -1 && state.run_display) { + while (wl_display_dispatch(state.display) != -1) { // NOP } |