Initial commit

This commit is contained in:
Nikita Ivanov 2022-05-22 12:55:04 +05:00
commit 16bdee675c
No known key found for this signature in database
GPG Key ID: 6E656AC5B97B5133
15 changed files with 660 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.o
*.d
/gen/
/ctpv
/embed/embed

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Nikita Ivanov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

53
Makefile Normal file
View File

@ -0,0 +1,53 @@
PREFIX := /usr/local
BINPREFIX := $(PREFIX)/bin
SRC := $(wildcard *.c)
OBJ := $(SRC:.c=.o)
DEP := $(OBJ:.o=.d)
PRE := $(wildcard prev/*.sh)
GEN := gen/prev/scripts.h gen/prev/helpers.h
CFLAGS += -Os -MD -Wall -Wextra -Wno-unused-parameter
LDFLAGS += -lmagic
all: ctpv
options:
@echo "CC = $(CC)"
@echo "CFLAGS = $(CFLAGS)"
@echo "LDFLAGS = $(LDFLAGS)"
install: ctpv
install -d $(BINPREFIX)
install $< $(BINPREFIX)
uninstall:
$(RM) $(BINPREFIX)/ctpv
clean:
$(RM) ctpv $(OBJ) $(DEP) $(GEN)
$(MAKE) -C embed clean
make_embed:
$(MAKE) -C embed
ctpv: $(OBJ)
ctpv.c: $(GEN)
gen/prev/scripts.h: embed/embed $(PRE)
@mkdir -p $(@D)
embed/embed -p prev_scr_ $(PRE) > $@
gen/prev/helpers.h: embed/embed helpers.sh
@mkdir -p $(@D)
embed/embed -p prev_ helpers.sh > $@
embed/embed: make_embed
@:
-include $(DEP)
.PHONY: all options install uninstall clean make_embed
.DELETE_ON_ERROR:

117
ctpv.c Normal file
View File

@ -0,0 +1,117 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <magic.h>
#include "error.h"
#include "utils.h"
#include "previews.h"
static magic_t magic;
static struct {
int server;
} ctpv;
static void cleanup(void) {
cleanup_previews();
if (magic != NULL)
magic_close(magic);
}
static int init_magic() {
magic = magic_open(MAGIC_MIME_TYPE);
ERRCHK_RET(!magic, "magic_open() failed");
ERRCHK_RET(magic_load(magic, NULL) != 0, "magic_load() failed: %s",
magic_error(magic));
return OK;
}
static const char *get_mimetype(char const *path) {
const char *r = magic_file(magic, path);
if (!r) {
print_errorf("magic_file() failed: %s", magic_error(magic));
return NULL;
}
return r;
}
static const char *get_ext(char const *path) {
const char *r = strrchr(path, '.');
if (!r)
return NULL;
return &r[1];
}
static int server(void)
{
/* TODO */
return OK;
}
#define GET_PARG(a, i) (a) = argc > (i) ? argv[i] : NULL
static int client(int argc, char *argv[])
{
char *f, *w, *h, *x, *y;
GET_PARG(f, 0);
GET_PARG(w, 1);
GET_PARG(h, 2);
GET_PARG(x, 3);
GET_PARG(y, 4);
ERRCHK_RET(!f, "file not given");
ERRCHK_RET(access(f, R_OK) != 0, "failed to access '%s': %s", f,
strerror(errno));
ERRCHK_RET_OK(init_magic());
init_previews(previews, LEN(previews));
const char *mimetype = get_mimetype(f);
ERRCHK_RET(!mimetype);
Preview *p = find_preview(get_ext(f), mimetype);
if (!p) {
puts("no preview found");
return OK;
}
PreviewArgs args = { .f = f, .w = w, .h = h, .x = x, .y = y };
ERRCHK_RET_OK(run_preview(p, &args));
return OK;
}
int main(int argc, char *argv[])
{
program = argc > 0 ? argv[0] : "ctpv";
int c;
while ((c = getopt(argc, argv, "s")) != -1) {
switch (c) {
case 's':
ctpv.server = 1;
break;
default:
return EXIT_FAILURE;
}
}
int ret;
if (ctpv.server)
ret = server();
else
ret = client(argc, &argv[optind]);
cleanup();
return ret == OK ? EXIT_SUCCESS : EXIT_FAILURE;
}

6
embed/Makefile Normal file
View File

@ -0,0 +1,6 @@
embed: embed.c
clean:
$(RM) embed
.PHONY: clean

72
embed/embed.c Normal file
View File

@ -0,0 +1,72 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
void getvarname(char *res, char *prefix, char *filename)
{
char *s = strrchr(filename, '/');
if (s)
s++;
else
s = filename;
if (prefix) {
size_t prefix_len = strlen(prefix);
memcpy(res, prefix, prefix_len);
res += prefix_len;
}
for (int c, i = 0; s[i] != 0; i++) {
c = s[i];
if (!isalnum(c))
c = '_';
res[i] = c;
}
}
void embed_file(char *prefix, char *filename)
{
FILE *f = fopen(filename, "r");
if (!f) {
fprintf(stderr, "failed to open %s: %s\n", filename, strerror(errno));
exit(EXIT_FAILURE);
}
static char varname[FILENAME_MAX];
getvarname(varname, prefix, filename);
printf("char %s[] = { ", varname);
int c;
while ((c = fgetc(f)) != EOF)
printf("0x%x, ", c);
puts("0 };");
fclose(f);
}
int main(int argc, char *argv[])
{
char *prefix = NULL;
int c;
while ((c = getopt(argc, argv, "p:")) != -1) {
switch (c) {
case 'p':
prefix = optarg;
break;
default:
return EXIT_FAILURE;
}
}
for (int i = optind; i < argc; i++)
embed_file(prefix, argv[i]);
return EXIT_SUCCESS;
}

16
error.c Normal file
View File

@ -0,0 +1,16 @@
#include <stdio.h>
#include "error.h"
#include "utils.h"
void print_error(char const *error_msg)
{
fprintf(stderr, "%s: %s\n", program, error_msg);
}
void print_errorf(char const *format, ...)
{
char s[1024];
FORMATTED_STRING(s, format);
print_error(s);
}

33
error.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef ERROR_H
#define ERROR_H
#define _ERRCHK_RET_PR(format, ...) \
print_error##__VA_OPT__(f)(format __VA_OPT__(, ) __VA_ARGS__)
/*
* If cond is true, return ERR. Also call print_error or
* print_errorf if error message or format string is given.
*/
#define ERRCHK_RET(cond, ...) \
do { \
if (cond) { \
__VA_OPT__(_ERRCHK_RET_PR(__VA_ARGS__);) \
return ERR; \
} \
} while (0)
/*
* Shortcut for ERRCHK_RET(expr != OK)
*/
#define ERRCHK_RET_OK(expr, ...) \
ERRCHK_RET((expr) != OK __VA_OPT__(, ) __VA_ARGS__)
enum {
OK,
ERR,
};
void print_error(char const *error_msg);
void print_errorf(char const *format, ...);
#endif

3
helpers.sh Normal file
View File

@ -0,0 +1,3 @@
exists() {
command -v "$1" > /dev/null
}

1
prev/file.sh Normal file
View File

@ -0,0 +1 @@
echo "$f"

176
preview.c Normal file
View File

@ -0,0 +1,176 @@
#include <stdio.h>
#include <string.h>
#include "utils.h"
#include "error.h"
#include "preview.h"
#define PREVP_SIZE sizeof(Preview *)
static char shell[] = "sh";
static Preview **sorted_by_ext,
**sorted_by_mimetype;
static size_t prevs_length;
static int cmp_prev_ext(const void *p1, const void *p2)
{
Preview *pr1 = *(Preview **)p1;
Preview *pr2 = *(Preview **)p2;
if (!pr1->ext)
return 1;
if (!pr2->ext)
return -1;
return strcmp(pr1->ext, pr2->ext);
}
static int cmp_prev_mimetype(const void *p1, const void *p2)
{
Preview *pr1 = *(Preview **)p1;
Preview *pr2 = *(Preview **)p2;
if (pr1->type[0] == '\0')
return 1;
else if (pr2->type[0] == '\0')
return -1;
if (!pr1->subtype && pr2->subtype) {
return 1;
} else if (pr1->subtype && !pr2->subtype) {
return -1;
} else {
int ret = strcmp(pr1->type, pr2->type);
if (ret == 0 && pr1->subtype && pr2->subtype)
return strcmp(pr1->subtype, pr2->subtype);
return ret;
}
}
void init_previews(Preview *ps, size_t len)
{
prevs_length = len;
size_t size = len * PREVP_SIZE;
sorted_by_ext = malloc(size);
sorted_by_mimetype = malloc(size);
if (!sorted_by_ext || !sorted_by_mimetype) {
print_error("malloc() failed");
abort();
}
for (size_t i = 0; i < len; i++)
sorted_by_ext[i] = sorted_by_mimetype[i] = &ps[i];
qsort(sorted_by_ext, len, PREVP_SIZE, cmp_prev_ext);
qsort(sorted_by_mimetype, len, PREVP_SIZE, cmp_prev_mimetype);
}
void cleanup_previews(void)
{
if (sorted_by_ext) {
free(sorted_by_ext);
sorted_by_ext = NULL;
}
if (sorted_by_mimetype) {
free(sorted_by_mimetype);
sorted_by_mimetype = NULL;
}
prevs_length = 0;
}
static Preview *find_by_ext(char const *ext)
{
Preview *key = &(Preview){ .ext = (char *)ext };
void *p =
bsearch(&key, sorted_by_ext, prevs_length, PREVP_SIZE, cmp_prev_ext);
return p ? *(Preview **)p : NULL;
}
static void break_mimetype(char *mimetype, char **type, char **subtype)
{
*type = mimetype[0] == '\0' ? NULL : mimetype;
*subtype = NULL;
char *s = strchr(mimetype, '/');
if (!s) {
print_errorf("invalid mimetype: '%s'", mimetype);
abort();
}
*s = '\0';
*subtype = &s[1];
}
#define MIMETYPE_MAX 64
static Preview *find_by_mimetype(char const *mimetype)
{
Preview *p;
char mimetype_c[MIMETYPE_MAX], *t, *s;
strncpy(mimetype_c, mimetype, MIMETYPE_MAX - 1);
break_mimetype(mimetype_c, &t, &s);
for (size_t i = 0; i < prevs_length; i++) {
p = sorted_by_mimetype[i];
if (strcmp(t, p->type) == 0) {
if (p->subtype) {
if (strcmp(s, p->subtype) == 0)
return p;
} else {
return p;
}
}
}
return NULL;
}
Preview *find_preview(char const *ext, char const *mimetype)
{
if (!sorted_by_ext || !sorted_by_mimetype) {
print_error("init_previews() not called");
abort();
}
Preview *ret = NULL;
if (mimetype)
ret = find_by_mimetype(mimetype);
if (!ret)
ret = find_by_ext(ext);
return ret;
}
#define SET_PENV(n, v) \
do { \
if (v) \
ERRCHK_RET(setenv((n), (v), 1) != 0); \
} while (0)
int run_preview(Preview *p, PreviewArgs *pa)
{
SET_PENV("f", pa->f);
SET_PENV("w", pa->w);
SET_PENV("h", pa->h);
SET_PENV("x", pa->x);
SET_PENV("y", pa->y);
char *args[] = { shell, "-c", p->script, shell, NULL };
int ret, exitcode;
ret = spawn(args, NULL, &exitcode);
if (exitcode > 0)
printf("error: preview exited with code: %d\n", exitcode);
return ret;
}

19
preview.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef PREVIEW_H
#define PREVIEW_H
#include <stdlib.h>
typedef struct {
char *ext, *type, *subtype, *script;
} Preview;
typedef struct {
char *f, *w, *h, *x, *y;
} PreviewArgs;
void init_previews(Preview *ps, size_t len);
void cleanup_previews(void);
Preview *find_preview(char const *ext, char const *mimetype);
int run_preview(Preview *p, PreviewArgs *pa);
#endif

16
previews.h Normal file
View File

@ -0,0 +1,16 @@
#include <stdlib.h>
#include "gen/prev/scripts.h"
#include "preview.h"
/*
* This file is supposed to be included in ctpv.c
*/
#define P(e, t, s, f) { e, t, s, f }
Preview previews[] = {
P(NULL, "text", "plain", prev_scr_file_sh),
};
#undef P

90
utils.c Normal file
View File

@ -0,0 +1,90 @@
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "error.h"
#include "utils.h"
char *program;
/*
* Call command
*
* If cpid is NULL, wait for the command to finish executing;
* otherwise store pid in cpid
*/
int spawn(char *args[], pid_t *cpid, int *exitcode)
{
if (exitcode)
*exitcode = -1;
pid_t pid = fork();
ERRCHK_RET(pid == -1, "fork() failed");
/* Child process */
if (pid == 0) {
execvp(args[0], args);
print_errorf("exec() failed: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if (cpid) {
*cpid = pid;
} else {
int stat;
ERRCHK_RET(waitpid(pid, &stat, 0) == -1, "waitpid() failed: %s",
strerror(errno));
if (exitcode && WIFEXITED(stat))
*exitcode = WEXITSTATUS(stat);
}
return OK;
}
CharVec char_v_new(size_t cap)
{
CharVec v;
v.buf = NULL;
v.len = 0;
v.cap = cap;
return v;
}
void char_v_free(CharVec *v)
{
if (v->buf) {
free(v->buf);
v->buf = NULL;
v->len = 0;
}
}
void char_v_append(CharVec *v, char c)
{
if (!v->buf) {
v->buf = malloc(v->cap * sizeof(v->buf[0]));
if (!v->buf) {
print_error("calloc() failed");
abort();
}
v->buf[0] = '\0';
v->len++;
}
if (v->len + 1 >= v->cap) {
v->cap *= 2;
v->buf = realloc(v->buf, v->cap * sizeof(v->buf[0]));
if (!v->buf) {
print_error("realloc() failed");
abort();
}
}
v->buf[v->len - 1] = c;
v->buf[v->len] = '\0';
v->len++;
}

32
utils.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef UTILS_H
#define UTILS_H
#include <stdlib.h>
#include <stdarg.h>
#define LEN(a) (sizeof(a) / sizeof((a)[0]))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define FORMATTED_STRING(arr, format) \
do { \
va_list args; \
va_start(args, (format)); \
vsnprintf((arr), LEN(arr) - 1, (format), args); \
va_end(args); \
} while (0)
typedef struct {
char *buf;
size_t len, cap;
} CharVec;
extern char *program;
int spawn(char *args[], pid_t *cpid, int *exitcode);
CharVec char_v_new(size_t cap);
void char_v_append(CharVec *v, char c);
void char_v_free(CharVec *v);
#endif