2021-10-28 12:41:16 +02:00
|
|
|
/* Copyright 2011-2020 Bert Muennich
|
|
|
|
* Copyright 2021 nsxiv contributors
|
2011-01-17 14:57:59 +01:00
|
|
|
*
|
2021-09-16 11:32:59 +02:00
|
|
|
* This file is a part of nsxiv.
|
2011-08-18 01:18:26 +02:00
|
|
|
*
|
2021-09-16 11:32:59 +02:00
|
|
|
* nsxiv is free software; you can redistribute it and/or modify
|
2013-02-08 21:52:41 +01:00
|
|
|
* 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.
|
2011-08-18 01:18:26 +02:00
|
|
|
*
|
2021-09-16 11:32:59 +02:00
|
|
|
* nsxiv is distributed in the hope that it will be useful,
|
2013-02-08 21:52:41 +01:00
|
|
|
* 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
|
2021-09-16 11:32:59 +02:00
|
|
|
* along with nsxiv. If not, see <http://www.gnu.org/licenses/>.
|
2011-01-17 14:57:59 +01:00
|
|
|
*/
|
|
|
|
|
2021-09-16 11:32:59 +02:00
|
|
|
#include "nsxiv.h"
|
Add ability to bind arbitrary functions.
Before all the predated commands where kept in an array and their
indexes were used in bindings. This meant that users couldn't add their
own functions from the config file. Now key/mouse bindings have been
changed to to store the function ptr (wrapped in a cmd_t struct to also
store the mode) directly instead.
General cleanup done in this commit:
Defined `MODE_ALL` instead of using magic number.
For example, suppose one had bindings like:
{ 0, XK_q, g_quit, None },
{ ShitMask, XK_q, {quit_err}, None }
{ ControlMask, XK_q, {quit_err, .mode=MODE_IMAGE}, None }
The existing binding `q` has been left unchanged and is defined the same
way. However, the new hypothetical binding `Shift-q` can be used to call
the custom function quit_err in any mode (default). `Ctrl-q` on the
other hand will be called only on image mode.
Closes #50
2021-09-18 21:27:12 +02:00
|
|
|
#include "commands.h"
|
2017-10-16 21:10:35 +02:00
|
|
|
#define _MAPPINGS_CONFIG
|
|
|
|
#include "config.h"
|
|
|
|
|
2011-01-17 16:41:50 +01:00
|
|
|
#include <stdlib.h>
|
2011-02-02 10:34:14 +01:00
|
|
|
#include <string.h>
|
2013-02-11 23:05:26 +01:00
|
|
|
#include <fcntl.h>
|
2011-03-01 18:49:02 +01:00
|
|
|
#include <unistd.h>
|
2013-02-11 23:05:26 +01:00
|
|
|
#include <errno.h>
|
2017-10-24 21:43:36 +02:00
|
|
|
#include <locale.h>
|
2013-02-11 23:05:26 +01:00
|
|
|
#include <signal.h>
|
2012-02-27 19:22:05 +01:00
|
|
|
#include <sys/select.h>
|
2011-07-26 18:01:29 +02:00
|
|
|
#include <sys/stat.h>
|
2013-02-11 23:05:26 +01:00
|
|
|
#include <sys/wait.h>
|
2017-05-17 20:07:32 +02:00
|
|
|
#include <time.h>
|
2011-08-19 15:02:10 +02:00
|
|
|
#include <X11/keysym.h>
|
2014-01-09 20:38:46 +01:00
|
|
|
#include <X11/XF86keysym.h>
|
2011-01-17 16:41:50 +01:00
|
|
|
|
2021-10-11 05:07:18 +02:00
|
|
|
#define MODMASK(mask) ((mask) & ~ignore_mask)
|
|
|
|
#define BAR_SEP " "
|
|
|
|
|
2011-09-02 04:33:44 +02:00
|
|
|
typedef struct {
|
|
|
|
struct timeval when;
|
2011-09-11 21:01:24 +02:00
|
|
|
bool active;
|
2011-09-02 04:33:44 +02:00
|
|
|
timeout_f handler;
|
|
|
|
} timeout_t;
|
|
|
|
|
|
|
|
/* timeout handler functions: */
|
2011-10-12 18:38:29 +02:00
|
|
|
void redraw(void);
|
|
|
|
void reset_cursor(void);
|
|
|
|
void animate(void);
|
2014-01-04 18:38:40 +01:00
|
|
|
void slideshow(void);
|
2012-03-13 21:58:48 +01:00
|
|
|
void clear_resize(void);
|
2011-09-02 04:33:44 +02:00
|
|
|
|
2011-02-16 17:09:46 +01:00
|
|
|
appmode_t mode;
|
2017-05-17 20:07:32 +02:00
|
|
|
arl_t arl;
|
2011-01-19 14:07:45 +01:00
|
|
|
img_t img;
|
2011-02-16 21:40:20 +01:00
|
|
|
tns_t tns;
|
2011-01-19 14:07:45 +01:00
|
|
|
win_t win;
|
2011-01-20 16:24:48 +01:00
|
|
|
|
2011-08-18 00:38:55 +02:00
|
|
|
fileinfo_t *files;
|
2011-01-23 12:36:27 +01:00
|
|
|
int filecnt, fileidx;
|
2012-08-16 13:40:04 +02:00
|
|
|
int alternate;
|
2014-08-16 19:24:34 +02:00
|
|
|
int markcnt;
|
2018-06-09 11:57:42 +02:00
|
|
|
int markidx;
|
2011-01-19 14:07:45 +01:00
|
|
|
|
2011-10-16 16:08:55 +02:00
|
|
|
int prefix;
|
2014-01-31 13:21:23 +01:00
|
|
|
bool extprefix;
|
2011-10-16 16:08:55 +02:00
|
|
|
|
2012-03-13 21:58:48 +01:00
|
|
|
bool resized = false;
|
|
|
|
|
2015-10-28 23:03:37 +01:00
|
|
|
typedef struct {
|
|
|
|
int err;
|
|
|
|
char *cmd;
|
|
|
|
} extcmd_t;
|
|
|
|
|
2013-01-07 14:30:42 +01:00
|
|
|
struct {
|
2020-01-16 10:31:41 +01:00
|
|
|
extcmd_t f;
|
|
|
|
int fd;
|
|
|
|
unsigned int i, lastsep;
|
|
|
|
pid_t pid;
|
2013-02-11 23:05:26 +01:00
|
|
|
} info;
|
2011-01-20 17:00:59 +01:00
|
|
|
|
2014-02-18 21:10:07 +01:00
|
|
|
struct {
|
2015-10-28 23:03:37 +01:00
|
|
|
extcmd_t f;
|
2014-02-18 21:10:07 +01:00
|
|
|
bool warned;
|
|
|
|
} keyhandler;
|
2014-01-11 22:47:41 +01:00
|
|
|
|
2011-09-02 04:33:44 +02:00
|
|
|
timeout_t timeouts[] = {
|
2014-01-04 18:38:40 +01:00
|
|
|
{ { 0, 0 }, false, redraw },
|
2011-09-11 21:01:24 +02:00
|
|
|
{ { 0, 0 }, false, reset_cursor },
|
2014-01-04 18:38:40 +01:00
|
|
|
{ { 0, 0 }, false, animate },
|
|
|
|
{ { 0, 0 }, false, slideshow },
|
2012-03-13 21:58:48 +01:00
|
|
|
{ { 0, 0 }, false, clear_resize },
|
2011-09-02 04:33:44 +02:00
|
|
|
};
|
2011-08-19 15:02:10 +02:00
|
|
|
|
2021-10-11 05:07:18 +02:00
|
|
|
/**************************
|
|
|
|
function implementations
|
|
|
|
**************************/
|
2013-02-08 22:05:31 +01:00
|
|
|
void cleanup(void)
|
|
|
|
{
|
2015-10-28 23:03:37 +01:00
|
|
|
img_close(&img, false);
|
2017-05-17 20:07:32 +02:00
|
|
|
arl_cleanup(&arl);
|
2015-10-28 23:03:37 +01:00
|
|
|
tns_free(&tns);
|
|
|
|
win_close(&win);
|
2011-02-03 10:15:01 +01:00
|
|
|
}
|
|
|
|
|
2014-10-24 11:14:01 +02:00
|
|
|
void check_add_file(char *filename, bool given)
|
2013-02-08 22:05:31 +01:00
|
|
|
{
|
2015-10-28 23:03:37 +01:00
|
|
|
char *path;
|
2012-02-21 12:49:29 +01:00
|
|
|
|
2015-10-28 20:59:48 +01:00
|
|
|
if (*filename == '\0')
|
2011-08-18 00:38:55 +02:00
|
|
|
return;
|
2011-07-26 18:01:29 +02:00
|
|
|
|
2015-10-28 23:03:37 +01:00
|
|
|
if (access(filename, R_OK) < 0 ||
|
|
|
|
(path = realpath(filename, NULL)) == NULL)
|
|
|
|
{
|
2014-10-24 11:14:01 +02:00
|
|
|
if (given)
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, errno, "%s", filename);
|
2011-08-18 00:38:55 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fileidx == filecnt) {
|
|
|
|
filecnt *= 2;
|
2015-10-28 22:29:01 +01:00
|
|
|
files = erealloc(files, filecnt * sizeof(*files));
|
2015-01-04 15:38:49 +01:00
|
|
|
memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files));
|
2011-08-18 00:38:55 +02:00
|
|
|
}
|
2014-03-17 20:01:53 +01:00
|
|
|
|
2015-10-28 22:29:01 +01:00
|
|
|
files[fileidx].name = estrdup(filename);
|
2015-10-28 23:03:37 +01:00
|
|
|
files[fileidx].path = path;
|
2015-01-04 15:38:49 +01:00
|
|
|
if (given)
|
|
|
|
files[fileidx].flags |= FF_WARN;
|
2011-08-18 00:38:55 +02:00
|
|
|
fileidx++;
|
2011-07-26 18:01:29 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void remove_file(int n, bool manual)
|
|
|
|
{
|
2011-04-11 16:58:38 +02:00
|
|
|
if (n < 0 || n >= filecnt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (filecnt == 1) {
|
2011-09-26 21:28:27 +02:00
|
|
|
if (!manual)
|
2021-09-16 11:32:59 +02:00
|
|
|
fprintf(stderr, "nsxiv: no more files to display, aborting\n");
|
2011-09-26 21:28:27 +02:00
|
|
|
exit(manual ? EXIT_SUCCESS : EXIT_FAILURE);
|
2011-04-11 16:58:38 +02:00
|
|
|
}
|
2015-01-04 15:38:49 +01:00
|
|
|
if (files[n].flags & FF_MARK)
|
2014-08-16 19:24:34 +02:00
|
|
|
markcnt--;
|
2011-04-11 16:58:38 +02:00
|
|
|
|
2011-09-03 19:08:49 +02:00
|
|
|
if (files[n].path != files[n].name)
|
|
|
|
free((void*) files[n].path);
|
|
|
|
free((void*) files[n].name);
|
|
|
|
|
2014-09-30 21:54:17 +02:00
|
|
|
if (n + 1 < filecnt) {
|
2014-10-01 20:25:36 +02:00
|
|
|
if (tns.thumbs != NULL) {
|
|
|
|
memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) *
|
|
|
|
sizeof(*tns.thumbs));
|
|
|
|
memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs));
|
|
|
|
}
|
|
|
|
memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files));
|
2011-04-11 16:58:38 +02:00
|
|
|
}
|
2011-07-26 18:01:29 +02:00
|
|
|
filecnt--;
|
2018-12-29 18:48:23 +01:00
|
|
|
if (fileidx > n || fileidx == filecnt)
|
2017-10-26 22:20:39 +02:00
|
|
|
fileidx--;
|
2018-12-29 18:48:23 +01:00
|
|
|
if (alternate > n || alternate == filecnt)
|
2013-10-21 21:57:21 +02:00
|
|
|
alternate--;
|
2018-12-29 18:48:23 +01:00
|
|
|
if (markidx > n || markidx == filecnt)
|
2018-06-09 11:57:42 +02:00
|
|
|
markidx--;
|
2011-04-11 16:58:38 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void set_timeout(timeout_f handler, int time, bool overwrite)
|
|
|
|
{
|
2021-10-28 22:00:53 +02:00
|
|
|
unsigned int i;
|
2011-09-02 04:33:44 +02:00
|
|
|
|
2011-09-06 09:11:03 +02:00
|
|
|
for (i = 0; i < ARRLEN(timeouts); i++) {
|
2011-09-02 04:33:44 +02:00
|
|
|
if (timeouts[i].handler == handler) {
|
|
|
|
if (!timeouts[i].active || overwrite) {
|
|
|
|
gettimeofday(&timeouts[i].when, 0);
|
2011-10-13 16:50:06 +02:00
|
|
|
TV_ADD_MSEC(&timeouts[i].when, time);
|
2011-09-11 21:01:24 +02:00
|
|
|
timeouts[i].active = true;
|
2011-09-02 04:33:44 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void reset_timeout(timeout_f handler)
|
|
|
|
{
|
2021-10-28 22:00:53 +02:00
|
|
|
unsigned int i;
|
2011-09-02 04:33:44 +02:00
|
|
|
|
2011-09-06 09:11:03 +02:00
|
|
|
for (i = 0; i < ARRLEN(timeouts); i++) {
|
2011-09-02 04:33:44 +02:00
|
|
|
if (timeouts[i].handler == handler) {
|
2011-09-11 21:01:24 +02:00
|
|
|
timeouts[i].active = false;
|
2011-09-02 04:33:44 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
bool check_timeouts(struct timeval *t)
|
|
|
|
{
|
2011-09-02 18:54:41 +02:00
|
|
|
int i = 0, tdiff, tmin = -1;
|
2011-09-02 04:33:44 +02:00
|
|
|
struct timeval now;
|
|
|
|
|
2011-09-06 09:11:03 +02:00
|
|
|
while (i < ARRLEN(timeouts)) {
|
2011-09-02 04:33:44 +02:00
|
|
|
if (timeouts[i].active) {
|
2012-10-31 23:24:21 +01:00
|
|
|
gettimeofday(&now, 0);
|
2011-10-13 16:50:06 +02:00
|
|
|
tdiff = TV_DIFF(&timeouts[i].when, &now);
|
2011-09-02 04:33:44 +02:00
|
|
|
if (tdiff <= 0) {
|
2011-09-11 21:01:24 +02:00
|
|
|
timeouts[i].active = false;
|
2011-09-29 12:43:36 +02:00
|
|
|
if (timeouts[i].handler != NULL)
|
2011-09-02 04:33:44 +02:00
|
|
|
timeouts[i].handler();
|
2011-09-02 18:54:41 +02:00
|
|
|
i = tmin = -1;
|
2011-09-02 04:33:44 +02:00
|
|
|
} else if (tmin < 0 || tdiff < tmin) {
|
|
|
|
tmin = tdiff;
|
|
|
|
}
|
|
|
|
}
|
2011-09-02 18:54:41 +02:00
|
|
|
i++;
|
2011-09-02 04:33:44 +02:00
|
|
|
}
|
2011-09-29 12:43:36 +02:00
|
|
|
if (tmin > 0 && t != NULL)
|
2011-10-13 16:50:06 +02:00
|
|
|
TV_SET_MSEC(t, tmin);
|
2011-09-02 04:33:44 +02:00
|
|
|
return tmin > 0;
|
|
|
|
}
|
|
|
|
|
2018-02-18 13:16:00 +01:00
|
|
|
void close_info(void)
|
|
|
|
{
|
|
|
|
if (info.fd != -1) {
|
|
|
|
kill(info.pid, SIGTERM);
|
|
|
|
close(info.fd);
|
|
|
|
info.fd = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-11 23:05:26 +01:00
|
|
|
void open_info(void)
|
|
|
|
{
|
|
|
|
int pfd[2];
|
2016-08-06 12:22:47 +02:00
|
|
|
char w[12], h[12];
|
2013-02-11 23:05:26 +01:00
|
|
|
|
2021-10-28 22:00:53 +02:00
|
|
|
if (info.f.err || info.fd >= 0 || win.bar.h == 0)
|
2013-02-12 17:55:47 +01:00
|
|
|
return;
|
2014-10-01 22:35:22 +02:00
|
|
|
win.bar.l.buf[0] = '\0';
|
2013-02-12 17:55:47 +01:00
|
|
|
if (pipe(pfd) < 0)
|
|
|
|
return;
|
2018-02-18 13:16:00 +01:00
|
|
|
if ((info.pid = fork()) == 0) {
|
2013-02-11 23:05:26 +01:00
|
|
|
close(pfd[0]);
|
|
|
|
dup2(pfd[1], 1);
|
2016-08-06 12:22:47 +02:00
|
|
|
snprintf(w, sizeof(w), "%d", img.w);
|
|
|
|
snprintf(h, sizeof(h), "%d", img.h);
|
|
|
|
execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL);
|
2015-10-28 23:03:37 +01:00
|
|
|
error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd);
|
2013-02-11 23:05:26 +01:00
|
|
|
}
|
2014-11-27 22:25:27 +01:00
|
|
|
close(pfd[1]);
|
2018-02-18 13:16:00 +01:00
|
|
|
if (info.pid < 0) {
|
2014-11-27 22:25:27 +01:00
|
|
|
close(pfd[0]);
|
|
|
|
} else {
|
|
|
|
fcntl(pfd[0], F_SETFL, O_NONBLOCK);
|
|
|
|
info.fd = pfd[0];
|
|
|
|
info.i = info.lastsep = 0;
|
|
|
|
}
|
2013-02-11 23:05:26 +01:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void read_info(void)
|
|
|
|
{
|
2013-02-11 23:05:26 +01:00
|
|
|
ssize_t i, n;
|
|
|
|
char buf[BAR_L_LEN];
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
n = read(info.fd, buf, sizeof(buf));
|
|
|
|
if (n < 0 && errno == EAGAIN)
|
|
|
|
return;
|
|
|
|
else if (n == 0)
|
2013-01-07 14:30:42 +01:00
|
|
|
goto end;
|
2013-02-11 23:05:26 +01:00
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
if (buf[i] == '\n') {
|
|
|
|
if (info.lastsep == 0) {
|
2014-10-01 22:35:22 +02:00
|
|
|
win.bar.l.buf[info.i++] = ' ';
|
2013-02-11 23:05:26 +01:00
|
|
|
info.lastsep = 1;
|
2013-01-07 14:30:42 +01:00
|
|
|
}
|
|
|
|
} else {
|
2014-10-01 22:35:22 +02:00
|
|
|
win.bar.l.buf[info.i++] = buf[i];
|
2013-02-11 23:05:26 +01:00
|
|
|
info.lastsep = 0;
|
2013-01-07 14:30:42 +01:00
|
|
|
}
|
2014-10-01 22:35:22 +02:00
|
|
|
if (info.i + 1 == win.bar.l.size)
|
2013-02-11 23:05:26 +01:00
|
|
|
goto end;
|
2013-01-07 14:30:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
2013-02-11 23:05:26 +01:00
|
|
|
info.i -= info.lastsep;
|
2014-10-01 22:35:22 +02:00
|
|
|
win.bar.l.buf[info.i] = '\0';
|
2014-07-28 20:36:32 +02:00
|
|
|
win_draw(&win);
|
2018-02-18 13:16:00 +01:00
|
|
|
close_info();
|
2013-01-07 14:30:42 +01:00
|
|
|
}
|
2011-02-03 14:32:02 +01:00
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void load_image(int new)
|
|
|
|
{
|
2017-10-26 22:20:39 +02:00
|
|
|
bool prev = new < fileidx;
|
2014-08-29 20:16:24 +02:00
|
|
|
static int current;
|
|
|
|
|
2011-07-26 18:01:29 +02:00
|
|
|
if (new < 0 || new >= filecnt)
|
|
|
|
return;
|
2011-04-11 16:58:38 +02:00
|
|
|
|
2015-10-28 20:59:48 +01:00
|
|
|
if (win.xwin != None)
|
|
|
|
win_set_cursor(&win, CURSOR_WATCH);
|
2014-01-04 18:38:40 +01:00
|
|
|
reset_timeout(slideshow);
|
2011-09-03 17:01:39 +02:00
|
|
|
|
2014-08-29 20:16:24 +02:00
|
|
|
if (new != current)
|
|
|
|
alternate = current;
|
2013-10-21 21:57:21 +02:00
|
|
|
|
2011-09-11 21:01:24 +02:00
|
|
|
img_close(&img, false);
|
2011-08-18 00:38:55 +02:00
|
|
|
while (!img_load(&img, &files[new])) {
|
2011-09-11 21:01:24 +02:00
|
|
|
remove_file(new, false);
|
2011-07-26 18:01:29 +02:00
|
|
|
if (new >= filecnt)
|
|
|
|
new = filecnt - 1;
|
2017-10-26 22:20:39 +02:00
|
|
|
else if (new > 0 && prev)
|
2013-10-21 21:57:21 +02:00
|
|
|
new--;
|
2011-02-27 13:29:24 +01:00
|
|
|
}
|
2015-01-04 15:38:49 +01:00
|
|
|
files[new].flags &= ~FF_WARN;
|
2014-08-29 20:16:24 +02:00
|
|
|
fileidx = current = new;
|
2013-01-07 14:30:42 +01:00
|
|
|
|
2018-02-18 13:16:00 +01:00
|
|
|
close_info();
|
2013-02-11 23:05:26 +01:00
|
|
|
open_info();
|
2017-05-17 20:07:32 +02:00
|
|
|
arl_setup(&arl, files[fileidx].path);
|
2021-09-12 20:26:07 +02:00
|
|
|
win_set_title(&win, files[fileidx].path);
|
2011-08-18 16:05:32 +02:00
|
|
|
|
2011-09-29 12:43:36 +02:00
|
|
|
if (img.multi.cnt > 0 && img.multi.animate)
|
2011-09-11 21:01:24 +02:00
|
|
|
set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
|
2011-09-06 11:55:31 +02:00
|
|
|
else
|
|
|
|
reset_timeout(animate);
|
2011-02-03 14:32:02 +01:00
|
|
|
}
|
|
|
|
|
2018-06-09 13:12:46 +02:00
|
|
|
bool mark_image(int n, bool on)
|
|
|
|
{
|
2018-06-09 14:06:16 +02:00
|
|
|
markidx = n;
|
2018-06-09 13:12:46 +02:00
|
|
|
if (!!(files[n].flags & FF_MARK) != on) {
|
|
|
|
files[n].flags ^= FF_MARK;
|
|
|
|
markcnt += on ? 1 : -1;
|
|
|
|
if (mode == MODE_THUMB)
|
|
|
|
tns_mark(&tns, n, on);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-10-01 22:35:22 +02:00
|
|
|
void bar_put(win_bar_t *bar, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
size_t len = bar->size - (bar->p - bar->buf), n;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
n = vsnprintf(bar->p, len, fmt, ap);
|
|
|
|
bar->p += MIN(len, n);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void update_info(void)
|
|
|
|
{
|
2014-10-01 22:35:22 +02:00
|
|
|
unsigned int i, fn, fw;
|
2013-08-10 21:18:53 +02:00
|
|
|
const char * mark;
|
2014-10-01 22:35:22 +02:00
|
|
|
win_bar_t *l = &win.bar.l, *r = &win.bar.r;
|
2011-04-11 11:53:00 +02:00
|
|
|
|
2013-02-12 17:55:47 +01:00
|
|
|
/* update bar contents */
|
|
|
|
if (win.bar.h == 0)
|
|
|
|
return;
|
2014-10-01 22:35:22 +02:00
|
|
|
for (fw = 0, i = filecnt; i > 0; fw++, i /= 10);
|
2015-01-04 15:38:49 +01:00
|
|
|
mark = files[fileidx].flags & FF_MARK ? "* " : "";
|
2014-10-01 22:35:22 +02:00
|
|
|
l->p = l->buf;
|
|
|
|
r->p = r->buf;
|
2013-02-12 17:55:47 +01:00
|
|
|
if (mode == MODE_THUMB) {
|
2017-12-07 14:08:37 +01:00
|
|
|
if (tns.loadnext < tns.end)
|
2015-01-04 21:19:26 +01:00
|
|
|
bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1);
|
2017-12-07 14:08:37 +01:00
|
|
|
else if (tns.initnext < filecnt)
|
2015-01-04 21:19:26 +01:00
|
|
|
bar_put(l, "Caching... %0*d", fw, tns.initnext + 1);
|
2017-12-07 14:08:37 +01:00
|
|
|
else
|
|
|
|
strncpy(l->buf, files[fileidx].name, l->size);
|
2014-10-01 22:35:22 +02:00
|
|
|
bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt);
|
2011-04-11 11:53:00 +02:00
|
|
|
} else {
|
2014-10-01 22:35:22 +02:00
|
|
|
bar_put(r, "%s", mark);
|
2016-12-01 20:33:24 +01:00
|
|
|
if (img.ss.on) {
|
|
|
|
if (img.ss.delay % 10 != 0)
|
2017-12-07 21:44:40 +01:00
|
|
|
bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10);
|
2016-12-01 20:33:24 +01:00
|
|
|
else
|
2017-12-07 21:44:40 +01:00
|
|
|
bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10);
|
2016-12-01 20:33:24 +01:00
|
|
|
}
|
2021-10-28 22:00:53 +02:00
|
|
|
if (img.gamma)
|
2017-12-07 21:44:40 +01:00
|
|
|
bar_put(r, "G%+d" BAR_SEP, img.gamma);
|
|
|
|
bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0));
|
2012-02-12 19:00:41 +01:00
|
|
|
if (img.multi.cnt > 0) {
|
2013-01-07 14:30:42 +01:00
|
|
|
for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
|
2017-12-07 21:44:40 +01:00
|
|
|
bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
|
2012-02-12 19:00:41 +01:00
|
|
|
}
|
2014-10-01 22:35:22 +02:00
|
|
|
bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
|
2017-12-07 14:08:37 +01:00
|
|
|
if (info.f.err)
|
2014-10-01 22:35:22 +02:00
|
|
|
strncpy(l->buf, files[fileidx].name, l->size);
|
2011-04-11 11:53:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-05 13:53:29 +02:00
|
|
|
int ptr_third_x(void)
|
|
|
|
{
|
|
|
|
int x, y;
|
|
|
|
|
|
|
|
win_cursor_pos(&win, &x, &y);
|
|
|
|
return MAX(0, MIN(2, (x / (win.w * 0.33))));
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void redraw(void)
|
|
|
|
{
|
2014-01-04 18:38:40 +01:00
|
|
|
int t;
|
|
|
|
|
|
|
|
if (mode == MODE_IMAGE) {
|
2011-09-17 17:23:51 +02:00
|
|
|
img_render(&img);
|
2014-01-04 18:38:40 +01:00
|
|
|
if (img.ss.on) {
|
2016-12-01 20:33:24 +01:00
|
|
|
t = img.ss.delay * 100;
|
2014-01-08 20:31:07 +01:00
|
|
|
if (img.multi.cnt > 0 && img.multi.animate)
|
|
|
|
t = MAX(t, img.multi.length);
|
2014-01-04 18:38:40 +01:00
|
|
|
set_timeout(slideshow, t, false);
|
|
|
|
}
|
|
|
|
} else {
|
2011-09-17 17:23:51 +02:00
|
|
|
tns_render(&tns);
|
2014-01-04 18:38:40 +01:00
|
|
|
}
|
2012-02-12 19:00:41 +01:00
|
|
|
update_info();
|
|
|
|
win_draw(&win);
|
2011-09-02 04:33:44 +02:00
|
|
|
reset_timeout(redraw);
|
2011-09-03 17:01:39 +02:00
|
|
|
reset_cursor();
|
2011-09-02 04:33:44 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void reset_cursor(void)
|
|
|
|
{
|
2021-10-28 22:00:53 +02:00
|
|
|
int c;
|
|
|
|
unsigned int i;
|
2011-09-03 17:01:39 +02:00
|
|
|
cursor_t cursor = CURSOR_NONE;
|
|
|
|
|
|
|
|
if (mode == MODE_IMAGE) {
|
2011-09-06 09:11:03 +02:00
|
|
|
for (i = 0; i < ARRLEN(timeouts); i++) {
|
2011-09-03 17:01:39 +02:00
|
|
|
if (timeouts[i].handler == reset_cursor) {
|
2017-10-05 13:53:29 +02:00
|
|
|
if (timeouts[i].active) {
|
|
|
|
c = ptr_third_x();
|
|
|
|
c = MAX(fileidx > 0 ? 0 : 1, c);
|
|
|
|
c = MIN(fileidx + 1 < filecnt ? 2 : 1, c);
|
|
|
|
cursor = imgcursor[c];
|
|
|
|
}
|
2011-09-03 17:01:39 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2015-01-04 21:19:26 +01:00
|
|
|
if (tns.loadnext < tns.end || tns.initnext < filecnt)
|
2011-09-03 17:01:39 +02:00
|
|
|
cursor = CURSOR_WATCH;
|
|
|
|
else
|
|
|
|
cursor = CURSOR_ARROW;
|
|
|
|
}
|
|
|
|
win_set_cursor(&win, cursor);
|
2011-09-02 04:33:44 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void animate(void)
|
|
|
|
{
|
2014-09-01 20:41:27 +02:00
|
|
|
if (img_frame_animate(&img)) {
|
2011-09-11 21:01:24 +02:00
|
|
|
redraw();
|
|
|
|
set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
|
|
|
|
}
|
2011-09-10 18:41:20 +02:00
|
|
|
}
|
|
|
|
|
2014-01-04 18:38:40 +01:00
|
|
|
void slideshow(void)
|
|
|
|
{
|
|
|
|
load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0);
|
|
|
|
redraw();
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void clear_resize(void)
|
|
|
|
{
|
2012-03-13 21:58:48 +01:00
|
|
|
resized = false;
|
|
|
|
}
|
|
|
|
|
2016-11-29 12:07:08 +01:00
|
|
|
Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg)
|
|
|
|
{
|
|
|
|
return ev->type == ButtonPress || ev->type == KeyPress;
|
|
|
|
}
|
|
|
|
|
2021-10-07 02:37:34 +02:00
|
|
|
void handle_key_handler(bool init)
|
|
|
|
{
|
|
|
|
extprefix = init;
|
|
|
|
if (win.bar.h == 0)
|
|
|
|
return;
|
|
|
|
if (init) {
|
|
|
|
close_info();
|
|
|
|
snprintf(win.bar.l.buf, win.bar.l.size, "Getting key handler input "
|
|
|
|
"(%s to abort)...", XKeysymToString(keyhandler_abort));
|
|
|
|
} else { /* abort */
|
|
|
|
open_info();
|
|
|
|
update_info();
|
|
|
|
}
|
|
|
|
win_draw(&win);
|
|
|
|
}
|
|
|
|
|
2014-01-11 22:47:41 +01:00
|
|
|
void run_key_handler(const char *key, unsigned int mask)
|
2014-01-02 23:19:31 +01:00
|
|
|
{
|
|
|
|
pid_t pid;
|
2014-11-27 22:37:20 +01:00
|
|
|
FILE *pfs;
|
|
|
|
bool marked = mode == MODE_THUMB && markcnt > 0;
|
2014-08-16 21:31:05 +02:00
|
|
|
bool changed = false;
|
2018-02-18 14:32:55 +01:00
|
|
|
int f, i, pfd[2];
|
2014-11-27 22:37:20 +01:00
|
|
|
int fcnt = marked ? markcnt : 1;
|
2015-08-19 21:29:39 +02:00
|
|
|
char kstr[32];
|
2014-11-27 22:37:20 +01:00
|
|
|
struct stat *oldst, st;
|
2016-11-29 12:07:08 +01:00
|
|
|
XEvent dump;
|
2014-01-02 23:19:31 +01:00
|
|
|
|
2021-10-28 22:00:53 +02:00
|
|
|
if (keyhandler.f.err) {
|
2014-02-18 21:10:07 +01:00
|
|
|
if (!keyhandler.warned) {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
|
2014-02-18 21:10:07 +01:00
|
|
|
keyhandler.warned = true;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (key == NULL)
|
2014-01-02 23:19:31 +01:00
|
|
|
return;
|
|
|
|
|
2014-11-27 22:37:20 +01:00
|
|
|
if (pipe(pfd) < 0) {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, errno, "pipe");
|
2014-11-27 22:37:20 +01:00
|
|
|
return;
|
2014-08-16 21:31:05 +02:00
|
|
|
}
|
2014-11-27 22:37:20 +01:00
|
|
|
if ((pfs = fdopen(pfd[1], "w")) == NULL) {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, errno, "open pipe");
|
2014-11-27 22:37:20 +01:00
|
|
|
close(pfd[0]), close(pfd[1]);
|
|
|
|
return;
|
|
|
|
}
|
2015-10-28 22:29:01 +01:00
|
|
|
oldst = emalloc(fcnt * sizeof(*oldst));
|
2014-01-02 23:19:31 +01:00
|
|
|
|
2018-02-18 13:16:00 +01:00
|
|
|
close_info();
|
2014-10-01 22:35:22 +02:00
|
|
|
strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
|
2014-07-28 20:36:32 +02:00
|
|
|
win_draw(&win);
|
2014-06-15 14:15:48 +02:00
|
|
|
win_set_cursor(&win, CURSOR_WATCH);
|
2014-01-08 20:31:50 +01:00
|
|
|
|
2014-11-27 22:37:20 +01:00
|
|
|
snprintf(kstr, sizeof(kstr), "%s%s%s%s",
|
|
|
|
mask & ControlMask ? "C-" : "",
|
|
|
|
mask & Mod1Mask ? "M-" : "",
|
|
|
|
mask & ShiftMask ? "S-" : "", key);
|
|
|
|
|
2014-01-02 23:19:31 +01:00
|
|
|
if ((pid = fork()) == 0) {
|
2014-11-27 22:37:20 +01:00
|
|
|
close(pfd[1]);
|
|
|
|
dup2(pfd[0], 0);
|
2015-10-28 23:03:37 +01:00
|
|
|
execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL);
|
|
|
|
error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd);
|
2014-11-27 22:37:20 +01:00
|
|
|
}
|
|
|
|
close(pfd[0]);
|
|
|
|
if (pid < 0) {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, errno, "fork");
|
2014-11-27 22:37:20 +01:00
|
|
|
fclose(pfs);
|
2014-06-15 14:15:48 +02:00
|
|
|
goto end;
|
2014-01-02 23:19:31 +01:00
|
|
|
}
|
2014-11-27 22:37:20 +01:00
|
|
|
|
|
|
|
for (f = i = 0; f < fcnt; i++) {
|
2015-01-04 15:38:49 +01:00
|
|
|
if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
|
2014-11-27 22:37:20 +01:00
|
|
|
stat(files[i].path, &oldst[f]);
|
2017-01-07 14:44:33 +01:00
|
|
|
fprintf(pfs, "%s\n", files[i].name);
|
2014-11-27 22:37:20 +01:00
|
|
|
f++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(pfs);
|
2018-02-18 14:32:55 +01:00
|
|
|
while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
|
2014-01-08 20:31:50 +01:00
|
|
|
|
2014-11-27 22:37:20 +01:00
|
|
|
for (f = i = 0; f < fcnt; i++) {
|
2015-01-04 15:38:49 +01:00
|
|
|
if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
|
2014-11-27 22:37:20 +01:00
|
|
|
if (stat(files[i].path, &st) != 0 ||
|
2021-10-11 05:07:18 +02:00
|
|
|
memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
|
2014-11-27 22:37:20 +01:00
|
|
|
{
|
|
|
|
if (tns.thumbs != NULL) {
|
|
|
|
tns_unload(&tns, i);
|
|
|
|
tns.loadnext = MIN(tns.loadnext, i);
|
|
|
|
}
|
|
|
|
changed = true;
|
2014-08-16 21:31:05 +02:00
|
|
|
}
|
2014-11-27 22:37:20 +01:00
|
|
|
f++;
|
2014-08-16 21:31:05 +02:00
|
|
|
}
|
2014-01-08 20:31:50 +01:00
|
|
|
}
|
2017-02-15 20:20:41 +01:00
|
|
|
/* drop user input events that occurred while running the key handler */
|
2016-11-29 12:07:08 +01:00
|
|
|
while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
|
|
|
|
|
2014-08-16 21:31:05 +02:00
|
|
|
end:
|
2014-01-02 23:19:31 +01:00
|
|
|
if (mode == MODE_IMAGE) {
|
2014-08-16 21:31:05 +02:00
|
|
|
if (changed) {
|
|
|
|
img_close(&img, true);
|
|
|
|
load_image(fileidx);
|
2018-02-18 13:16:00 +01:00
|
|
|
} else {
|
2015-08-19 21:29:39 +02:00
|
|
|
open_info();
|
2014-08-16 21:31:05 +02:00
|
|
|
}
|
2014-01-02 23:19:31 +01:00
|
|
|
}
|
2014-11-27 22:37:20 +01:00
|
|
|
free(oldst);
|
2014-07-28 20:36:32 +02:00
|
|
|
reset_cursor();
|
2014-01-02 23:19:31 +01:00
|
|
|
redraw();
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void on_keypress(XKeyEvent *kev)
|
|
|
|
{
|
2021-10-28 22:00:53 +02:00
|
|
|
unsigned int i;
|
2018-01-09 19:37:56 +01:00
|
|
|
unsigned int sh = 0;
|
2014-01-02 13:36:00 +01:00
|
|
|
KeySym ksym, shksym;
|
2018-01-09 19:37:56 +01:00
|
|
|
char dummy, key;
|
2014-07-23 23:41:23 +02:00
|
|
|
bool dirty = false;
|
2011-08-19 15:02:10 +02:00
|
|
|
|
2018-01-09 19:37:56 +01:00
|
|
|
XLookupString(kev, &key, 1, &ksym, NULL);
|
|
|
|
|
2014-01-02 13:36:00 +01:00
|
|
|
if (kev->state & ShiftMask) {
|
|
|
|
kev->state &= ~ShiftMask;
|
2018-01-09 19:37:56 +01:00
|
|
|
XLookupString(kev, &dummy, 1, &shksym, NULL);
|
2014-01-02 13:36:00 +01:00
|
|
|
kev->state |= ShiftMask;
|
2018-01-09 19:37:56 +01:00
|
|
|
if (ksym != shksym)
|
|
|
|
sh = ShiftMask;
|
2014-01-02 13:36:00 +01:00
|
|
|
}
|
2014-01-02 23:19:31 +01:00
|
|
|
if (IsModifierKey(ksym))
|
|
|
|
return;
|
2021-09-24 12:39:50 +02:00
|
|
|
if (extprefix && ksym == keyhandler_abort && MODMASK(kev->state) == 0) {
|
2021-10-07 02:37:34 +02:00
|
|
|
handle_key_handler(false);
|
2014-01-31 13:21:23 +01:00
|
|
|
} else if (extprefix) {
|
|
|
|
run_key_handler(XKeysymToString(ksym), kev->state & ~sh);
|
2021-10-11 05:07:18 +02:00
|
|
|
extprefix = false;
|
2014-01-31 13:21:23 +01:00
|
|
|
} else if (key >= '0' && key <= '9') {
|
2011-10-16 16:08:55 +02:00
|
|
|
/* number prefix for commands */
|
2014-01-31 13:21:23 +01:00
|
|
|
prefix = prefix * 10 + (int) (key - '0');
|
2011-10-16 16:08:55 +02:00
|
|
|
return;
|
2014-01-31 13:21:23 +01:00
|
|
|
} else for (i = 0; i < ARRLEN(keys); i++) {
|
2014-01-02 13:36:00 +01:00
|
|
|
if (keys[i].ksym == ksym &&
|
2014-01-08 22:58:27 +01:00
|
|
|
MODMASK(keys[i].mask | sh) == MODMASK(kev->state) &&
|
Add ability to bind arbitrary functions.
Before all the predated commands where kept in an array and their
indexes were used in bindings. This meant that users couldn't add their
own functions from the config file. Now key/mouse bindings have been
changed to to store the function ptr (wrapped in a cmd_t struct to also
store the mode) directly instead.
General cleanup done in this commit:
Defined `MODE_ALL` instead of using magic number.
For example, suppose one had bindings like:
{ 0, XK_q, g_quit, None },
{ ShitMask, XK_q, {quit_err}, None }
{ ControlMask, XK_q, {quit_err, .mode=MODE_IMAGE}, None }
The existing binding `q` has been left unchanged and is defined the same
way. However, the new hypothetical binding `Shift-q` can be used to call
the custom function quit_err in any mode (default). `Ctrl-q` on the
other hand will be called only on image mode.
Closes #50
2021-09-18 21:27:12 +02:00
|
|
|
keys[i].cmd.func &&
|
|
|
|
(keys[i].cmd.mode == MODE_ALL || keys[i].cmd.mode == mode))
|
2014-01-02 13:36:00 +01:00
|
|
|
{
|
Add ability to bind arbitrary functions.
Before all the predated commands where kept in an array and their
indexes were used in bindings. This meant that users couldn't add their
own functions from the config file. Now key/mouse bindings have been
changed to to store the function ptr (wrapped in a cmd_t struct to also
store the mode) directly instead.
General cleanup done in this commit:
Defined `MODE_ALL` instead of using magic number.
For example, suppose one had bindings like:
{ 0, XK_q, g_quit, None },
{ ShitMask, XK_q, {quit_err}, None }
{ ControlMask, XK_q, {quit_err, .mode=MODE_IMAGE}, None }
The existing binding `q` has been left unchanged and is defined the same
way. However, the new hypothetical binding `Shift-q` can be used to call
the custom function quit_err in any mode (default). `Ctrl-q` on the
other hand will be called only on image mode.
Closes #50
2021-09-18 21:27:12 +02:00
|
|
|
if (keys[i].cmd.func(keys[i].arg))
|
2014-07-23 23:41:23 +02:00
|
|
|
dirty = true;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
}
|
2014-07-23 23:41:23 +02:00
|
|
|
if (dirty)
|
|
|
|
redraw();
|
2014-01-08 22:58:27 +01:00
|
|
|
prefix = 0;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void on_buttonpress(XButtonEvent *bev)
|
|
|
|
{
|
2021-10-28 22:00:53 +02:00
|
|
|
int sel;
|
|
|
|
unsigned int i;
|
2014-07-23 23:41:23 +02:00
|
|
|
bool dirty = false;
|
2014-01-11 22:52:37 +01:00
|
|
|
static Time firstclick;
|
2011-08-19 15:02:10 +02:00
|
|
|
|
|
|
|
if (mode == MODE_IMAGE) {
|
2011-09-11 21:01:24 +02:00
|
|
|
set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
|
2017-10-05 13:49:11 +02:00
|
|
|
reset_cursor();
|
2011-08-19 15:02:10 +02:00
|
|
|
|
2011-09-06 09:11:03 +02:00
|
|
|
for (i = 0; i < ARRLEN(buttons); i++) {
|
2011-08-19 15:02:10 +02:00
|
|
|
if (buttons[i].button == bev->button &&
|
2014-01-08 22:58:27 +01:00
|
|
|
MODMASK(buttons[i].mask) == MODMASK(bev->state) &&
|
Add ability to bind arbitrary functions.
Before all the predated commands where kept in an array and their
indexes were used in bindings. This meant that users couldn't add their
own functions from the config file. Now key/mouse bindings have been
changed to to store the function ptr (wrapped in a cmd_t struct to also
store the mode) directly instead.
General cleanup done in this commit:
Defined `MODE_ALL` instead of using magic number.
For example, suppose one had bindings like:
{ 0, XK_q, g_quit, None },
{ ShitMask, XK_q, {quit_err}, None }
{ ControlMask, XK_q, {quit_err, .mode=MODE_IMAGE}, None }
The existing binding `q` has been left unchanged and is defined the same
way. However, the new hypothetical binding `Shift-q` can be used to call
the custom function quit_err in any mode (default). `Ctrl-q` on the
other hand will be called only on image mode.
Closes #50
2021-09-18 21:27:12 +02:00
|
|
|
buttons[i].cmd.func &&
|
|
|
|
(buttons[i].cmd.mode == MODE_ALL || buttons[i].cmd.mode == mode))
|
2011-08-19 15:02:10 +02:00
|
|
|
{
|
Add ability to bind arbitrary functions.
Before all the predated commands where kept in an array and their
indexes were used in bindings. This meant that users couldn't add their
own functions from the config file. Now key/mouse bindings have been
changed to to store the function ptr (wrapped in a cmd_t struct to also
store the mode) directly instead.
General cleanup done in this commit:
Defined `MODE_ALL` instead of using magic number.
For example, suppose one had bindings like:
{ 0, XK_q, g_quit, None },
{ ShitMask, XK_q, {quit_err}, None }
{ ControlMask, XK_q, {quit_err, .mode=MODE_IMAGE}, None }
The existing binding `q` has been left unchanged and is defined the same
way. However, the new hypothetical binding `Shift-q` can be used to call
the custom function quit_err in any mode (default). `Ctrl-q` on the
other hand will be called only on image mode.
Closes #50
2021-09-18 21:27:12 +02:00
|
|
|
if (buttons[i].cmd.func(buttons[i].arg))
|
2014-07-23 23:41:23 +02:00
|
|
|
dirty = true;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
}
|
2014-07-23 23:41:23 +02:00
|
|
|
if (dirty)
|
|
|
|
redraw();
|
2011-08-19 15:02:10 +02:00
|
|
|
} else {
|
|
|
|
/* thumbnail mode (hard-coded) */
|
|
|
|
switch (bev->button) {
|
|
|
|
case Button1:
|
|
|
|
if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
|
2014-08-16 18:36:17 +02:00
|
|
|
if (sel != fileidx) {
|
|
|
|
tns_highlight(&tns, fileidx, false);
|
2014-01-11 22:52:37 +01:00
|
|
|
tns_highlight(&tns, sel, true);
|
2014-08-16 18:36:17 +02:00
|
|
|
fileidx = sel;
|
2014-01-11 22:52:37 +01:00
|
|
|
firstclick = bev->time;
|
|
|
|
redraw();
|
|
|
|
} else if (bev->time - firstclick <= TO_DOUBLE_CLICK) {
|
2011-08-19 15:02:10 +02:00
|
|
|
mode = MODE_IMAGE;
|
2011-09-11 21:01:24 +02:00
|
|
|
set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
|
2014-08-16 18:36:17 +02:00
|
|
|
load_image(fileidx);
|
2014-01-11 22:52:37 +01:00
|
|
|
redraw();
|
2011-08-19 15:02:10 +02:00
|
|
|
} else {
|
2014-01-11 22:52:37 +01:00
|
|
|
firstclick = bev->time;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2013-11-14 14:37:08 +01:00
|
|
|
case Button3:
|
|
|
|
if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
|
2018-06-09 13:18:18 +02:00
|
|
|
bool on = !(files[sel].flags & FF_MARK);
|
|
|
|
XEvent e;
|
|
|
|
|
2021-10-11 05:07:18 +02:00
|
|
|
while (true) {
|
2018-06-09 13:18:18 +02:00
|
|
|
if (sel >= 0 && mark_image(sel, on))
|
|
|
|
redraw();
|
|
|
|
XMaskEvent(win.env.dpy,
|
|
|
|
ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e);
|
|
|
|
if (e.type == ButtonPress || e.type == ButtonRelease)
|
|
|
|
break;
|
|
|
|
while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e));
|
|
|
|
sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y);
|
|
|
|
}
|
2013-11-14 14:37:08 +01:00
|
|
|
}
|
|
|
|
break;
|
2011-08-19 15:02:10 +02:00
|
|
|
case Button4:
|
|
|
|
case Button5:
|
2011-10-27 16:21:01 +02:00
|
|
|
if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN,
|
|
|
|
(bev->state & ControlMask) != 0))
|
2011-08-19 15:02:10 +02:00
|
|
|
redraw();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-01-08 22:58:27 +01:00
|
|
|
prefix = 0;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
void run(void)
|
|
|
|
{
|
2011-09-02 04:33:44 +02:00
|
|
|
int xfd;
|
2011-08-19 15:02:10 +02:00
|
|
|
fd_set fds;
|
2011-09-02 04:33:44 +02:00
|
|
|
struct timeval timeout;
|
2021-09-29 05:51:13 +02:00
|
|
|
const struct timespec ten_ms = {0, 10000000};
|
2015-01-04 21:19:26 +01:00
|
|
|
bool discard, init_thumb, load_thumb, to_set;
|
2012-08-17 16:54:29 +02:00
|
|
|
XEvent ev, nextev;
|
2011-08-19 15:02:10 +02:00
|
|
|
|
2011-09-29 12:43:36 +02:00
|
|
|
while (true) {
|
2014-09-26 22:45:01 +02:00
|
|
|
to_set = check_timeouts(&timeout);
|
2015-01-04 21:19:26 +01:00
|
|
|
init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
|
2014-09-26 22:45:01 +02:00
|
|
|
load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
|
2011-09-02 04:33:44 +02:00
|
|
|
|
2017-05-17 20:07:32 +02:00
|
|
|
if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
|
|
|
|
arl.fd != -1) && XPending(win.env.dpy) == 0)
|
2013-02-11 23:05:26 +01:00
|
|
|
{
|
2014-09-26 22:45:01 +02:00
|
|
|
if (load_thumb) {
|
|
|
|
set_timeout(redraw, TO_REDRAW_THUMBS, false);
|
2015-01-04 21:19:26 +01:00
|
|
|
if (!tns_load(&tns, tns.loadnext, false, false)) {
|
2014-09-26 22:45:01 +02:00
|
|
|
remove_file(tns.loadnext, false);
|
|
|
|
tns.dirty = true;
|
|
|
|
}
|
|
|
|
if (tns.loadnext >= tns.end)
|
|
|
|
redraw();
|
2015-01-04 21:19:26 +01:00
|
|
|
} else if (init_thumb) {
|
|
|
|
set_timeout(redraw, TO_REDRAW_THUMBS, false);
|
|
|
|
if (!tns_load(&tns, tns.initnext, false, true))
|
|
|
|
remove_file(tns.initnext, false);
|
2014-09-26 22:45:01 +02:00
|
|
|
} else {
|
|
|
|
xfd = ConnectionNumber(win.env.dpy);
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(xfd, &fds);
|
|
|
|
if (info.fd != -1) {
|
|
|
|
FD_SET(info.fd, &fds);
|
|
|
|
xfd = MAX(xfd, info.fd);
|
|
|
|
}
|
2017-05-17 20:07:32 +02:00
|
|
|
if (arl.fd != -1) {
|
|
|
|
FD_SET(arl.fd, &fds);
|
|
|
|
xfd = MAX(xfd, arl.fd);
|
2017-01-26 22:18:32 +01:00
|
|
|
}
|
2014-09-26 22:45:01 +02:00
|
|
|
select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL);
|
|
|
|
if (info.fd != -1 && FD_ISSET(info.fd, &fds))
|
|
|
|
read_info();
|
2017-05-17 20:07:32 +02:00
|
|
|
if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) {
|
2017-05-17 20:16:16 +02:00
|
|
|
if (arl_handle(&arl)) {
|
2017-05-17 20:07:32 +02:00
|
|
|
/* when too fast, imlib2 can't load the image */
|
|
|
|
nanosleep(&ten_ms, NULL);
|
2018-02-18 13:14:00 +01:00
|
|
|
img_close(&img, true);
|
2017-05-17 20:07:32 +02:00
|
|
|
load_image(fileidx);
|
|
|
|
redraw();
|
|
|
|
}
|
|
|
|
}
|
2013-02-11 23:05:26 +01:00
|
|
|
}
|
2014-09-26 22:45:01 +02:00
|
|
|
continue;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
|
2012-08-17 16:54:29 +02:00
|
|
|
do {
|
|
|
|
XNextEvent(win.env.dpy, &ev);
|
2012-10-29 18:25:17 +01:00
|
|
|
discard = false;
|
|
|
|
if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
|
2012-08-17 16:54:29 +02:00
|
|
|
XPeekEvent(win.env.dpy, &nextev);
|
2012-10-29 18:25:17 +01:00
|
|
|
switch (ev.type) {
|
|
|
|
case ConfigureNotify:
|
2017-10-04 18:12:27 +02:00
|
|
|
case MotionNotify:
|
2012-10-29 18:25:17 +01:00
|
|
|
discard = ev.type == nextev.type;
|
|
|
|
break;
|
|
|
|
case KeyPress:
|
|
|
|
discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
|
|
|
|
&& ev.xkey.keycode == nextev.xkey.keycode;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (discard);
|
2012-08-17 16:54:29 +02:00
|
|
|
|
2011-09-29 12:43:36 +02:00
|
|
|
switch (ev.type) {
|
2011-08-19 15:02:10 +02:00
|
|
|
/* handle events */
|
2011-09-29 12:43:36 +02:00
|
|
|
case ButtonPress:
|
|
|
|
on_buttonpress(&ev.xbutton);
|
|
|
|
break;
|
|
|
|
case ClientMessage:
|
2014-01-27 23:16:08 +01:00
|
|
|
if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
|
Add ability to bind arbitrary functions.
Before all the predated commands where kept in an array and their
indexes were used in bindings. This meant that users couldn't add their
own functions from the config file. Now key/mouse bindings have been
changed to to store the function ptr (wrapped in a cmd_t struct to also
store the mode) directly instead.
General cleanup done in this commit:
Defined `MODE_ALL` instead of using magic number.
For example, suppose one had bindings like:
{ 0, XK_q, g_quit, None },
{ ShitMask, XK_q, {quit_err}, None }
{ ControlMask, XK_q, {quit_err, .mode=MODE_IMAGE}, None }
The existing binding `q` has been left unchanged and is defined the same
way. However, the new hypothetical binding `Shift-q` can be used to call
the custom function quit_err in any mode (default). `Ctrl-q` on the
other hand will be called only on image mode.
Closes #50
2021-09-18 21:27:12 +02:00
|
|
|
cg_quit();
|
2021-09-15 10:33:51 +02:00
|
|
|
break;
|
|
|
|
case DestroyNotify:
|
|
|
|
exit(EXIT_FAILURE);
|
2011-09-29 12:43:36 +02:00
|
|
|
break;
|
|
|
|
case ConfigureNotify:
|
|
|
|
if (win_configure(&win, &ev.xconfigure)) {
|
2012-12-20 09:57:36 +01:00
|
|
|
if (mode == MODE_IMAGE) {
|
|
|
|
img.dirty = true;
|
2011-09-29 12:43:36 +02:00
|
|
|
img.checkpan = true;
|
2012-12-20 09:57:36 +01:00
|
|
|
} else {
|
2011-09-29 12:43:36 +02:00
|
|
|
tns.dirty = true;
|
2012-12-20 09:57:36 +01:00
|
|
|
}
|
2019-07-16 19:26:04 +02:00
|
|
|
if (!resized) {
|
2012-03-13 21:58:48 +01:00
|
|
|
redraw();
|
|
|
|
set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
|
|
|
|
resized = true;
|
|
|
|
} else {
|
|
|
|
set_timeout(redraw, TO_REDRAW_RESIZE, false);
|
|
|
|
}
|
2011-09-29 12:43:36 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case KeyPress:
|
2012-10-29 18:25:17 +01:00
|
|
|
on_keypress(&ev.xkey);
|
2011-09-29 12:43:36 +02:00
|
|
|
break;
|
|
|
|
case MotionNotify:
|
|
|
|
if (mode == MODE_IMAGE) {
|
|
|
|
set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
|
2017-10-05 13:49:11 +02:00
|
|
|
reset_cursor();
|
2011-09-29 12:43:36 +02:00
|
|
|
}
|
|
|
|
break;
|
2011-08-19 15:02:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-08 22:05:31 +01:00
|
|
|
int fncmp(const void *a, const void *b)
|
|
|
|
{
|
2011-08-18 00:38:55 +02:00
|
|
|
return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
|
2011-04-08 10:23:42 +02:00
|
|
|
}
|
|
|
|
|
2018-02-18 14:32:55 +01:00
|
|
|
void sigchld(int sig)
|
|
|
|
{
|
|
|
|
while (waitpid(-1, NULL, WNOHANG) > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void setup_signal(int sig, void (*handler)(int sig))
|
|
|
|
{
|
|
|
|
struct sigaction sa;
|
|
|
|
|
|
|
|
sa.sa_handler = handler;
|
|
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
|
|
|
if (sigaction(sig, &sa, 0) == -1)
|
|
|
|
error(EXIT_FAILURE, errno, "signal %d", sig);
|
|
|
|
}
|
|
|
|
|
2021-10-11 05:07:18 +02:00
|
|
|
int main(int argc, char *argv[])
|
2013-02-08 22:05:31 +01:00
|
|
|
{
|
2011-09-08 20:54:24 +02:00
|
|
|
int i, start;
|
2011-05-29 11:45:58 +02:00
|
|
|
size_t n;
|
2011-09-08 20:54:24 +02:00
|
|
|
ssize_t len;
|
2011-08-18 00:38:55 +02:00
|
|
|
char *filename;
|
2014-01-11 22:47:41 +01:00
|
|
|
const char *homedir, *dsuffix = "";
|
2011-02-02 09:01:05 +01:00
|
|
|
struct stat fstats;
|
2011-04-08 10:23:42 +02:00
|
|
|
r_dir_t dir;
|
2011-01-20 16:24:48 +01:00
|
|
|
|
2018-02-18 14:32:55 +01:00
|
|
|
setup_signal(SIGCHLD, sigchld);
|
|
|
|
setup_signal(SIGPIPE, SIG_IGN);
|
2014-12-01 14:03:49 +01:00
|
|
|
|
2017-10-24 21:43:36 +02:00
|
|
|
setlocale(LC_COLLATE, "");
|
|
|
|
|
2011-01-19 18:16:44 +01:00
|
|
|
parse_options(argc, argv);
|
2011-01-19 14:07:45 +01:00
|
|
|
|
2011-04-08 14:44:00 +02:00
|
|
|
if (options->clean_cache) {
|
2014-09-30 21:54:17 +02:00
|
|
|
tns_init(&tns, NULL, NULL, NULL, NULL);
|
2021-10-28 22:00:53 +02:00
|
|
|
tns_clean_cache();
|
2011-09-26 15:40:07 +02:00
|
|
|
exit(EXIT_SUCCESS);
|
2011-04-08 14:44:00 +02:00
|
|
|
}
|
|
|
|
|
2013-03-19 21:11:29 +01:00
|
|
|
if (options->filecnt == 0 && !options->from_stdin) {
|
2011-01-19 14:07:45 +01:00
|
|
|
print_usage();
|
2011-09-26 15:40:07 +02:00
|
|
|
exit(EXIT_FAILURE);
|
2011-01-19 14:07:45 +01:00
|
|
|
}
|
|
|
|
|
2011-02-14 17:51:04 +01:00
|
|
|
if (options->recursive || options->from_stdin)
|
2015-10-28 22:23:28 +01:00
|
|
|
filecnt = 1024;
|
2011-02-02 09:01:05 +01:00
|
|
|
else
|
|
|
|
filecnt = options->filecnt;
|
|
|
|
|
2015-10-28 22:29:01 +01:00
|
|
|
files = emalloc(filecnt * sizeof(*files));
|
2015-01-04 15:38:49 +01:00
|
|
|
memset(files, 0, filecnt * sizeof(*files));
|
2011-01-19 14:07:45 +01:00
|
|
|
fileidx = 0;
|
2011-01-20 16:24:48 +01:00
|
|
|
|
2011-02-14 17:51:04 +01:00
|
|
|
if (options->from_stdin) {
|
2015-10-28 21:50:17 +01:00
|
|
|
n = 0;
|
2011-08-18 00:38:55 +02:00
|
|
|
filename = NULL;
|
2015-10-28 21:50:17 +01:00
|
|
|
while ((len = getline(&filename, &n, stdin)) > 0) {
|
2011-05-29 11:45:58 +02:00
|
|
|
if (filename[len-1] == '\n')
|
|
|
|
filename[len-1] = '\0';
|
2014-10-24 11:14:01 +02:00
|
|
|
check_add_file(filename, true);
|
2011-02-14 17:51:04 +01:00
|
|
|
}
|
2014-08-21 20:51:02 +02:00
|
|
|
free(filename);
|
2013-03-19 21:11:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < options->filecnt; i++) {
|
|
|
|
filename = options->filenames[i];
|
2011-04-08 10:23:42 +02:00
|
|
|
|
2013-03-19 21:11:29 +01:00
|
|
|
if (stat(filename, &fstats) < 0) {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, errno, "%s", filename);
|
2013-03-19 21:11:29 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!S_ISDIR(fstats.st_mode)) {
|
2014-10-24 11:14:01 +02:00
|
|
|
check_add_file(filename, true);
|
2013-03-19 21:11:29 +01:00
|
|
|
} else {
|
2016-09-28 18:59:15 +02:00
|
|
|
if (r_opendir(&dir, filename, options->recursive) < 0) {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, errno, "%s", filename);
|
2013-03-19 21:11:29 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
start = fileidx;
|
2018-07-05 03:39:27 +02:00
|
|
|
while ((filename = r_readdir(&dir, true)) != NULL) {
|
2014-10-24 11:14:01 +02:00
|
|
|
check_add_file(filename, false);
|
2013-03-19 21:11:29 +01:00
|
|
|
free((void*) filename);
|
2011-02-14 17:51:04 +01:00
|
|
|
}
|
2013-03-19 21:11:29 +01:00
|
|
|
r_closedir(&dir);
|
|
|
|
if (fileidx - start > 1)
|
|
|
|
qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
|
2011-02-02 09:01:05 +01:00
|
|
|
}
|
2011-01-20 16:24:48 +01:00
|
|
|
}
|
|
|
|
|
2015-10-28 23:03:37 +01:00
|
|
|
if (fileidx == 0)
|
|
|
|
error(EXIT_FAILURE, 0, "No valid image file given, aborting");
|
2011-01-19 14:07:45 +01:00
|
|
|
|
2011-05-25 09:23:23 +02:00
|
|
|
filecnt = fileidx;
|
2011-06-28 13:45:57 +02:00
|
|
|
fileidx = options->startnum < filecnt ? options->startnum : 0;
|
2011-05-25 09:23:23 +02:00
|
|
|
|
2011-04-11 16:58:38 +02:00
|
|
|
win_init(&win);
|
2011-01-21 12:57:35 +01:00
|
|
|
img_init(&img, &win);
|
2017-05-17 20:07:32 +02:00
|
|
|
arl_init(&arl);
|
2011-01-17 16:41:50 +01:00
|
|
|
|
2014-01-11 22:47:41 +01:00
|
|
|
if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
|
|
|
|
homedir = getenv("HOME");
|
|
|
|
dsuffix = "/.config";
|
|
|
|
}
|
|
|
|
if (homedir != NULL) {
|
2015-10-28 23:03:37 +01:00
|
|
|
extcmd_t *cmd[] = { &info.f, &keyhandler.f };
|
2014-01-11 22:47:41 +01:00
|
|
|
const char *name[] = { "image-info", "key-handler" };
|
2021-09-16 11:32:59 +02:00
|
|
|
const char *s = "/nsxiv/exec/";
|
2014-01-11 22:47:41 +01:00
|
|
|
|
|
|
|
for (i = 0; i < ARRLEN(cmd); i++) {
|
2021-10-28 22:00:53 +02:00
|
|
|
n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1;
|
2021-10-23 23:57:22 +02:00
|
|
|
cmd[i]->cmd = emalloc(n);
|
2021-09-16 11:32:59 +02:00
|
|
|
snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]);
|
2015-10-28 23:03:37 +01:00
|
|
|
if (access(cmd[i]->cmd, X_OK) != 0)
|
|
|
|
cmd[i]->err = errno;
|
2013-01-07 14:30:42 +01:00
|
|
|
}
|
2014-01-11 22:47:41 +01:00
|
|
|
} else {
|
2015-10-28 23:03:37 +01:00
|
|
|
error(0, 0, "Exec directory not found");
|
2013-01-07 14:30:42 +01:00
|
|
|
}
|
2013-02-11 23:05:26 +01:00
|
|
|
info.fd = -1;
|
2013-01-07 14:30:42 +01:00
|
|
|
|
2011-09-11 21:01:24 +02:00
|
|
|
if (options->thumb_mode) {
|
2011-08-19 13:09:22 +02:00
|
|
|
mode = MODE_THUMB;
|
2014-09-30 21:54:17 +02:00
|
|
|
tns_init(&tns, files, &filecnt, &fileidx, &win);
|
2015-01-04 21:19:26 +01:00
|
|
|
while (!tns_load(&tns, fileidx, false, false))
|
|
|
|
remove_file(fileidx, false);
|
2011-02-16 17:09:46 +01:00
|
|
|
} else {
|
2011-08-19 13:09:22 +02:00
|
|
|
mode = MODE_IMAGE;
|
2011-02-21 14:59:29 +01:00
|
|
|
tns.thumbs = NULL;
|
2011-02-27 13:29:24 +01:00
|
|
|
load_image(fileidx);
|
2011-02-16 17:09:46 +01:00
|
|
|
}
|
2011-04-11 16:58:38 +02:00
|
|
|
win_open(&win);
|
2021-09-12 20:26:07 +02:00
|
|
|
win_set_title(&win, files[fileidx].path);
|
2014-07-28 20:36:32 +02:00
|
|
|
win_set_cursor(&win, CURSOR_WATCH);
|
2011-09-29 12:43:36 +02:00
|
|
|
|
2015-10-28 23:03:37 +01:00
|
|
|
atexit(cleanup);
|
|
|
|
|
2014-09-26 20:45:15 +02:00
|
|
|
set_timeout(redraw, 25, false);
|
|
|
|
|
2011-01-19 14:07:45 +01:00
|
|
|
run();
|
2011-01-17 16:41:50 +01:00
|
|
|
|
2011-01-17 14:57:59 +01:00
|
|
|
return 0;
|
|
|
|
}
|