Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Arun Prakash Jana 2016-08-20 19:44:31 +05:30
commit bd882d5561
10 changed files with 1292 additions and 0 deletions

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
Copyright (c) 2014-2016 Lazaros Koromilas <lostd@2f30.org>
Copyright (c) 2014-2016 Dimitris Papastamos <sin@2f30.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

45
Makefile Normal file
View file

@ -0,0 +1,45 @@
VERSION = 0.5
PREFIX = /usr/local
MANPREFIX = $(PREFIX)/man
#CPPFLAGS = -DDEBUG
#CFLAGS = -g
LDLIBS = -lcurses
DISTFILES = noice.c strlcat.c strlcpy.c util.h config.def.h\
noice.1 Makefile README LICENSE
OBJ = noice.o strlcat.o strlcpy.o
BIN = noice
all: $(BIN)
$(BIN): $(OBJ)
$(CC) $(CFLAGS) -o $@ $(OBJ) $(LDLIBS)
noice.o: util.h config.h
strlcat.o: util.h
strlcpy.o: util.h
config.h:
cp config.def.h $@
install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
cp -f $(BIN).1 $(DESTDIR)$(MANPREFIX)/man1
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
rm -f $(DESTDIR)$(MANPREFIX)/man1/$(BIN).1
dist:
mkdir -p noice-$(VERSION)
cp $(DISTFILES) noice-$(VERSION)
tar -cf noice-$(VERSION).tar noice-$(VERSION)
gzip noice-$(VERSION).tar
rm -rf noice-$(VERSION)
clean:
rm -f $(BIN) $(OBJ) noice-$(VERSION).tar.gz

62
README Normal file
View file

@ -0,0 +1,62 @@
__
___ ___ /\_\ ___ __
/' _ `\ / __`\/\ \ /'___\ /'__`\
/\ \/\ \/\ \L\ \ \ \/\ \__//\ __/
\ \_\ \_\ \____/\ \_\ \____\ \____\
\/_/\/_/\/___/ \/_/\/____/\/____/
-- by lostd and sin
=======================================================
What is it?
===========
noice is a small curses-based file browser.
It was first developed to be used with a TV remote control for a media
center solution.
Getting started
===============
Get the latest version from the git-repository; build and install it. Run
noice in a directory to display its content in the form of a list, where
each line is a file or directory. The currently selected item will be
preceded with a " > " by default.
For more information refer to the manpage.
Building
========
To build noice you need a curses implementation available. In most
cases you just do:
make
It is known to work on OpenBSD, NetBSD, FreeBSD, DragonFly BSD, Linux, OSX,
IRIX 6.5, Haiku and Solaris 9. Some notes for building on certain systems
follow.
* IRIX 6.5:
Tested with gcc from http://freeware.sgi.com/.
make CC="gcc" LDLIBS="-lgen -lcurses"
* Haiku:
make LDLIBS="-lncurses"
* Solaris 9:
Tested with gcc from http://www.opencsw.org/.
export PATH=/usr/ccs/bin:/opt/csw/bin:$PATH
make CC="gcc"
Contact
=======
To report bugs and/or submit patches, you can reach us through
the freenode IRC network at #2f30.

71
config.def.h Normal file
View file

@ -0,0 +1,71 @@
/* See LICENSE file for copyright and license details. */
#define CWD "cwd: "
#define CURSR " > "
#define EMPTY " "
int mtimeorder = 0; /* Set to 1 to sort by time modified */
int idletimeout = 0; /* Screensaver timeout in seconds, 0 to disable */
char *idlecmd = "rain"; /* The screensaver program */
struct assoc assocs[] = {
{ "\\.(avi|mp4|mkv|mp3|ogg|flac|mov)$", "mplayer" },
{ "\\.(png|jpg|gif)$", "feh" },
{ "\\.(html|svg)$", "firefox" },
{ "\\.pdf$", "mupdf" },
{ "\\.sh$", "sh" },
{ ".", "less" },
};
struct key bindings[] = {
/* Quit */
{ 'q', SEL_QUIT },
/* Back */
{ KEY_BACKSPACE, SEL_BACK },
{ KEY_LEFT, SEL_BACK },
{ 'h', SEL_BACK },
{ CONTROL('H'), SEL_BACK },
/* Inside */
{ KEY_ENTER, SEL_GOIN },
{ '\r', SEL_GOIN },
{ KEY_RIGHT, SEL_GOIN },
{ 'l', SEL_GOIN },
/* Filter */
{ '/', SEL_FLTR },
{ '&', SEL_FLTR },
/* Next */
{ 'j', SEL_NEXT },
{ KEY_DOWN, SEL_NEXT },
{ CONTROL('N'), SEL_NEXT },
/* Previous */
{ 'k', SEL_PREV },
{ KEY_UP, SEL_PREV },
{ CONTROL('P'), SEL_PREV },
/* Page down */
{ KEY_NPAGE, SEL_PGDN },
{ CONTROL('D'), SEL_PGDN },
/* Page up */
{ KEY_PPAGE, SEL_PGUP },
{ CONTROL('U'), SEL_PGUP },
/* Home */
{ KEY_HOME, SEL_HOME },
{ CONTROL('A'), SEL_HOME },
{ '^', SEL_HOME },
/* End */
{ KEY_END, SEL_END },
{ CONTROL('E'), SEL_END },
{ '$', SEL_END },
/* Change dir */
{ 'c', SEL_CD },
{ '~', SEL_CDHOME },
/* Toggle hide .dot files */
{ '.', SEL_TOGGLEDOT },
/* Toggle sort by time */
{ 't', SEL_MTIME },
{ CONTROL('L'), SEL_REDRAW },
/* Run command */
{ 'z', SEL_RUN, "top" },
{ '!', SEL_RUN, "sh", "SHELL" },
/* Run command with argument */
{ 'e', SEL_RUNARG, "vi", "EDITOR" },
{ 'p', SEL_RUNARG, "less", "PAGER" },
};

32
mktest.sh Normal file
View file

@ -0,0 +1,32 @@
#!/bin/sh
# Create test files and directories
test -e test && {
echo "Remove test and try again"
exit 1
}
mkdir test && cd test
echo 'It works!' > normal.txt
echo 'Με δουλέβει;' > 'κοινό.txt'
ln -s normal.txt ln-normal.txt
ln -s normal.txt ln-normal
mkdir normal-dir
ln -s normal-dir ln-normal-dir
ln -s nowhere ln-nowhere
mkfifo mk-fifo
touch no-access && chmod 000 no-access
mkdir no-access-dir && chmod 000 no-access-dir
ln -s ../normal.txt normal-dir/ln-normal.txt
ln -s ../normal.txt normal-dir/ln-normal
echo 'int main(void) { *((char *)0) = 0; }' > ill.c
make ill > /dev/null
echo 'test/ill' > ill.sh
mkdir empty-dir
mkdir cage
echo 'chmod 000 test/cage' > cage/lock.sh
echo 'chmod 755 test/cage' > cage-unlock.sh
mkdir cage/lion
echo 'chmod 000 test/cage' > cage/lion/lock.sh

124
noice.1 Normal file
View file

@ -0,0 +1,124 @@
.Dd February 25, 2016
.Dt NOICE 1
.Os
.Sh NAME
.Nm noice
.Nd small file browser
.Sh SYNOPSIS
.Nm noice
.Op Ar dir
.Sh DESCRIPTION
.Nm
is a simple and efficient file browser that gets out of your way
as much as possible. It was initially implemented to be controlled
with a TV remote control.
.Pp
.Nm
defaults to the current directory if
.Ar dir
is not specified. As an extra feature, if
.Ar dir
is a relative path,
.Nm
will not go back beyond the first component of the path using standard
navigation key presses.
.Pp
.Nm
supports both vi-like and emacs-like key bindings in the default
configuration. The default key bindings are described below;
their functionality is described in more detail later.
.Pp
.Bl -tag -width "l, [Right], [Return] or C-mXXXX" -offset indent -compact
.It Ic k, [Up] or C-p
Move to previous entry.
.It Ic j, [Down] or C-n
Move to next entry.
.It Ic [Pgup] or C-u
Scroll up half a page.
.It Ic [Pgdown] or C-d
Scroll down half a page.
.It Ic [Home], ^ or C-a
Move to the first entry.
.It Ic [End], $ or C-e
Move to the last entry.
.It Ic l, [Right], [Return] or C-m
Open file or enter directory.
.It Ic h, C-h, [Left] or [Backspace]
Back up one directory level.
.It Ic / or &
Change filter (see below for more information).
.It Ic c
Change into the given directory.
.It Ic ~
Change to the HOME directory.
.It Ic \&.
Toggle hide .dot files.
.It Ic t
Toggle sort by time modified.
.It Ic C-l
Force a redraw.
.It Ic \&!
Spawn a shell in current directory.
.It Ic z
Run the system top utility.
.It Ic e
Open selected entry with the vi editor.
.It Ic p
Open selected entry with the less pager.
.It Ic q
Quit.
.El
.Pp
Backing up one directory level will set the cursor position at the
directory you came out of.
.Sh CONFIGURATION
.Nm
is configured by modifying
.Pa config.h
and recompiling the code.
.Pp
The file associations are specified by regexes
matching on the currently selected filename. If a match is found the associated
program is executed with the filename passed in as the argument. If no match
is found the program
.Xr less 1
is invoked. This is useful for editing text files
as one can use the 'v' command in
.Xr less 1 to edit the file using the EDITOR environment variable.
.Pp
See the examples section below for more information.
.Sh FILTERS
Filters allow you to use regexes to display only the matched
entries in the current directory view. This effectively allows
searching through the directory tree for a particular entry.
.Pp
Filters do not stack on top of each other. They are applied anew
every time.
.Pp
To reset the filter you can input an empty filter expression.
.Pp
If
.Nm
is invoked as root the default filter will also match hidden
files.
.Sh ENVIRONMENT
The SHELL, EDITOR and PAGER environment variables take precedence
when dealing with the !, e and p commands respectively.
.Sh EXAMPLES
The following example shows one possible configuration for
file associations which is also the default:
.Bd -literal
struct assoc assocs[] = {
{ "\\.(avi|mp4|mkv|mp3|ogg|flac|mov)$", "mplayer" },
{ "\\.(png|jpg|gif)$", "feh" },
{ "\\.(html|svg)$", "firefox" },
{ "\\.pdf$", "mupdf" },
{ "\\.sh$", "sh" },
{ ".", "less" },
};
.Ed
.Sh KNOWN ISSUES
If you are using urxvt you might have to set backspacekey to DEC.
.Sh AUTHORS
.An Lazaros Koromilas Aq Mt lostd@2f30.org ,
.An Dimitris Papastamos Aq Mt sin@2f30.org .

824
noice.c Normal file
View file

@ -0,0 +1,824 @@
/* See LICENSE file for copyright and license details. */
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <curses.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <locale.h>
#include <regex.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "util.h"
#ifdef DEBUG
#define DEBUG_FD 8
#define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
#define DPRINTF_U(x) dprintf(DEBUG_FD, #x "=%u\n", x)
#define DPRINTF_S(x) dprintf(DEBUG_FD, #x "=%s\n", x)
#define DPRINTF_P(x) dprintf(DEBUG_FD, #x "=0x%p\n", x)
#else
#define DPRINTF_D(x)
#define DPRINTF_U(x)
#define DPRINTF_S(x)
#define DPRINTF_P(x)
#endif /* DEBUG */
#define LEN(x) (sizeof(x) / sizeof(*(x)))
#undef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define ISODD(x) ((x) & 1)
#define CONTROL(c) ((c) ^ 0x40)
struct assoc {
char *regex; /* Regex to match on filename */
char *bin; /* Program */
};
/* Supported actions */
enum action {
SEL_QUIT = 1,
SEL_BACK,
SEL_GOIN,
SEL_FLTR,
SEL_NEXT,
SEL_PREV,
SEL_PGDN,
SEL_PGUP,
SEL_HOME,
SEL_END,
SEL_CD,
SEL_CDHOME,
SEL_TOGGLEDOT,
SEL_MTIME,
SEL_REDRAW,
SEL_RUN,
SEL_RUNARG,
};
struct key {
int sym; /* Key pressed */
enum action act; /* Action */
char *run; /* Program to run */
char *env; /* Environment variable to run */
};
#include "config.h"
struct entry {
char name[PATH_MAX];
mode_t mode;
time_t t;
};
/* Global context */
struct entry *dents;
int ndents, cur;
int idle;
/*
* Layout:
* .---------
* | cwd: /mnt/path
* |
* | file0
* | file1
* | > file2
* | file3
* | file4
* ...
* | filen
* |
* | Permission denied
* '------
*/
void printmsg(char *);
void printwarn(void);
void printerr(int, char *);
#undef dprintf
int
dprintf(int fd, const char *fmt, ...)
{
char buf[BUFSIZ];
int r;
va_list ap;
va_start(ap, fmt);
r = vsnprintf(buf, sizeof(buf), fmt, ap);
if (r > 0)
write(fd, buf, r);
va_end(ap);
return r;
}
void *
xmalloc(size_t size)
{
void *p;
p = malloc(size);
if (p == NULL)
printerr(1, "malloc");
return p;
}
void *
xrealloc(void *p, size_t size)
{
p = realloc(p, size);
if (p == NULL)
printerr(1, "realloc");
return p;
}
char *
xstrdup(const char *s)
{
char *p;
p = strdup(s);
if (p == NULL)
printerr(1, "strdup");
return p;
}
/* Some implementations of dirname(3) may modify `path' and some
* return a pointer inside `path'. */
char *
xdirname(const char *path)
{
static char out[PATH_MAX];
char tmp[PATH_MAX], *p;
strlcpy(tmp, path, sizeof(tmp));
p = dirname(tmp);
if (p == NULL)
printerr(1, "dirname");
strlcpy(out, p, sizeof(out));
return out;
}
void
spawn(char *file, char *arg, char *dir)
{
pid_t pid;
int status;
pid = fork();
if (pid == 0) {
if (dir != NULL)
chdir(dir);
execlp(file, file, arg, NULL);
_exit(1);
} else {
/* Ignore interruptions */
while (waitpid(pid, &status, 0) == -1)
DPRINTF_D(status);
DPRINTF_D(pid);
}
}
char *
xgetenv(char *name, char *fallback)
{
char *value;
if (name == NULL)
return fallback;
value = getenv(name);
return value && value[0] ? value : fallback;
}
char *
openwith(char *file)
{
regex_t regex;
char *bin = NULL;
int i;
for (i = 0; i < LEN(assocs); i++) {
if (regcomp(&regex, assocs[i].regex,
REG_NOSUB | REG_EXTENDED | REG_ICASE) != 0)
continue;
if (regexec(&regex, file, 0, NULL, 0) == 0) {
bin = assocs[i].bin;
break;
}
}
DPRINTF_S(bin);
return bin;
}
int
setfilter(regex_t *regex, char *filter)
{
char errbuf[LINE_MAX];
size_t len;
int r;
r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED | REG_ICASE);
if (r != 0) {
len = COLS;
if (len > sizeof(errbuf))
len = sizeof(errbuf);
regerror(r, regex, errbuf, len);
printmsg(errbuf);
}
return r;
}
int
visible(regex_t *regex, char *file)
{
return regexec(regex, file, 0, NULL, 0) == 0;
}
int
entrycmp(const void *va, const void *vb)
{
const struct entry *a = va, *b = vb;
if (mtimeorder)
return b->t - a->t;
return strcmp(a->name, b->name);
}
void
initcurses(void)
{
char *term;
if (initscr() == NULL) {
term = getenv("TERM");
if (term != NULL)
fprintf(stderr, "error opening terminal: %s\n", term);
else
fprintf(stderr, "failed to initialize curses\n");
exit(1);
}
cbreak();
noecho();
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
curs_set(FALSE); /* Hide cursor */
timeout(1000); /* One second */
}
void
exitcurses(void)
{
endwin(); /* Restore terminal */
}
/* Messages show up at the bottom */
void
printmsg(char *msg)
{
move(LINES - 1, 0);
printw("%s\n", msg);
}
/* Display warning as a message */
void
printwarn(void)
{
printmsg(strerror(errno));
}
/* Kill curses and display error before exiting */
void
printerr(int ret, char *prefix)
{
exitcurses();
fprintf(stderr, "%s: %s\n", prefix, strerror(errno));
exit(ret);
}
/* Clear the last line */
void
clearprompt(void)
{
printmsg("");
}
/* Print prompt on the last line */
void
printprompt(char *str)
{
clearprompt();
printw(str);
}
/* Returns SEL_* if key is bound and 0 otherwise.
* Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */
int
nextsel(char **run, char **env)
{
int c, i;
c = getch();
if (c == -1)
idle++;
else
idle = 0;
for (i = 0; i < LEN(bindings); i++)
if (c == bindings[i].sym) {
*run = bindings[i].run;
*env = bindings[i].env;
return bindings[i].act;
}
return 0;
}
char *
readln(void)
{
static char ln[LINE_MAX];
timeout(-1);
echo();
curs_set(TRUE);
memset(ln, 0, sizeof(ln));
wgetnstr(stdscr, ln, sizeof(ln) - 1);
noecho();
curs_set(FALSE);
timeout(1000);
return ln[0] ? ln : NULL;
}
int
canopendir(char *path)
{
DIR *dirp;
dirp = opendir(path);
if (dirp == NULL)
return 0;
closedir(dirp);
return 1;
}
char *
mkpath(char *dir, char *name, char *out, size_t n)
{
/* Handle absolute path */
if (name[0] == '/') {
strlcpy(out, name, n);
} else {
/* Handle root case */
if (strcmp(dir, "/") == 0) {
strlcpy(out, "/", n);
strlcat(out, name, n);
} else {
strlcpy(out, dir, n);
strlcat(out, "/", n);
strlcat(out, name, n);
}
}
return out;
}
void
printent(struct entry *ent, int active)
{
char name[PATH_MAX];
unsigned int maxlen = COLS - strlen(CURSR) - 1;
char cm = 0;
/* Copy name locally */
strlcpy(name, ent->name, sizeof(name));
if (S_ISDIR(ent->mode)) {
cm = '/';
maxlen--;
} else if (S_ISLNK(ent->mode)) {
cm = '@';
maxlen--;
} else if (S_ISSOCK(ent->mode)) {
cm = '=';
maxlen--;
} else if (S_ISFIFO(ent->mode)) {
cm = '|';
maxlen--;
} else if (ent->mode & S_IXUSR) {
cm = '*';
maxlen--;
}
/* No text wrapping in entries */
if (strlen(name) > maxlen)
name[maxlen] = '\0';
if (cm == 0)
printw("%s%s\n", active ? CURSR : EMPTY, name);
else
printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
}
int
dentfill(char *path, struct entry **dents,
int (*filter)(regex_t *, char *), regex_t *re)
{
char newpath[PATH_MAX];
DIR *dirp;
struct dirent *dp;
struct stat sb;
int r, n = 0;
dirp = opendir(path);
if (dirp == NULL)
return 0;
while ((dp = readdir(dirp)) != NULL) {
/* Skip self and parent */
if (strcmp(dp->d_name, ".") == 0 ||
strcmp(dp->d_name, "..") == 0)
continue;
if (filter(re, dp->d_name) == 0)
continue;
*dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
strlcpy((*dents)[n].name, dp->d_name, sizeof((*dents)[n].name));
/* Get mode flags */
mkpath(path, dp->d_name, newpath, sizeof(newpath));
r = lstat(newpath, &sb);
if (r == -1)
printerr(1, "lstat");
(*dents)[n].mode = sb.st_mode;
(*dents)[n].t = sb.st_mtime;
n++;
}
/* Should never be null */
r = closedir(dirp);
if (r == -1)
printerr(1, "closedir");
return n;
}
void
dentfree(struct entry *dents)
{
free(dents);
}
/* Return the position of the matching entry or 0 otherwise */
int
dentfind(struct entry *dents, int n, char *cwd, char *path)
{
char tmp[PATH_MAX];
int i;
if (path == NULL)
return 0;
for (i = 0; i < n; i++) {
mkpath(cwd, dents[i].name, tmp, sizeof(tmp));
DPRINTF_S(path);
DPRINTF_S(tmp);
if (strcmp(tmp, path) == 0)
return i;
}
return 0;
}
int
populate(char *path, char *oldpath, char *fltr)
{
regex_t re;
int r;
/* Can fail when permissions change while browsing */
if (canopendir(path) == 0)
return -1;
/* Search filter */
r = setfilter(&re, fltr);
if (r != 0)
return -1;
dentfree(dents);
ndents = 0;
dents = NULL;
ndents = dentfill(path, &dents, visible, &re);
qsort(dents, ndents, sizeof(*dents), entrycmp);
/* Find cur from history */
cur = dentfind(dents, ndents, path, oldpath);
return 0;
}
void
redraw(char *path)
{
char cwd[PATH_MAX], cwdresolved[PATH_MAX];
size_t ncols;
int nlines, odd;
int i;
nlines = MIN(LINES - 4, ndents);
/* Clean screen */
erase();
/* Strip trailing slashes */
for (i = strlen(path) - 1; i > 0; i--)
if (path[i] == '/')
path[i] = '\0';
else
break;
DPRINTF_D(cur);
DPRINTF_S(path);
/* No text wrapping in cwd line */
ncols = COLS;
if (ncols > PATH_MAX)
ncols = PATH_MAX;
strlcpy(cwd, path, ncols);
cwd[ncols - strlen(CWD) - 1] = '\0';
realpath(cwd, cwdresolved);
printw(CWD "%s\n\n", cwdresolved);
/* Print listing */
odd = ISODD(nlines);
if (cur < nlines / 2) {
for (i = 0; i < nlines; i++)
printent(&dents[i], i == cur);
} else if (cur >= ndents - nlines / 2) {
for (i = ndents - nlines; i < ndents; i++)
printent(&dents[i], i == cur);
} else {
for (i = cur - nlines / 2;
i < cur + nlines / 2 + odd; i++)
printent(&dents[i], i == cur);
}
}
void
browse(char *ipath, char *ifilter)
{
char path[PATH_MAX], oldpath[PATH_MAX], newpath[PATH_MAX];
char fltr[LINE_MAX];
char *bin, *dir, *tmp, *run, *env;
struct stat sb;
regex_t re;
int r, fd;
strlcpy(path, ipath, sizeof(path));
strlcpy(fltr, ifilter, sizeof(fltr));
oldpath[0] = '\0';
begin:
r = populate(path, oldpath, fltr);
if (r == -1) {
printwarn();
goto nochange;
}
for (;;) {
redraw(path);
nochange:
switch (nextsel(&run, &env)) {
case SEL_QUIT:
dentfree(dents);
return;
case SEL_BACK:
/* There is no going back */
if (strcmp(path, "/") == 0 ||
strcmp(path, ".") == 0 ||
strchr(path, '/') == NULL)
goto nochange;
dir = xdirname(path);
if (canopendir(dir) == 0) {
printwarn();
goto nochange;
}
/* Save history */
strlcpy(oldpath, path, sizeof(oldpath));
strlcpy(path, dir, sizeof(path));
/* Reset filter */
strlcpy(fltr, ifilter, sizeof(fltr));
goto begin;
case SEL_GOIN:
/* Cannot descend in empty directories */
if (ndents == 0)
goto nochange;
mkpath(path, dents[cur].name, newpath, sizeof(newpath));
DPRINTF_S(newpath);
/* Get path info */
fd = open(newpath, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
printwarn();
goto nochange;
}
r = fstat(fd, &sb);
if (r == -1) {
printwarn();
close(fd);
goto nochange;
}
close(fd);
DPRINTF_U(sb.st_mode);
switch (sb.st_mode & S_IFMT) {
case S_IFDIR:
if (canopendir(newpath) == 0) {
printwarn();
goto nochange;
}
strlcpy(path, newpath, sizeof(path));
/* Reset filter */
strlcpy(fltr, ifilter, sizeof(fltr));
goto begin;
case S_IFREG:
bin = openwith(newpath);
if (bin == NULL) {
printmsg("No association");
goto nochange;
}
exitcurses();
spawn(bin, newpath, NULL);
initcurses();
continue;
default:
printmsg("Unsupported file");
goto nochange;
}
case SEL_FLTR:
/* Read filter */
printprompt("filter: ");
tmp = readln();
if (tmp == NULL)
tmp = ifilter;
/* Check and report regex errors */
r = setfilter(&re, tmp);
if (r != 0)
goto nochange;
strlcpy(fltr, tmp, sizeof(fltr));
DPRINTF_S(fltr);
/* Save current */
if (ndents > 0)
mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
goto begin;
case SEL_NEXT:
if (cur < ndents - 1)
cur++;
break;
case SEL_PREV:
if (cur > 0)
cur--;
break;
case SEL_PGDN:
if (cur < ndents - 1)
cur += MIN((LINES - 4) / 2, ndents - 1 - cur);
break;
case SEL_PGUP:
if (cur > 0)
cur -= MIN((LINES - 4) / 2, cur);
break;
case SEL_HOME:
cur = 0;
break;
case SEL_END:
cur = ndents - 1;
break;
case SEL_CD:
/* Read target dir */
printprompt("chdir: ");
tmp = readln();
if (tmp == NULL) {
clearprompt();
goto nochange;
}
mkpath(path, tmp, newpath, sizeof(newpath));
if (canopendir(newpath) == 0) {
printwarn();
goto nochange;
}
strlcpy(path, newpath, sizeof(path));
/* Reset filter */
strlcpy(fltr, ifilter, sizeof(fltr))
DPRINTF_S(path);
goto begin;
case SEL_CDHOME:
tmp = getenv("HOME");
if (tmp == NULL) {
clearprompt();
goto nochange;
}
if (canopendir(tmp) == 0) {
printwarn();
goto nochange;
}
strlcpy(path, tmp, sizeof(path));
/* Reset filter */
strlcpy(fltr, ifilter, sizeof(fltr));
DPRINTF_S(path);
goto begin;
case SEL_TOGGLEDOT:
if (strcmp(fltr, ifilter) != 0)
strlcpy(fltr, ifilter, sizeof(fltr));
else
strlcpy(fltr, ".", sizeof(fltr));
goto begin;
case SEL_MTIME:
mtimeorder = !mtimeorder;
/* Save current */
if (ndents > 0)
mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
goto begin;
case SEL_REDRAW:
/* Save current */
if (ndents > 0)
mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
goto begin;
case SEL_RUN:
run = xgetenv(env, run);
exitcurses();
spawn(run, NULL, path);
initcurses();
break;
case SEL_RUNARG:
run = xgetenv(env, run);
exitcurses();
spawn(run, dents[cur].name, path);
initcurses();
break;
}
/* Screensaver */
if (idletimeout != 0 && idle == idletimeout) {
idle = 0;
exitcurses();
spawn(idlecmd, NULL, NULL);
initcurses();
}
}
}
void
usage(char *argv0)
{
fprintf(stderr, "usage: %s [dir]\n", argv0);
exit(1);
}
int
main(int argc, char *argv[])
{
char cwd[PATH_MAX], *ipath;
char *ifilter;
if (argc > 2)
usage(argv[0]);
/* Confirm we are in a terminal */
if (!isatty(0) || !isatty(1)) {
fprintf(stderr, "stdin or stdout is not a tty\n");
exit(1);
}
if (getuid() == 0)
ifilter = ".";
else
ifilter = "^[^.]"; /* Hide dotfiles */
if (argv[1] != NULL) {
ipath = argv[1];
} else {
ipath = getcwd(cwd, sizeof(cwd));
if (ipath == NULL)
ipath = "/";
}
signal(SIGINT, SIG_IGN);
/* Test initial path */
if (canopendir(ipath) == 0) {
fprintf(stderr, "%s: %s\n", ipath, strerror(errno));
exit(1);
}
/* Set locale before curses setup */
setlocale(LC_ALL, "");
initcurses();
browse(ipath, ifilter);
exitcurses();
exit(0);
}

55
strlcat.c Normal file
View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
#include "util.h"
/*
* Appends src to string dst of size dsize (unlike strncat, dsize is the
* full size of dst, not space left). At most dsize-1 characters
* will be copied. Always NUL terminates (unless dsize <= strlen(dst)).
* Returns strlen(src) + MIN(dsize, strlen(initial dst)).
* If retval >= dsize, truncation occurred.
*/
size_t
strlcat(char *dst, const char *src, size_t dsize)
{
const char *odst = dst;
const char *osrc = src;
size_t n = dsize;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end. */
while (n-- != 0 && *dst != '\0')
dst++;
dlen = dst - odst;
n = dsize - dlen;
if (n-- == 0)
return(dlen + strlen(src));
while (*src != '\0') {
if (n != 0) {
*dst++ = *src;
n--;
}
src++;
}
*dst = '\0';
return(dlen + (src - osrc)); /* count does not include NUL */
}

50
strlcpy.c Normal file
View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
#include "util.h"
/*
* Copy string src to buffer dst of size dsize. At most dsize-1
* chars will be copied. Always NUL terminates (unless dsize == 0).
* Returns strlen(src); if retval >= dsize, truncation occurred.
*/
size_t
strlcpy(char *dst, const char *src, size_t dsize)
{
const char *osrc = src;
size_t nleft = dsize;
/* Copy as many bytes as will fit. */
if (nleft != 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src. */
if (nleft == 0) {
if (dsize != 0)
*dst = '\0'; /* NUL-terminate dst */
while (*src++)
;
}
return(src - osrc - 1); /* count does not include NUL */
}

5
util.h Normal file
View file

@ -0,0 +1,5 @@
/* See LICENSE file for copyright and license details. */
#undef strlcat
size_t strlcat(char *, const char *, size_t);
#undef strlcpy
size_t strlcpy(char *, const char *, size_t);