summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGhostNaN <GhostNaN@protonmail.com>2020-11-23 14:24:18 -0500
committerGhostNaN <GhostNaN@protonmail.com>2020-11-23 14:24:18 -0500
commit2905b10655aafb0ed0341b38b92f692be3907aae (patch)
tree074a7018ad236341d6ed29ad4fcad2128f35b6e2
parent00e08d4de44c9ac265d617bb72fe126c9dab145e (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.build8
-rw-r--r--src/holder.c407
-rw-r--r--src/main.c408
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, &registry_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;
+}
diff --git a/src/main.c b/src/main.c
index db611ac..20dea7c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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
}