From edb117e3bdb4d6bef4a4749d94144df8472c0a4d Mon Sep 17 00:00:00 2001 From: Max Voit Date: Thu, 26 Jan 2017 22:18:32 +0100 Subject: [PATCH] Add autoreload support by inotify (and dummy backend nop) --- Makefile | 7 ++ autoreload.h | 43 ++++++++++++ autoreload_inotify.c | 154 +++++++++++++++++++++++++++++++++++++++++++ autoreload_nop.c | 31 +++++++++ main.c | 13 +++- 5 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 autoreload.h create mode 100644 autoreload_inotify.c create mode 100644 autoreload_nop.c diff --git a/Makefile b/Makefile index f375fa3..4276326 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,13 @@ endif .PHONY: clean install uninstall SRC := commands.c image.c main.c options.c thumbs.c util.c window.c +# conditionally compile in autoreload-backend; usage: `make AUTORELOAD=nop` +ifeq ($(AUTORELOAD),nop) + SRC += autoreload_nop.c +else + SRC += autoreload_inotify.c +endif + DEP := $(SRC:.c=.d) OBJ := $(SRC:.c=.o) diff --git a/autoreload.h b/autoreload.h new file mode 100644 index 0000000..439b695 --- /dev/null +++ b/autoreload.h @@ -0,0 +1,43 @@ +/* Copyright 2017 Max Voit + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#ifndef AUTORELOAD_H +#define AUTORELOAD_H + +#include "types.h" + +void arl_cleanup(void); +void arl_handle(void); +void arl_init(void); +void arl_setup(void); +void arl_setup_dir(void); + +typedef struct { + int fd; + int wd; + bool watching_dir; +} autoreload_t; + +extern autoreload_t autoreload; +extern int fileidx; +extern fileinfo_t *files; + +void load_image(int); +void redraw(void); + +#endif /* AUTORELOAD_H */ diff --git a/autoreload_inotify.c b/autoreload_inotify.c new file mode 100644 index 0000000..4a8e455 --- /dev/null +++ b/autoreload_inotify.c @@ -0,0 +1,154 @@ +/* Copyright 2017 Max Voit + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "autoreload.h" + +const struct timespec ten_ms = {0, 10000000}; + +void arl_cleanup(void) +{ + if (autoreload.fd != -1 && autoreload.wd != -1) + { + if(inotify_rm_watch(autoreload.fd, autoreload.wd)) + error(0, 0, "Failed to remove inotify watch."); + } +} + +void arl_handle(void) +{ + ssize_t len; + char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); + const struct inotify_event *event; + char *ptr; + char *fntmp, *fn; + + len = read(autoreload.fd, buf, sizeof buf); + if (len == -1) + { + error(0, 0, "Failed to read inotify events."); + return; + } + + for (ptr = buf; ptr < buf + len; + ptr += sizeof(struct inotify_event) + event->len) + { + + event = (const struct inotify_event *) ptr; + + /* events from watching the file itself */ + if (event->mask & IN_CLOSE_WRITE) + { + load_image(fileidx); + redraw(); + } + + if (event->mask & IN_DELETE_SELF) + arl_setup_dir(); + + /* events from watching the file's directory */ + if (event->mask & IN_CREATE) + { + fntmp = strdup(files[fileidx].path); + fn = basename(fntmp); + + if (0 == strcmp(event->name, fn)) + { + /* this is the file we're looking for */ + + /* cleanup, this has not been one-shot */ + if (autoreload.watching_dir) + { + if(inotify_rm_watch(autoreload.fd, autoreload.wd)) + error(0, 0, "Failed to remove inotify watch."); + autoreload.watching_dir = false; + } + + /* when too fast, imlib2 can't load the image */ + nanosleep(&ten_ms, NULL); + load_image(fileidx); + redraw(); + } + free(fntmp); + } + } +} + +void arl_init(void) +{ + /* this needs to be done only once */ + autoreload.fd = inotify_init(); + autoreload.watching_dir = false; + if (autoreload.fd == -1) + error(0, 0, "Could not initialize inotify."); +} + +void arl_setup(void) +{ + if (autoreload.fd == -1) + { + error(0, 0, "Uninitialized, could not add inotify watch."); + return; + } + + /* may have switched from a deleted to another image */ + if (autoreload.watching_dir) + { + if (inotify_rm_watch(autoreload.fd, autoreload.wd)) + error(0, 0, "Failed to remove inotify watch."); + autoreload.watching_dir = false; + } + + autoreload.wd = inotify_add_watch(autoreload.fd, files[fileidx].path, + IN_ONESHOT | IN_CLOSE_WRITE | IN_DELETE_SELF); + if (autoreload.wd == -1) + error(0, 0, "Failed to add inotify watch on file '%s'.", files[fileidx].path); +} + +void arl_setup_dir(void) +{ + char *dntmp, *dn; + + if (autoreload.fd == -1) + { + error(0, 0, "Uninitialized, could not add inotify watch on directory."); + return; + } + + /* get dirname */ + dntmp = (char*) strdup(files[fileidx].path); + dn = (char*) dirname(dntmp); + + /* this is not one-shot as other stuff may be created too + note: we won't handle deletion of the directory itself, + this is a design decision */ + autoreload.wd = inotify_add_watch(autoreload.fd, dn,IN_CREATE); + if (autoreload.wd == -1) + error(0, 0, "Failed to add inotify watch on directory '%s'.", dn); + else + autoreload.watching_dir = true; + + free(dntmp); +} diff --git a/autoreload_nop.c b/autoreload_nop.c new file mode 100644 index 0000000..19641f8 --- /dev/null +++ b/autoreload_nop.c @@ -0,0 +1,31 @@ +/* Copyright 2017 Max Voit + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "autoreload.h" + +void arl_cleanup(void) { } + +void arl_handle(void) { } + +void arl_init(void) { } + +void arl_setup(void) { } + +void arl_setup_dir(void) { } + + diff --git a/main.c b/main.c index 9e337f4..6214b94 100644 --- a/main.c +++ b/main.c @@ -38,6 +38,7 @@ #include "thumbs.h" #include "util.h" #include "window.h" +#include "autoreload.h" #define _MAPPINGS_CONFIG #include "config.h" @@ -64,6 +65,7 @@ fileinfo_t *files; int filecnt, fileidx; int alternate; int markcnt; +autoreload_t autoreload; int prefix; bool extprefix; @@ -98,6 +100,7 @@ timeout_t timeouts[] = { void cleanup(void) { img_close(&img, false); + arl_cleanup(); tns_free(&tns); win_close(&win); } @@ -317,6 +320,7 @@ void load_image(int new) info.open = false; open_info(); + arl_setup(); if (img.multi.cnt > 0 && img.multi.animate) set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); @@ -685,7 +689,7 @@ void run(void) init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; - if ((init_thumb || load_thumb || to_set || info.fd != -1) && + if ((init_thumb || load_thumb || to_set || info.fd != -1 || autoreload.fd != -1) && XPending(win.env.dpy) == 0) { if (load_thumb) { @@ -708,9 +712,15 @@ void run(void) FD_SET(info.fd, &fds); xfd = MAX(xfd, info.fd); } + if (autoreload.fd != -1) { + FD_SET(autoreload.fd, &fds); + xfd = MAX(xfd, autoreload.fd); + } select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL); if (info.fd != -1 && FD_ISSET(info.fd, &fds)) read_info(); + if (autoreload.fd != -1 && FD_ISSET(autoreload.fd, &fds)) + arl_handle(); } continue; } @@ -854,6 +864,7 @@ int main(int argc, char **argv) win_init(&win); img_init(&img, &win); + arl_init(); if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { homedir = getenv("HOME");