dp

[PL ONLY] Simple and modular daemon
git clone git://git.kocotian.pl/dp.git
Log | Files | Refs | README | LICENSE

commit d50842782fb42b8b337aaeeb446f253f902ee084
Author: Kacper Kocot <kocotian@kocotian.pl>
Date:   Sun, 25 Oct 2020 19:21:15 +0100

initial commit

Diffstat:
ALICENSE | 13+++++++++++++
ALICENSE_LIBSL | 24++++++++++++++++++++++++
AMakefile | 24++++++++++++++++++++++++
AREADME.md | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aarg.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.h | 12++++++++++++
Adaemonize.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adp | 0
Adp.1 | 20++++++++++++++++++++
Adp.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adpm | 0
Adpm.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adpmi | 0
Adpmi.1 | 26++++++++++++++++++++++++++
Adpmi.c | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahh.tar | 0
Ahttp.h | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatics.h | 3+++
Atbkeys.h | 0
Atermbox/bin/termbox.a | 0
Atermbox/input.c | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/license | 19+++++++++++++++++++
Atermbox/makefile | 40++++++++++++++++++++++++++++++++++++++++
Atermbox/memstream.c | 36++++++++++++++++++++++++++++++++++++
Atermbox/memstream.h | 20++++++++++++++++++++
Atermbox/readme.md | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/ringbuffer.c | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/ringbuffer.h | 26++++++++++++++++++++++++++
Atermbox/term.c | 412+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/term.h | 38++++++++++++++++++++++++++++++++++++++
Atermbox/termbox.c | 886+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/termbox.h | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/termplus.h | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/tools/astylerc | 27+++++++++++++++++++++++++++
Atermbox/tools/collect_terminfo.py | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atermbox/utf8.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.h | 10++++++++++
38 files changed, 3825 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/LICENSE_LIBSL b/LICENSE_LIBSL @@ -0,0 +1,24 @@ +MIT/X Consortium License + +© 2013-2019 Anselm R Garbe <anselm@garbe.ca> +© 2015-2019 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2016 Markus Teich <markus.teich@stusta.mhn.de> +© 2015 Eric Pruitt <eric.pruitt@gmail.com> + +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. diff --git a/Makefile b/Makefile @@ -0,0 +1,24 @@ +CC="gcc" +DESTDIR="/usr/local/" +BINDIR="bin" +DPDIR="share/dp" +MANDIR="man" + +dp: dp.c config.h daemonize.h + ${CC} -o $@ $< -Wunused-function -Wunused-label -Wunused-value -Wunused-variable -pedantic -std=c99 + +dpm: dpm.c config.h + ${CC} -o $@ $< termbox/bin/termbox.a -Wunused-function -Wunused-label -Wunused-value -Wunused-variable -pedantic -std=c99 + +dpmi: dpmi.c config.h + ${CC} -o $@ $< -Wunused-function -Wunused-label -Wunused-value -Wunused-variable -pedantic -std=c99 + +all: dp dpm dpmi + +install: all + mkdir -p ${DESTDIR}${BINDIR} ${DESTDIR}${DPDIR} ${DESTDIR}${MANDIR}/man1 + install -Dm 755 dp ${DESTDIR}${BINDIR} + install -Dm 755 dpm ${DESTDIR}${BINDIR} + install -Dm 755 dpmi ${DESTDIR}${BINDIR} + cp -f dp.1 dpmi.1 ${DESTDIR}${MANDIR}/man1/ + dpmi -g fortune sentences covid diff --git a/README.md b/README.md @@ -0,0 +1,101 @@ +# Don't Panic + +Don't Panic to prosty, lekki i modularny daemon działający w tle. Jego głównym zadaniem jest wykonywanie modułów będących najczęściej skryptami powłoki (chociaż mogą być również napisane w dowolnym innym języku programowania) i przekazywanie ich standardowego wyjścia do powiadomień. Moduły najczęściej pobierają i przetwarzają dane z serwera i wypisują je na standardowe wyjście. + +Don't Panic jest jedynie daemonem, do jego obsługi używa się oddzielnych programów niewbudowanych w rdzeń daemona, jak np. "Don't Panic Manager" służący m.in. do konfiguracji odstępów czasowych w jakich będą wykonywane moduły oraz "Don't Panic Module Installer" służący do pobierania i usuwania modułów. +Sprawia to, że daemon podąża Filozofią UNIXa - "Pisz programy które robią jedną rzecz i robią ją dobrze. Rób programy aby pracowały ze sobą. Rób programy które łapią strumienie tekstowe, ponieważ to uniwersalny interfejs". + +## Instalacja +Aby zainstalować Don't Panic wraz z programami pomocniczymi służącymi do jego konfiguracji, będziesz potrzebować: +- Systemu operacyjnego Linux (lub innego zgodnego z POSIX.1-2008) +- Dostępu do konta root (lub uprawnień do zapisu w katalogu /usr/local) +- Programu `make` +- Kompilatora `GCC` (lub innego zgodnego z C99) +- `libnotify` do wyświetlania powiadomień + +**Uwaga**: Każde polecenie zaczynające się od `#` wymaga uprawnień roota. Aby to wykonać najczęściej należy na początku każdego takiego polecenia wpisać `sudo` + +### Instalacja `make` i `gcc` +#### Debian i pochodne (Ubuntu, Mint, elementary...): +```sh +# apt install make gcc +``` + +#### Arch Linux i pochodne (Artix, ArchBang, Antegros...): +```sh +# pacman -Sy make gcc +``` + +### Instalacja `libnotify` +#### Debian i pochodne (Ubuntu, Mint, elementary...): +```sh +# apt install libnotify-bin libnotify-dev +``` + +#### Arch Linux i pochodne (Artix, ArchBang, Antegros...): +```sh +# pacman -Sy libnotify +``` + +### Kompilacja i instalacja +```sh +$ git clone https://git.kocotian.pl/dp +$ cd dp +$ make all +# sudo make install +``` + +## Pierwsze uruchomienie +Aby uruchomić daemon należy wpisać +```sh +$ dp +``` + +## Konfiguracja +Podczas instalacji zostają pobrane 3 moduły - `fortune`, `sentences` oraz `covid`. Wykonują się one w godzinnych odstępach czasowych. Aby zmienić odstępy czasowe, należy uruchomić półgraficzny menadżer daemona: +```sh +# dpm +``` +Pozwala on skonfigurować odstępy czasowe lub wyłączyć daemona. + +## Dodawanie i usuwanie modułów +Don't Panic przychodzi z programem `dpmi` pozwalającym na dodawanie i usuwanie nowych modułów. Aby zainstalować moduł, należy wpisać: +```sh +# dpmi -g <nazwa modułu> +``` +Przykładowo, moduł `weather` nie jest domyślnie instalowany. Aby z kolei usunąć moduł, należy wpisać: +```sh +# dpmi -r <nazwa modułu> +``` + +## Powiadomienia się nie wyświetlają: +Jeśli powiadomienia się nie wyświetlaja najprawdopodobniej brakuje programu wyświetlającego powiadomienia. +Aby rozwiązać problem, najpierw należy spróbować wpisać komendę odpowiedzialną za wyświetlenie powiadomienia: +```sh +$ notify-send "test" +``` +Jeśli komenda nie została znaleziona, należy wykonać ponownie krok polegający na instalacji biblioteki `libnotify`. Jeśli komenda została wykonana pomyślnie, ale nie pokazało się żadne powiadomienie może to oznaczać, że: +- powiadomienia mogą być wyciszone/wyłączone. Należy je wtedy włączyć ponownie w ustawieniach +- brakuje programu odpowiedzialnego za wyświetlanie powiadomień. Poleca się wtedy zainstalować program `dunst`: + - Debian, Ubuntu...: + ```sh + # apt install dunst + ``` + - Arch, Artix...: + ```sh + # pacman -Sy dunst + ``` + a następnie uruchomić ponownie system. Jeśli powiadomienia nadal się nie pokazują należy wpisać `dunst &`, jest to jednak rozwiązanie tymczasowe służące do sprawdzenia czy wszystko działa poprawnie. Jeśli po wpisaniu `dunst &` powiadomienia się wyświetlają, należy rozważyć dodanie `dunst` do listy programów uruchamianych przy starcie. + +Jeśli powiadomienia dalej się nie wyświetlają, nie jest to niestety nasza wina :(, program podczas testów działał bezbłędnie. + +## Opcjonalnie: Instalacja lokalna +DP udostępnia plik `config.h` który zawiera różne zmienne konfiguracyjne. Ich zmiana pozwala m.in. na instalację lokalną dla jednego użytkownika. Aby to zrobić należy: +- Otworzyć plik `config.h` i zmienić wszystkie wystąpienia `/usr/local` na np. `/home/jankowalski/.local`, gdzie `jankowalski` jest nazwą użytkownika +- W pliku `Makefile` zmienić zawartość zmiennej `DESTDIR` na docelowy katalog +- Przeprowadzić kompilację i instalację ponownie + +## Opcjonalnie: Zmiana systemu wyświetlającego powiadomienia +Aby zmienić system wyświetlający powiadomienia (domyślnie `libnotify`) należy: +- Otworzyć plik `config.h` +- Zmienić `notify-send` na własną komendę pozwalającą wyświetlać powiadomienia diff --git a/arg.h b/arg.h @@ -0,0 +1,51 @@ +/* See LICENSE_LIBSL file for copyright and license details. */ + +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.h b/config.h @@ -0,0 +1,12 @@ +static char pwdir[] = "/usr/local/share/dp"; /* pwdir - DP chdirs here */ +static char csvpath[] = "/usr/local/share/dp/modules.csv"; /* path to the modules file */ +static char pidpath[] = "/tmp/dp.pid"; /* path to DP' PID */ + +#ifdef DP /* only for Don't Panic Daemon */ +static char notifycmd[] = "notify-send"; +#endif + +#ifdef DPM /* only for Don't Panic Manager */ +#define COLOR_PRIMARY TB_WHITE /* Primary color (eg. borders) */ +#define COLOR_ACCENT TB_YELLOW /* Accent color (eg. modules list) */ +#endif diff --git a/daemonize.h b/daemonize.h @@ -0,0 +1,55 @@ +#ifndef DAEMONIZE_H +#define DAEMONIZE_H + +#define _XOPEN_SOURCE 700 + +/* + * daemonize.h + * simply daemonize your program + * version 1.1.2 + * (c) 2020 kocotian <kocotian@kocotian.pl> + * + * for copyright details, see + * git.kocotian.pl/daemonize.h/blob/main/LICENSE + */ + +#include <sys/stat.h> +#include <sys/types.h> + +#include <signal.h> +#include <stdlib.h> +#include <syslog.h> +#include <unistd.h> + +static int +daemonize(char *chdir_, int umask_, void (*signalize)(void)) +{ + pid_t pid; + int fd; + + if ((pid = fork()) < 0) + return EXIT_FAILURE; + if (pid > 0) + exit(EXIT_SUCCESS); + + if (setsid() < 0) + return EXIT_FAILURE; + + signalize(); + + if ((pid = fork()) < 0) + return EXIT_FAILURE; + if (pid > 0) + exit(EXIT_SUCCESS); + + chdir(chdir_); + umask(umask_); + + fd = sysconf(_SC_OPEN_MAX) + 1; + while (fd-- > 0) + close(fd); + + return EXIT_SUCCESS; +} + +#endif diff --git a/dp b/dp Binary files differ. diff --git a/dp.1 b/dp.1 @@ -0,0 +1,20 @@ +.TH DP 1 dp +.SH NAZWA +dp \- Don't Panic +.SH SKŁADNIA +.B dp +.SH OPIS +.P +Don't Panic to prosty, lekki i modularny daemon działający w tle, napisany na konkurs Hack Heroes 2020 organizowany w ramach Code Week 2020. Jego głównym zadaniem jest wykonywanie modułów będących najczęściej skryptami powłoki (chociaż mogą być również napisane w dowolnym innym języku programowania) i przekazywanie ich standardowego wyjścia do powiadomień. Moduły najczęściej pobierają i przetwarzają dane z serwera i wypisują je na standardowe wyjście. +.P +Don't Panic jest jedynie daemonem, do jego obsługi używa się oddzielnych programów niewbudowanych w rdzeń daemona, jak np. "Don't Panic Manager" służący m.in. do konfiguracji odstępów czasowych w jakich będą wykonywane moduły oraz "Don't Panic Module Installer" służący do pobierania i usuwania modułów. +Sprawia to, że daemon podąża Filozofią UNIXa - "Pisz programy które robią jedną rzecz i robią ją dobrze. Rób programy aby pracowały ze sobą. Rób programy które łapią strumienie tekstowe, ponieważ to uniwersalny interfejs". +.SH BŁĘDY +.P +Gdy przed rozpoczęciem działania w tle wystąpi błąd, dla przykładu inny daemon działa już w tle, program wyświetli stosowny komunikat i zwróci kod błędu +.I (-1) +.SH PRAWA AUTORSKIE +Copyright (c) 2020 by: +.B Karolina Nocek, +.B Kacper Kocot, +.B Wojciech Tomasik diff --git a/dp.c b/dp.c @@ -0,0 +1,188 @@ +#define _XOPEN_SOURCE 700 + +/* + * dp - don't panic + * simple unix-philosophy wrote + * modular daemon that shows notifications + * received from healthy modules + * + * Copyright (c) 2020 by: + * - Karolina Nocek + * - Wojciech Tomasik + * - Kacper Kocot + * + * For copying, redistribution, etc, + * see LICENSE file. + */ + +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "daemonize.h" +#include "util.c" + +#define DP +#define UMASK 0022 +#define SHELL(cmd) {.v = (const char*[]){"/bin/sh", "-c", cmd, NULL}} +#define SHNOTIFY(progname) {.v = (const char *)progname} + +typedef union { + int i; + unsigned int ui; + float f; + void *v; +} Arg; + +typedef struct { + int time; + char *name; +} Module; + +typedef struct { + int time; + void (*func)(const Arg *); + Arg arg; +} Command; + +void notifySend(char *notify); +void shellnotify(const Arg *arg); +void run(const Arg *arg); +int parsecsv(char *csvpath); +void sighandler(int signo); +void signalize(void); + +static Command *cmd = NULL; +static volatile int cmdlen = 0; +static int daemonized = 0; + +#include "config.h" + +void +notifySend(char *notify) +{ + char str[BUFSIZ]; + snprintf(str, BUFSIZ, "%s \"%s\"", + notifycmd, notify); + Arg argnotify = SHELL(str); + run(&argnotify); +} + +void +shellnotify(const Arg *arg) +{ + /* (+ 4) because: '"$(' - 2 chars, ')"' - 1 chars, plus NULL at end */ + char *command = malloc(strlen(arg -> v) + 4); + snprintf(command, strlen(arg -> v) + 4, "$(%s)", + (arg -> v)); + notifySend(command); + free(command); +} + +void +run(const Arg *arg) +{ + if (!fork()) { + setsid(); + execvp(((char **)arg -> v)[0], (char **)arg -> v); + syslog(LOG_PERROR, "execvp failed"); + unlink(pidpath); + exit(EXIT_FAILURE); + } +} + +int +parsecsv(char *csvpath) +{ + FILE *csvf; + int cmdlen = 0, iter = -1; + char buf[256]; char *tok; + free(cmd); + cmd = malloc(sizeof(Command)); + if ((csvf = fopen(csvpath, "r")) == NULL) { + if (!daemonized) perror(csvpath); + exit(EXIT_FAILURE); + } + while (fgets(buf, 256, csvf) != NULL) { + tok = strtok(buf, ","); + cmd[++iter].time = atoi(tok); + cmd[iter].func = shellnotify; + tok = strtok(NULL, ","); + cmd[iter].arg.v = malloc(strlen(tok)); + strncpy(cmd[iter].arg.v, tok, strlen(tok)); + cmd = realloc(cmd, sizeof(Command) * (++cmdlen + 1)); + } + fclose(csvf); + return cmdlen; +} + +void +sighandler(int signo) +{ + signal(signo, sighandler); + + switch (signo) { + case SIGINT: + unlink(pidpath); + free(cmd); + exit(EXIT_SUCCESS); + break; + case SIGUSR1: + cmdlen = parsecsv(csvpath); + break; + } +} + +void +signalize(void) +{ + signal(SIGINT, sighandler); + signal(SIGUSR1, sighandler); +} + +int +main(void) +{ + struct stat statvar; + FILE *pidf = NULL; + char iter = -1; + time_t t; + + if (!(stat(pidpath, &statvar) < 0)) + die("another dp process is running"); + + if (stat(pwdir, &statvar) < 0) + die("%s doesn't exist", pwdir); + + cmdlen = parsecsv(csvpath); + if (!(daemonized = !daemonize(pwdir, UMASK, signalize))) + die("failed to daemonize"); + + if ((pidf = fopen(pidpath, "w")) == NULL) + exit(EXIT_FAILURE); + + fprintf(pidf, "%d\n", getpid()); + fclose(pidf); + + while (1) { + t = time(NULL); + while (++iter < cmdlen) + if (cmd[iter].time) { + if (!((t / 60 % 60) % cmd[iter].time)) + cmd[iter].func(&(cmd[iter].arg)); + } + else + kill(getpid(), SIGINT); + iter = -1; + sleep(60 - t % 60); + } + return 0; +} diff --git a/dpm b/dpm Binary files differ. diff --git a/dpm.c b/dpm.c @@ -0,0 +1,198 @@ +#define _XOPEN_SOURCE 700 + +/* + * dpm - don't panic manager + * more user-friendly program + * that manages a don't panic daemon + * + * Copyright (c) 2020 by: + * - Karolina Nocek + * - Wojciech Tomasik + * - Kacper Kocot + * + * For copying, redistribution, etc, + * see LICENSE file. + */ + +#include <sys/stat.h> +#include <sys/types.h> + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "tbkeys.h" +#include "termbox/termbox.h" +#include "termbox/termplus.h" +#include "util.c" + +#define DPM + +typedef struct { + int time; + char *name; +} Module; + +static int parsecsv(char *csvpath); +static int savecsv(char *csvpath); +static int drawmenu(pid_t dppid, int selitem); +static pid_t getdppid(void); + +static Module *mods; +static volatile int modlen; + +#include "config.h" + +static int +drawmenu(pid_t dppid, int selitem) +{ + int iter; + + tb_clear(); + tb_printf("Status DP: ", 1, 1, TB_WHITE, TB_DEFAULT); + if (dppid < 0) + tb_printf("zatrzymany", 12, 1, TB_RED | TB_BOLD, TB_DEFAULT); + else + tb_printf("pracuje (PID: %d)", 12, 1, TB_GREEN | TB_BOLD, TB_DEFAULT, + dppid); + + iter = -1; + while (++iter < modlen) { + tb_printf(iter == selitem ? "=>" : " *", 1, iter + 3, + iter == selitem ? COLOR_ACCENT | TB_BOLD : TB_WHITE, TB_DEFAULT); + tb_printf("co %2d minut", 4, iter + 3, COLOR_ACCENT | TB_BOLD, TB_DEFAULT, + mods[iter].time); + tb_printf("wykonywanie", 16, iter + 3, TB_WHITE | TB_BOLD, TB_DEFAULT); + tb_printf("%s", 28, iter + 3, TB_WHITE, TB_DEFAULT, + mods[iter].name); + }; + tb_printf("Wciśnij ", 1, tb_height() - 4, COLOR_PRIMARY, TB_DEFAULT); + tb_printf("t", 9, tb_height() - 4, COLOR_ACCENT | TB_BOLD, TB_DEFAULT); + tb_printf("aby wyłączyć DP", 11, tb_height() - 4, COLOR_PRIMARY, TB_DEFAULT); + tb_printf("Wciśnij ", 1, tb_height() - 3, COLOR_PRIMARY, TB_DEFAULT); + tb_printf("s", 9, tb_height() - 3, COLOR_ACCENT | TB_BOLD, TB_DEFAULT); + tb_printf("aby zapisać", 11, tb_height() - 3, COLOR_PRIMARY, TB_DEFAULT); + tb_printf("Wciśnij ", 1, tb_height() - 2, COLOR_PRIMARY, TB_DEFAULT); + tb_printf("q", 9, tb_height() - 2, COLOR_ACCENT | TB_BOLD, TB_DEFAULT); + tb_printf("aby wyjść", 11, tb_height() - 2, COLOR_PRIMARY, TB_DEFAULT); + tb_drawbox(0, 0, tb_width() - 1, tb_height() - 1, COLOR_PRIMARY, TB_DEFAULT); +} + +static int +parsecsv(char *csvpath) +{ + FILE *csvf; + int modlen = 0, iter = -1; + char buf[256]; char *tok; + free(mods); + mods = malloc(sizeof(Module)); + if ((csvf = fopen(csvpath, "r")) == NULL) { + tb_shutdown(); + perror(csvpath); + exit(EXIT_FAILURE); + } + while (fgets(buf, 256, csvf) != NULL) { + tok = strtok(buf, ","); + mods[++iter].time = atoi(tok); + tok = strtok(NULL, ","); + mods[iter].name = malloc(strlen(tok)); + strncpy(mods[iter].name, tok, strlen(tok)); + mods = realloc(mods, sizeof(Module) * (++modlen + 1)); + } + fclose(csvf); + return modlen; +} + +static int +savecsv(char *csvpath) +{ + FILE *csvf; + int iter = -1; + if ((csvf = fopen(csvpath, "w")) == NULL) { + tb_shutdown(); + perror(csvpath); + exit(EXIT_FAILURE); + } + while (++iter < modlen) { + if (mods[iter].name != NULL) + fprintf(csvf, "%d,%s", + mods[iter].time, mods[iter].name); + } + fclose(csvf); + return modlen; +} + +static pid_t +getdppid(void) +{ + struct stat dpstat; + FILE *dppidf; + pid_t dppid; + + if (stat(pidpath, &dpstat) < 0) + dppid = -1; + else { + if ((dppidf = fopen(pidpath, "r")) == NULL) + die("Błąd odczytu pliku zawierającego PID DP"); + fscanf(dppidf, "%d", &dppid); + fclose(dppidf); + } + return dppid; +} + +int +main(int argc, char *argv[]) +{ + pid_t dppid; + char selitem = 0; + struct tb_event ev; + + dppid = getdppid(); + + chdir(pwdir); + modlen = parsecsv(csvpath); + + if (tb_init() < 0) + die("Inicjalizacja interfejsu zakończona niepowodzeniem"); + + tb_select_input_mode(TB_INPUT_ESC); + tb_select_output_mode(TB_OUTPUT_NORMAL); + + if (tb_width() < 48 || tb_height() < 16) + tb_die("Terminal jest zbyt mały"); + + drawmenu(dppid, selitem); + tb_present(); + + while (tb_poll_event(&ev)) { + if (ev.ch == 'q' || ev.key == TB_KEY_ESC) { + tb_shutdown(); + return 0; + } else if (ev.ch == 'j' || ev.key == TB_KEY_ARROW_DOWN) { + if (selitem < modlen - 1) + ++selitem; + } else if (ev.ch == 'k' || ev.key == TB_KEY_ARROW_UP) { + if (selitem > 0) + --selitem; + } else if (ev.ch == 'h' || ev.key == TB_KEY_ARROW_LEFT) { + if (mods[selitem].time > 1) + --(mods[selitem].time); + } else if (ev.ch == 'l' || ev.key == TB_KEY_ARROW_RIGHT) { + if (mods[selitem].time < 60) + ++(mods[selitem].time); + } else if (ev.ch == 's') { + savecsv(csvpath); + if (dppid != -1) + kill(dppid, SIGUSR1); + } else if (ev.ch == 't') { + if (!(dppid < 0)) { + kill(dppid, SIGINT); + dppid = getdppid(); + } + } + drawmenu(dppid, selitem); + tb_present(); + } +} diff --git a/dpmi b/dpmi Binary files differ. diff --git a/dpmi.1 b/dpmi.1 @@ -0,0 +1,26 @@ +.TH DPMI 1 dpmi +.SH NAZWA +dpmi \- Don't Panic Module Installer +.SH SKŁADNIA +.B dp +[-g] +[-r] +.B [lista modułów] +.SH OPIS +.P +Don't Panic Module Installer jest programem służącym do zarządzania modułami daemonu DP +.SH UŻYCIE +\fB\-g\fP +Oznacza, że chcemy zainstalować podane po fladze pakiety + +\fB\-r\fP +Oznacza, że chcemy usunąć podane po fladze pakiety + +Flag +.B nie wolno +mieszać ze sobą +.SH PRAWA AUTORSKIE +Copyright (c) 2020 by: +.B Karolina Nocek, +.B Kacper Kocot, +.B Wojciech Tomasik diff --git a/dpmi.c b/dpmi.c @@ -0,0 +1,222 @@ +#define _XOPEN_SOURCE 700 + +/* + * dpmi - don't panic module installer + * tool for adding optional modules + * for a don't panic daemon + * + * Copyright (c) 2020 by: + * - Karolina Nocek + * - Wojciech Tomasik + * - Kacper Kocot + * + * For copying, redistribution, etc, + * see LICENSE file. + */ + +#include <sys/stat.h> +#include <sys/types.h> + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "arg.h" +#include "http.h" +#include "util.c" + +#define DPMI + +typedef struct { + int time; + char *name; +} Module; + +static void getmodule(char *argv[]); +static void removemodule(char *argv[]); +static int parsecsv(char *csvpath); +static int savecsv(char *csvpath); + +static Module *mods; +static volatile int modlen; +char *argv0; + +#include "config.h" + +static void +getmodule(char *argv[]) +{ + char *response, modpath[BUFSIZ]; + size_t responseSize; + int iter = -1, uiter; + FILE *modf; + + while (argv[++iter] != NULL) { + printf("\033[1;34m:: \033[0mPobieram moduł \033[1m%s\033[0m...\n", + argv[iter]); + responseSize = httpGET("dp.kocotian.pl", 80, "/dl/", argv[iter], &response); + if (getResponseStatus(response, responseSize) != 200) { + udie("Wystąpił błąd \033[1m%d\033[0m", + getResponseStatus(response, responseSize)); + } + printf("\033[1;34m:: \033[0mOdebrano \033[1m%s\033[0m, zapisywanie...\n", + argv[iter]); + snprintf(modpath, 256, "%s/modules/%s", + pwdir, argv[iter]); + if((modf = fopen(modpath, "w")) == NULL) + udie("Nie udało się otworzyć pliku do zapisu:"); + fprintf(modf, "%s", truncateHeader(response)); + chmod(modpath, 0755); + fclose(modf); + mods = realloc(mods, sizeof(Module) * (++modlen + 1)); + mods[modlen - 1].time = 60; + mods[modlen - 1].name = malloc(strlen(argv[iter]) + 9); + strcpy(mods[modlen - 1].name, "modules/"); + strcat(mods[modlen - 1].name, argv[iter]); + strcat(mods[modlen - 1].name, "\n"); + uiter = -1; + while (++uiter < modlen - 1) { + if (mods[uiter].name != NULL) + if (!strcmp(mods[modlen - 1].name, mods[uiter].name)) { + mods[modlen - 1].name[strlen(mods[modlen - 1].name) - 1] = '\0'; + printf("\033[1;33m==> \033[0mModuł \033[1m%s \033[0mbył już zainstalowany, tylko aktualizuję.\n", + mods[modlen - 1].name); + mods[modlen - 1].name = NULL; + break; + } + } + free(response); + savecsv(csvpath); + } +} + +static void +removemodule(char *argv[]) +{ + char *buf, modpath[256], isRemoved = 0; + int iter = -1, uiter; + + while (argv[++iter] != NULL) { + printf("\033[1;34m:: \033[0mUsuwam moduł \033[1m%s\033[0m...\n", + argv[iter]); + snprintf(modpath, 256, "%s/modules/%s", + pwdir, argv[iter]); + buf = malloc(strlen(argv[iter]) + 9); + strcpy(buf, "modules/"); + strcat(buf, argv[iter]); + strcat(buf, "\n"); + uiter = -1; + while (++uiter < modlen) { + if (mods[uiter].name != NULL) + if (!strcmp(buf, mods[uiter].name)) { + if (unlink(modpath) < 0) + udie("Nie udało się usunąć modułu %s:", + argv[iter]); + mods[uiter].name = NULL; + isRemoved = 1; + break; + } + } + if(!isRemoved) + udie("Moduł \033[1m%s\033[0m nie jest zainstalowany", + argv[iter]); + free(buf); + savecsv(csvpath); + } +} + +static int +parsecsv(char *csvpath) +{ + FILE *csvf; + int modlen = 0, iter = -1; + char buf[256]; char *tok; + free(mods); + mods = malloc(sizeof(Module)); + if ((csvf = fopen(csvpath, "r")) == NULL) { + perror(csvpath); + exit(EXIT_FAILURE); + } + while (fgets(buf, 256, csvf) != NULL) { + tok = strtok(buf, ","); + mods[++iter].time = atoi(tok); + tok = strtok(NULL, ","); + mods[iter].name = malloc(strlen(tok)); + strncpy(mods[iter].name, tok, strlen(tok)); + mods = realloc(mods, sizeof(Module) * (++modlen + 1)); + } + fclose(csvf); + return modlen; +} + +static int +savecsv(char *csvpath) +{ + FILE *csvf; + int iter = -1; + if ((csvf = fopen(csvpath, "w")) == NULL) { + perror(csvpath); + exit(EXIT_FAILURE); + } + while (++iter < modlen) { + if (mods[iter].name != NULL) + fprintf(csvf, "%d,%s", + mods[iter].time, mods[iter].name); + } + fclose(csvf); + return modlen; +} + +static void +usage(void) +{ + udie("użycie: %s [-g STR] [-r STR]\n wpisz \"man dpmi\" po więcej informacji", + argv0); +} + +int +main(int argc, char *argv[]) +{ + struct stat dpstat; + char flag = 0; + FILE *dppidf; + pid_t dppid; + + ARGBEGIN { + case 'g': case 'r': /* fallthrough */ + flag = ARGC(); + break; + default: + usage(); + break; + } ARGEND; + + if (stat(pidpath, &dpstat) < 0) + dppid = -1; + else { + if ((dppidf = fopen(pidpath, "r")) == NULL) + udie("Błąd odczytu pliku zawierającego PID DP"); + fscanf(dppidf, "%d", &dppid); + fclose(dppidf); + } + + chdir(pwdir); + modlen = parsecsv(csvpath); + + switch (flag) { + case 'g': + getmodule(argv); + break; + case 'r': + removemodule(argv); + break; + } + if (dppid != -1) { + printf("\033[1;34m:: \033[0mOdświeżam DP...\n"); + kill(dppid, SIGUSR1); + } + printf("\033[1;32m:: \033[0mZakończono...\n"); + return 0; +} diff --git a/hh.tar b/hh.tar Binary files differ. diff --git a/http.h b/http.h @@ -0,0 +1,137 @@ +#ifndef HTTP_H +#define HTTP_H + +#define _XOPEN_SOURCE 700 + +/* + * http.h + * simple and small http library for C99 + * version 0.2 + * creator: kocotian + */ + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> + +#ifndef MAX_REQUEST_LEN +#define MAX_REQUEST_LEN 1024 +#endif + +size_t httpGET(char *hostname, unsigned short port, char *prepath, char *path, char **buffer); +int getResponseStatus(char *response, size_t responseSize); +int parseResponseLine(char *response, char *value, char **buffer); +char *truncateHeader(char *response); +int getHeaderLength(char *response); + +size_t +httpGET(char *hostname, unsigned short port, char *prepath, char *path, char **buffer) +{ + char tmpbuf[BUFSIZ]; + struct hostent *hostent; + struct sockaddr_in sockaddr_in; + in_addr_t in_addr; + int sockfd; + char *request_template = "GET %s%s HTTP/1.0\r\nHost: %s\r\n\r\n"; + char request[MAX_REQUEST_LEN]; + int request_length; + int byteswrote = 0; + long long int totalbytes = 0; + int writeiter = 0; + request_length = snprintf(request, MAX_REQUEST_LEN, request_template, + prepath, path, hostname); + if ((hostent = gethostbyname(hostname)) == NULL) { + fprintf(stderr, "gethostbyname(%s) error", hostname); + return 1; + } + if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + perror("socket opening error"); + return 1; + } + in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent -> h_addr_list))); + sockaddr_in.sin_port = htons(port); + sockaddr_in.sin_addr.s_addr = in_addr; + sockaddr_in.sin_family = AF_INET; + if (connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) < 0) { + perror("socket connecting error"); + return 1; + } + if ((write(sockfd, request, request_length)) < 0) { + perror("sending request error"); + return 1; + } + *buffer = calloc(BUFSIZ, 1); + while ((byteswrote = read(sockfd, tmpbuf, BUFSIZ)) > 0) { + totalbytes += byteswrote; + strncat(*buffer, tmpbuf, byteswrote); + *buffer = realloc(*buffer, BUFSIZ * (++writeiter + 1)); + } + close(sockfd); + return totalbytes; +} + +int +getResponseStatus(char *response, size_t responseSize) +{ + char *responsecopy = malloc(responseSize); + char *status; int ret; + strncpy(responsecopy, response, responseSize); + status = strtok(responsecopy, " "); + status = strtok(NULL, " "); + ret = atoi(status); + free(responsecopy); + return ret; +} + +int +parseResponseLine(char *response, char *value, char **buffer) +{ + char *tmpbuf = strtok(response, "\r"); + if (!strcmp(value, "HTTP")) { + *buffer = tmpbuf; + return 0; + } + while (strcmp(tmpbuf, "\n")) { + tmpbuf = strtok(NULL, "\r"); + unsigned short iter = -1; + char allOk = 1; + while (value[++iter]) { + if (value[iter] != tmpbuf[iter + 1]) { + allOk = 0; + break; + } + else allOk = 1; + } + if (allOk) { + iter += 2; + int i2 = 1; + *buffer = malloc(i2); + while (tmpbuf[iter]) { + (*buffer)[i2 - 1] = tmpbuf[++iter]; + *buffer = realloc(*buffer, ++i2); + } + (*buffer)[i2 - 2] = 0; + return 0; + } + } + return -1; +} + +char * +truncateHeader(char *response) +{ + return strstr(response, "\r\n\r\n") + 4; +} + +int +getHeaderLength(char *response) +{ + return strstr(response, "\r\n\r\n") - response; +} + +#endif diff --git a/statics.h b/statics.h @@ -0,0 +1,3 @@ +static char pwdir[] = "/usr/share/hmd"; +static char pidpath[] = "/tmp/hmd.pid"; +static char notifycmd[] = "notify-send"; diff --git a/tbkeys.h b/tbkeys.h diff --git a/termbox/bin/termbox.a b/termbox/bin/termbox.a Binary files differ. diff --git a/termbox/input.c b/termbox/input.c @@ -0,0 +1,319 @@ +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "term.h" + +#define BUFFER_SIZE_MAX 16 + +// if s1 starts with s2 returns 1, else 0 +static int starts_with(const char* s1, const char* s2) +{ + // nice huh? + while (*s2) + { + if (*s1++ != *s2++) + { + return 0; + } + } + + return 1; +} + +static int parse_mouse_event(struct tb_event* event, const char* buf, int len) +{ + if ((len >= 6) && starts_with(buf, "\033[M")) + { + // X10 mouse encoding, the simplest one + // \033 [ M Cb Cx Cy + int b = buf[3] - 32; + + switch (b & 3) + { + case 0: + if ((b & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_UP; + } + else + { + event->key = TB_KEY_MOUSE_LEFT; + } + + break; + + case 1: + if ((b & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_DOWN; + } + else + { + event->key = TB_KEY_MOUSE_MIDDLE; + } + + break; + + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + + default: + return -6; + } + + event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default + + if ((b & 32) != 0) + { + event->mod |= TB_MOD_MOTION; + } + + // the coord is 1,1 for upper left + event->x = (uint8_t)buf[4] - 1 - 32; + event->y = (uint8_t)buf[5] - 1 - 32; + return 6; + } + else if (starts_with(buf, "\033[<") || starts_with(buf, "\033[")) + { + // xterm 1006 extended mode or urxvt 1015 extended mode + // xterm: \033 [ < Cb ; Cx ; Cy (M or m) + // urxvt: \033 [ Cb ; Cx ; Cy M + int i, mi = -1, starti = -1; + int isM, isU, s1 = -1, s2 = -1; + int n1 = 0, n2 = 0, n3 = 0; + + for (i = 0; i < len; i++) + { + // We search the first (s1) and the last (s2) ';' + if (buf[i] == ';') + { + if (s1 == -1) + { + s1 = i; + } + + s2 = i; + } + + // We search for the first 'm' or 'M' + if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) + { + mi = i; + break; + } + } + + if (mi == -1) + { + return 0; + } + + // whether it's a capital M or not + isM = (buf[mi] == 'M'); + + if (buf[2] == '<') + { + isU = 0; + starti = 3; + } + else + { + isU = 1; + starti = 2; + } + + if (s1 == -1 || s2 == -1 || s1 == s2) + { + return 0; + } + + n1 = strtoul(&buf[starti], NULL, 10); + n2 = strtoul(&buf[s1 + 1], NULL, 10); + n3 = strtoul(&buf[s2 + 1], NULL, 10); + + if (isU) + { + n1 -= 32; + } + + switch (n1 & 3) + { + case 0: + if ((n1 & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_UP; + } + else + { + event->key = TB_KEY_MOUSE_LEFT; + } + + break; + + case 1: + if ((n1 & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_DOWN; + } + else + { + event->key = TB_KEY_MOUSE_MIDDLE; + } + + break; + + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + + default: + return mi + 1; + } + + if (!isM) + { + // on xterm mouse release is signaled by lowercase m + event->key = TB_KEY_MOUSE_RELEASE; + } + + event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default + + if ((n1 & 32) != 0) + { + event->mod |= TB_MOD_MOTION; + } + + event->x = (uint8_t)n2 - 1; + event->y = (uint8_t)n3 - 1; + return mi + 1; + } + + return 0; +} + +// convert escape sequence to event, and return consumed bytes on success (failure == 0) +static int parse_escape_seq(struct tb_event* event, const char* buf, int len) +{ + int mouse_parsed = parse_mouse_event(event, buf, len); + + if (mouse_parsed != 0) + { + return mouse_parsed; + } + + // it's pretty simple here, find 'starts_with' match and return success, else return failure + int i; + + for (i = 0; keys[i]; i++) + { + if (starts_with(buf, keys[i])) + { + event->ch = 0; + event->key = 0xFFFF - i; + return strlen(keys[i]); + } + } + + return 0; +} + +bool extract_event(struct tb_event* event, struct ringbuffer* inbuf, + int inputmode) +{ + char buf[BUFFER_SIZE_MAX + 1]; + int nbytes = ringbuffer_data_size(inbuf); + + if (nbytes > BUFFER_SIZE_MAX) + { + nbytes = BUFFER_SIZE_MAX; + } + + if (nbytes == 0) + { + return false; + } + + ringbuffer_read(inbuf, buf, nbytes); + buf[nbytes] = '\0'; + + if (buf[0] == '\033') + { + int n = parse_escape_seq(event, buf, nbytes); + + if (n != 0) + { + bool success = true; + + if (n < 0) + { + success = false; + n = -n; + } + + ringbuffer_pop(inbuf, 0, n); + return success; + } + else + { + // it's not escape sequence, then it's ALT or ESC, check inputmode + if (inputmode & TB_INPUT_ESC) + { + // if we're in escape mode, fill ESC event, pop buffer, return success + event->ch = 0; + event->key = TB_KEY_ESC; + event->mod = 0; + ringbuffer_pop(inbuf, 0, 1); + return true; + } + else if (inputmode & TB_INPUT_ALT) + { + // if we're in alt mode, set ALT modifier to event and redo parsing + event->mod = TB_MOD_ALT; + ringbuffer_pop(inbuf, 0, 1); + return extract_event(event, inbuf, inputmode); + } + + assert(!"never got here"); + } + } + + // if we're here, this is not an escape sequence and not an alt sequence + // so, it's a FUNCTIONAL KEY or a UNICODE character + + // first of all check if it's a functional key*/ + if ((unsigned char)buf[0] <= TB_KEY_SPACE || + (unsigned char)buf[0] == TB_KEY_BACKSPACE2) + { + // fill event, pop buffer, return success + event->ch = 0; + event->key = (uint16_t)buf[0]; + ringbuffer_pop(inbuf, 0, 1); + return true; + } + + // feh... we got utf8 here + + // check if there is all bytes + if (nbytes >= utf8_char_length(buf[0])) + { + // everything ok, fill event, pop buffer, return success + utf8_char_to_unicode(&event->ch, buf); + event->key = 0; + ringbuffer_pop(inbuf, 0, utf8_char_length(buf[0])); + return true; + } + + return false; +} diff --git a/termbox/license b/termbox/license @@ -0,0 +1,19 @@ +Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com> + +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. diff --git a/termbox/makefile b/termbox/makefile @@ -0,0 +1,40 @@ +NAME=termbox +CC=gcc +FLAGS=-std=c99 -pedantic -Wall -Werror -g + +OS:=$(shell uname -s) +ifeq ($(OS),Linux) + FLAGS+=-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE +endif + +BIND=bin +SRCD=src +OBJD=obj +INCL=-I$(SRCD) + +SRCS=$(SRCD)/termbox.c +SRCS+=$(SRCD)/input.c +SRCS+=$(SRCD)/memstream.c +SRCS+=$(SRCD)/ringbuffer.c +SRCS+=$(SRCD)/term.c +SRCS+=$(SRCD)/utf8.c + +OBJS:=$(patsubst $(SRCD)/%.c,$(OBJD)/$(SRCD)/%.o,$(SRCS)) + +.PHONY:all +all:$(BIND)/$(NAME).a + +$(OBJD)/%.o:%.c + @echo "building source object $@" + @mkdir -p $(@D) + @$(CC) $(INCL) $(FLAGS) -c -o $@ $< + +$(BIND)/$(NAME).a:$(OBJS) + @echo "compiling $@" + @mkdir -p $(BIND) + @ar rvs $(BIND)/$(NAME).a $(OBJS) + +clean: + @echo "cleaning workspace" + @rm -rf $(BIND) + @rm -rf $(OBJD) diff --git a/termbox/memstream.c b/termbox/memstream.c @@ -0,0 +1,36 @@ +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include "memstream.h" + +void memstream_init(struct memstream* s, int fd, void* buffer, size_t len) +{ + s->file = fd; + s->data = buffer; + s->pos = 0; + s->capa = len; +} + +void memstream_flush(struct memstream* s) +{ + write(s->file, s->data, s->pos); + s->pos = 0; +} + +void memstream_write(struct memstream* s, void* source, size_t len) +{ + unsigned char* data = source; + + if (s->pos + len > s->capa) + { + memstream_flush(s); + } + + memcpy(s->data + s->pos, data, len); + s->pos += len; +} + +void memstream_puts(struct memstream* s, const char* str) +{ + memstream_write(s, (void*) str, strlen(str)); +} diff --git a/termbox/memstream.h b/termbox/memstream.h @@ -0,0 +1,20 @@ +#ifndef H_MEMSTREAM +#define H_MEMSTREAM + +#include <stddef.h> +#include <stdio.h> + +struct memstream +{ + size_t pos; + size_t capa; + int file; + unsigned char* data; +}; + +void memstream_init(struct memstream* s, int fd, void* buffer, size_t len); +void memstream_flush(struct memstream* s); +void memstream_write(struct memstream* s, void* source, size_t len); +void memstream_puts(struct memstream* s, const char* str); + +#endif diff --git a/termbox/readme.md b/termbox/readme.md @@ -0,0 +1,57 @@ +# Termbox +[Termbox](https://github.com/nsf/termbox) +was a promising Text User Interface (TUI) library. +Unfortunately, its original author +[changed his mind](https://github.com/nsf/termbox/issues/37#issuecomment-261075481) +about consoles and despite the +[community's efforts](https://github.com/nsf/termbox/pull/104#issuecomment-300308156) +to keep the library's development going, preferred to let it die. Before it happened, +[some people](https://wiki.musl-libc.org/alternatives.html) +already noticed the robustness of the initial architecture +[became compromised](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff#commitcomment-3790714) +in a nonsensical refactoring frenzy. Now, the author refuses to merge features +like true-color support, invoking some +[spurious correlations](https://github.com/nsf/termbox/pull/104#issuecomment-300292223) +we will discuss no further. + +## The new Termbox-next +This fork was made to restore the codebase to its original quality (before +[66c3f91](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff)) +while providing all the functionnalities of the current implementation. +This was achieved by branching at +[a2e217f](https://github.com/nsf/termbox/commit/a2e217f0fb97e6bbd589136ea3945f9d5a123d81) +and cherry-picking all the commits up to +[d63b83a](https://github.com/nsf/termbox/commit/d63b83af04e0fd6da836bb8f37e5cec72a1dc95a) +if they weren't harmful. + +## Changes +A lot of things changed during the process: + - *waf*, the original build system, was completely removed from the + project and replaced by make. + - anything related to python was removed as well + +## Getting started +Termbox's interface only consists of 12 functions: +``` +tb_init() // initialization +tb_shutdown() // shutdown + +tb_width() // width of the terminal screen +tb_height() // height of the terminal screen + +tb_clear() // clear buffer +tb_present() // sync internal buffer with terminal + +tb_put_cell() +tb_change_cell() +tb_blit() // drawing functions + +tb_select_input_mode() // change input mode +tb_peek_event() // peek a keyboard event +tb_poll_event() // wait for a keyboard event +``` +See src/termbox.h header file for full detail. + +## TL;DR +`make` to build a static version of the lib under bin/termbox.a +`cd src/demo && make` to build the example programs in src/demo/ diff --git a/termbox/ringbuffer.c b/termbox/ringbuffer.c @@ -0,0 +1,195 @@ +#include "ringbuffer.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> // for ptrdiff_t + +int init_ringbuffer(struct ringbuffer* r, size_t size) +{ + r->buf = (char*)malloc(size); + + if (!r->buf) + { + return ERINGBUFFER_ALLOC_FAIL; + } + + r->size = size; + clear_ringbuffer(r); + + return 0; +} + +void free_ringbuffer(struct ringbuffer* r) +{ + free(r->buf); +} + +void clear_ringbuffer(struct ringbuffer* r) +{ + r->begin = 0; + r->end = 0; +} + +size_t ringbuffer_free_space(struct ringbuffer* r) +{ + if (r->begin == 0 && r->end == 0) + { + return r->size; + } + + if (r->begin < r->end) + { + return r->size - (r->end - r->begin) - 1; + } + else + { + return r->begin - r->end - 1; + } +} + +size_t ringbuffer_data_size(struct ringbuffer* r) +{ + if (r->begin == 0 && r->end == 0) + { + return 0; + } + + if (r->begin <= r->end) + { + return r->end - r->begin + 1; + } + else + { + return r->size - (r->begin - r->end) + 1; + } +} + + +void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size) +{ + if (ringbuffer_free_space(r) < size) + { + return; + } + + if (r->begin == 0 && r->end == 0) + { + memcpy(r->buf, data, size); + r->begin = r->buf; + r->end = r->buf + size - 1; + return; + } + + r->end++; + + if (r->begin < r->end) + { + if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) + { + // we can fit without cut + memcpy(r->end, data, size); + r->end += size - 1; + } + else + { + // make a cut + size_t s = r->buf + r->size - r->end; + memcpy(r->end, data, s); + size -= s; + memcpy(r->buf, (char*)data + s, size); + r->end = r->buf + size - 1; + } + } + else + { + memcpy(r->end, data, size); + r->end += size - 1; + } +} + +void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size) +{ + if (ringbuffer_data_size(r) < size) + { + return; + } + + int need_clear = 0; + + if (ringbuffer_data_size(r) == size) + { + need_clear = 1; + } + + if (r->begin < r->end) + { + if (data) + { + memcpy(data, r->begin, size); + } + + r->begin += size; + } + else + { + if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) + { + if (data) + { + memcpy(data, r->begin, size); + } + + r->begin += size; + } + else + { + size_t s = r->buf + r->size - r->begin; + + if (data) + { + memcpy(data, r->begin, s); + } + + size -= s; + + if (data) + { + memcpy((char*)data + s, r->buf, size); + } + + r->begin = r->buf + size; + } + } + + if (need_clear) + { + clear_ringbuffer(r); + } +} + +void ringbuffer_read(struct ringbuffer* r, void* data, size_t size) +{ + if (ringbuffer_data_size(r) < size) + { + return; + } + + if (r->begin < r->end) + { + memcpy(data, r->begin, size); + } + else + { + if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) + { + memcpy(data, r->begin, size); + } + else + { + size_t s = r->buf + r->size - r->begin; + memcpy(data, r->begin, s); + size -= s; + memcpy((char*)data + s, r->buf, size); + } + } +} diff --git a/termbox/ringbuffer.h b/termbox/ringbuffer.h @@ -0,0 +1,26 @@ +#ifndef H_RINGBUFFER +#define H_RINGBUFFER + +#include <stddef.h> + +#define ERINGBUFFER_ALLOC_FAIL -1 + +struct ringbuffer +{ + char* buf; + size_t size; + + char* begin; + char* end; +}; + +int init_ringbuffer(struct ringbuffer* r, size_t size); +void free_ringbuffer(struct ringbuffer* r); +void clear_ringbuffer(struct ringbuffer* r); +size_t ringbuffer_free_space(struct ringbuffer* r); +size_t ringbuffer_data_size(struct ringbuffer* r); +void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size); +void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size); +void ringbuffer_read(struct ringbuffer* r, void* data, size_t size); + +#endif diff --git a/termbox/term.c b/termbox/term.c @@ -0,0 +1,412 @@ +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "term.h" +#define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" +#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" + +#define EUNSUPPORTED_TERM -1 + +// rxvt-256color +static const char* rxvt_256color_keys[] = +{ + "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* rxvt_256color_funcs[] = +{ + "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +// Eterm +static const char* eterm_keys[] = +{ + "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* eterm_funcs[] = +{ + "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "", "", "", "", +}; + +// screen +static const char* screen_keys[] = +{ + "\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", + "\033OA", "\033OB", "\033OD", "\033OC", NULL +}; +static const char* screen_funcs[] = +{ + "\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", + "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +// rxvt-unicode +static const char* rxvt_unicode_keys[] = +{ + "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* rxvt_unicode_funcs[] = +{ + "\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", + "\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +// linux +static const char* linux_keys[] = +{ + "\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* linux_funcs[] = +{ + "", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", + "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", +}; + +// xterm +static const char* xterm_keys[] = +{ + "\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", + "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", + "\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB", + "\033OD", "\033OC", NULL +}; +static const char* xterm_funcs[] = +{ + "\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +struct term +{ + const char* name; + const char** keys; + const char** funcs; +}; + +static struct term terms[] = +{ + {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs}, + {"Eterm", eterm_keys, eterm_funcs}, + {"screen", screen_keys, screen_funcs}, + {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs}, + {"linux", linux_keys, linux_funcs}, + {"xterm", xterm_keys, xterm_funcs}, + {0, 0, 0}, +}; + +static int init_from_terminfo = 0; +const char** keys; +const char** funcs; + +static int try_compatible(const char* term, const char* name, + const char** tkeys, const char** tfuncs) +{ + if (strstr(term, name)) + { + keys = tkeys; + funcs = tfuncs; + return 0; + } + + return EUNSUPPORTED_TERM; +} + +static int init_term_builtin(void) +{ + int i; + const char* term = getenv("TERM"); + + if (term) + { + for (i = 0; terms[i].name; i++) + { + if (!strcmp(terms[i].name, term)) + { + keys = terms[i].keys; + funcs = terms[i].funcs; + return 0; + } + } + + // let's do some heuristic, maybe it's a compatible terminal + if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0) + { + return 0; + } + + // let's assume that 'cygwin' is xterm compatible + if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0) + { + return 0; + } + } + + return EUNSUPPORTED_TERM; +} + +// terminfo +static char* read_file(const char* file) +{ + FILE* f = fopen(file, "rb"); + + if (!f) + { + return 0; + } + + struct stat st; + + if (fstat(fileno(f), &st) != 0) + { + fclose(f); + return 0; + } + + char* data = malloc(st.st_size); + + if (!data) + { + fclose(f); + return 0; + } + + if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) + { + fclose(f); + free(data); + return 0; + } + + fclose(f); + return data; +} + +static char* terminfo_try_path(const char* path, const char* term) +{ + char tmp[4096]; + snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); + tmp[sizeof(tmp) - 1] = '\0'; + char* data = read_file(tmp); + + if (data) + { + return data; + } + + // fallback to darwin specific dirs structure + snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); + tmp[sizeof(tmp) - 1] = '\0'; + return read_file(tmp); +} + +static char* load_terminfo(void) +{ + char tmp[4096]; + const char* term = getenv("TERM"); + + if (!term) + { + return 0; + } + + // if TERMINFO is set, no other directory should be searched + const char* terminfo = getenv("TERMINFO"); + + if (terminfo) + { + return terminfo_try_path(terminfo, term); + } + + // next, consider ~/.terminfo + const char* home = getenv("HOME"); + + if (home) + { + snprintf(tmp, sizeof(tmp), "%s/.terminfo", home); + tmp[sizeof(tmp) - 1] = '\0'; + char* data = terminfo_try_path(tmp, term); + + if (data) + { + return data; + } + } + + // next, TERMINFO_DIRS + const char* dirs = getenv("TERMINFO_DIRS"); + + if (dirs) + { + snprintf(tmp, sizeof(tmp), "%s", dirs); + tmp[sizeof(tmp) - 1] = '\0'; + char* dir = strtok(tmp, ":"); + + while (dir) + { + const char* cdir = dir; + + if (strcmp(cdir, "") == 0) + { + cdir = "/usr/share/terminfo"; + } + + char* data = terminfo_try_path(cdir, term); + + if (data) + { + return data; + } + + dir = strtok(0, ":"); + } + } + + // fallback to /usr/share/terminfo + return terminfo_try_path("/usr/share/terminfo", term); +} + +#define TI_MAGIC 0432 +#define TI_ALT_MAGIC 542 +#define TI_HEADER_LENGTH 12 +#define TB_KEYS_NUM 22 + +static const char* terminfo_copy_string(char* data, int str, int table) +{ + const int16_t off = *(int16_t*)(data + str); + const char* src = data + table + off; + int len = strlen(src); + char* dst = malloc(len + 1); + strcpy(dst, src); + return dst; +} + +const int16_t ti_funcs[] = +{ + 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, +}; + +const int16_t ti_keys[] = +{ + // apparently not a typo; 67 is F10 for whatever reason + 66, 68, 69, 70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, + 81, 87, 61, 79, 83, +}; + +int init_term(void) +{ + int i; + char* data = load_terminfo(); + + if (!data) + { + init_from_terminfo = 0; + return init_term_builtin(); + } + + int16_t* header = (int16_t*)data; + + const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2; + + if ((header[1] + header[2]) % 2) + { + // old quirk to align everything on word boundaries + header[2] += 1; + } + + const int str_offset = TI_HEADER_LENGTH + + header[1] + header[2] + number_sec_len * header[3]; + const int table_offset = str_offset + 2 * header[4]; + + keys = malloc(sizeof(const char*) * (TB_KEYS_NUM + 1)); + + for (i = 0; i < TB_KEYS_NUM; i++) + { + keys[i] = terminfo_copy_string(data, + str_offset + 2 * ti_keys[i], table_offset); + } + + keys[i] = NULL; + + funcs = malloc(sizeof(const char*) * T_FUNCS_NUM); + + // the last two entries are reserved for mouse. because the table offset is + // not there, the two entries have to fill in manually + for (i = 0; i < T_FUNCS_NUM - 2; i++) + { + funcs[i] = terminfo_copy_string(data, + str_offset + 2 * ti_funcs[i], table_offset); + } + + funcs[T_FUNCS_NUM - 2] = ENTER_MOUSE_SEQ; + funcs[T_FUNCS_NUM - 1] = EXIT_MOUSE_SEQ; + init_from_terminfo = 1; + free(data); + return 0; +} + +void shutdown_term(void) +{ + if (init_from_terminfo) + { + int i; + + for (i = 0; i < TB_KEYS_NUM; i++) + { + free((void*)keys[i]); + } + + // the last two entries are reserved for mouse. because the table offset + // is not there, the two entries have to fill in manually and do not + // need to be freed. + for (i = 0; i < T_FUNCS_NUM - 2; i++) + { + free((void*)funcs[i]); + } + + free(keys); + free(funcs); + } +} diff --git a/termbox/term.h b/termbox/term.h @@ -0,0 +1,38 @@ +#ifndef H_TERM +#define H_TERM + +#include "termbox.h" +#include "ringbuffer.h" +#include <stdbool.h> + +#define EUNSUPPORTED_TERM -1 + +enum +{ + T_ENTER_CA, + T_EXIT_CA, + T_SHOW_CURSOR, + T_HIDE_CURSOR, + T_CLEAR_SCREEN, + T_SGR0, + T_UNDERLINE, + T_BOLD, + T_BLINK, + T_REVERSE, + T_ENTER_KEYPAD, + T_EXIT_KEYPAD, + T_ENTER_MOUSE, + T_EXIT_MOUSE, + T_FUNCS_NUM, +}; + +extern const char** keys; +extern const char** funcs; + +// true on success, false on failure +bool extract_event(struct tb_event* event, struct ringbuffer* inbuf, + int inputmode); +int init_term(void); +void shutdown_term(void); + +#endif diff --git a/termbox/termbox.c b/termbox/termbox.c @@ -0,0 +1,886 @@ +#include "term.h" +#include "termbox.h" +#include "termplus.h" +#include "memstream.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdbool.h> +#include <sys/select.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> + +struct cellbuf +{ + int width; + int height; + struct tb_cell* cells; +}; + +#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)] +#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1) +#define LAST_COORD_INIT -1 + +static struct termios orig_tios; + +static struct cellbuf back_buffer; +static struct cellbuf front_buffer; +static unsigned char write_buffer_data[32 * 1024]; +static struct memstream write_buffer; + +static int termw = -1; +static int termh = -1; + +static int inputmode = TB_INPUT_ESC; +static int outputmode = TB_OUTPUT_NORMAL; + +static struct ringbuffer inbuf; + +static int out; +static FILE* in; + +static int out_fileno; +static int in_fileno; + +static int winch_fds[2]; + +static int lastx = LAST_COORD_INIT; +static int lasty = LAST_COORD_INIT; +static int cursor_x = -1; +static int cursor_y = -1; + +static uint32_t background = TB_DEFAULT; +static uint32_t foreground = TB_DEFAULT; + +static void write_cursor(int x, int y); +static void write_sgr(uint32_t fg, uint32_t bg); + +static void cellbuf_init(struct cellbuf* buf, int width, int height); +static void cellbuf_resize(struct cellbuf* buf, int width, int height); +static void cellbuf_clear(struct cellbuf* buf); +static void cellbuf_free(struct cellbuf* buf); + +static void update_size(void); +static void update_term_size(void); +static void send_attr(uint32_t fg, uint32_t bg); +static void send_char(int x, int y, uint32_t c); +static void send_clear(void); +static void sigwinch_handler(int xxx); +static int wait_fill_event(struct tb_event* event, struct timeval* timeout); + +// may happen in a different thread +static volatile int buffer_size_change_request; + +int tb_init_file(const char* name) +{ + out = open(name, O_WRONLY); + in = fopen(name, "r"); + + if (out == -1 || !in) + { + if (out != -1) + { + close(out); + } + + if (in) + { + fclose(in); + } + + return TB_EFAILED_TO_OPEN_TTY; + } + + out_fileno = out; + in_fileno = fileno(in); + + if (init_term() < 0) + { + close(out); + fclose(in); + + return TB_EUNSUPPORTED_TERMINAL; + } + + if (pipe(winch_fds) < 0) + { + close(out); + fclose(in); + + return TB_EPIPE_TRAP_ERROR; + } + + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigwinch_handler; + sa.sa_flags = 0; + sigaction(SIGWINCH, &sa, 0); + tcgetattr(out_fileno, &orig_tios); + + struct termios tios; + + memcpy(&tios, &orig_tios, sizeof(tios)); + tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP + | INLCR | IGNCR | ICRNL | IXON); + tios.c_oflag &= ~OPOST; + tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tios.c_cflag &= ~(CSIZE | PARENB); + tios.c_cflag |= CS8; + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + tcsetattr(out_fileno, TCSAFLUSH, &tios); + + memstream_init(&write_buffer, out_fileno, write_buffer_data, + sizeof(write_buffer_data)); + memstream_puts(&write_buffer, funcs[T_ENTER_CA]); + memstream_puts(&write_buffer, funcs[T_ENTER_KEYPAD]); + memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]); + send_clear(); + + update_term_size(); + cellbuf_init(&back_buffer, termw, termh); + cellbuf_init(&front_buffer, termw, termh); + cellbuf_clear(&back_buffer); + cellbuf_clear(&front_buffer); + init_ringbuffer(&inbuf, 4096); + return 0; +} + +int tb_init(void) +{ + return tb_init_file("/dev/tty"); +} + +void tb_shutdown(void) +{ + if (termw == -1) + { + fputs("tb_shutdown() should not be called twice.", stderr); + abort(); + } + + memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]); + memstream_puts(&write_buffer, funcs[T_SGR0]); + memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]); + memstream_puts(&write_buffer, funcs[T_EXIT_CA]); + memstream_puts(&write_buffer, funcs[T_EXIT_KEYPAD]); + memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]); + memstream_flush(&write_buffer); + tcsetattr(out_fileno, TCSAFLUSH, &orig_tios); + + shutdown_term(); + close(out); + fclose(in); + close(winch_fds[0]); + close(winch_fds[1]); + + cellbuf_free(&back_buffer); + cellbuf_free(&front_buffer); + free_ringbuffer(&inbuf); + termw = termh = -1; +} + +void tb_present(void) +{ + int x, y, w, i; + struct tb_cell* back, *front; + + // invalidate cursor position + lastx = LAST_COORD_INIT; + lasty = LAST_COORD_INIT; + + if (buffer_size_change_request) + { + update_size(); + buffer_size_change_request = 0; + } + + for (y = 0; y < front_buffer.height; ++y) + { + for (x = 0; x < front_buffer.width;) + { + back = &CELL(&back_buffer, x, y); + front = &CELL(&front_buffer, x, y); + w = wcwidth(back->ch); + + if (w < 1) + { + w = 1; + } + + if (memcmp(back, front, sizeof(struct tb_cell)) == 0) + { + x += w; + continue; + } + + memcpy(front, back, sizeof(struct tb_cell)); + send_attr(back->fg, back->bg); + + if (w > 1 && x >= front_buffer.width - (w - 1)) + { + // Not enough room for wide ch, so send spaces + for (i = x; i < front_buffer.width; ++i) + { + send_char(i, y, ' '); + } + } + else + { + send_char(x, y, back->ch); + + for (i = 1; i < w; ++i) + { + front = &CELL(&front_buffer, x + i, y); + front->ch = 0; + front->fg = back->fg; + front->bg = back->bg; + } + } + + x += w; + } + } + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) + { + write_cursor(cursor_x, cursor_y); + } + + memstream_flush(&write_buffer); +} + +void tb_set_cursor(int cx, int cy) +{ + if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy)) + { + memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]); + } + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy)) + { + memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]); + } + + cursor_x = cx; + cursor_y = cy; + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) + { + write_cursor(cursor_x, cursor_y); + } +} + +void tb_put_cell(int x, int y, const struct tb_cell* cell) +{ + if ((unsigned)x >= (unsigned)back_buffer.width) + { + return; + } + + if ((unsigned)y >= (unsigned)back_buffer.height) + { + return; + } + + CELL(&back_buffer, x, y) = *cell; +} + +void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg) +{ + struct tb_cell c = {ch, fg, bg}; + tb_put_cell(x, y, &c); +} + +void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells) +{ + if (x + w < 0 || x >= back_buffer.width) + { + return; + } + + if (y + h < 0 || y >= back_buffer.height) + { + return; + } + + int xo = 0, yo = 0, ww = w, hh = h; + + if (x < 0) + { + xo = -x; + ww -= xo; + x = 0; + } + + if (y < 0) + { + yo = -y; + hh -= yo; + y = 0; + } + + if (ww > back_buffer.width - x) + { + ww = back_buffer.width - x; + } + + if (hh > back_buffer.height - y) + { + hh = back_buffer.height - y; + } + + int sy; + struct tb_cell* dst = &CELL(&back_buffer, x, y); + const struct tb_cell* src = cells + yo * w + xo; + size_t size = sizeof(struct tb_cell) * ww; + + for (sy = 0; sy < hh; ++sy) + { + memcpy(dst, src, size); + dst += back_buffer.width; + src += w; + } +} + +struct tb_cell* tb_cell_buffer(void) +{ + return back_buffer.cells; +} + +int tb_poll_event(struct tb_event* event) +{ + return wait_fill_event(event, 0); +} + +int tb_peek_event(struct tb_event* event, int timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; + return wait_fill_event(event, &tv); +} + +int tb_width(void) +{ + return termw; +} + +int tb_height(void) +{ + return termh; +} + +void tb_clear(void) +{ + if (buffer_size_change_request) + { + update_size(); + buffer_size_change_request = 0; + } + + cellbuf_clear(&back_buffer); +} + +int tb_select_input_mode(int mode) +{ + if (mode) + { + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) + { + mode |= TB_INPUT_ESC; + } + + // technically termbox can handle that, but let's be nice + // and show here what mode is actually used + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) + { + mode &= ~TB_INPUT_ALT; + } + + inputmode = mode; + + if (mode & TB_INPUT_MOUSE) + { + memstream_puts(&write_buffer, funcs[T_ENTER_MOUSE]); + memstream_flush(&write_buffer); + } + else + { + memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]); + memstream_flush(&write_buffer); + } + } + + return inputmode; +} + +int tb_select_output_mode(int mode) +{ + if (mode) + { + outputmode = mode; + } + + return outputmode; +} + +void tb_set_clear_attributes(uint32_t fg, uint32_t bg) +{ + foreground = fg; + background = bg; +} + +static unsigned convertnum(uint32_t num, char* buf) +{ + unsigned i, l = 0; + int ch; + + do + { + buf[l++] = '0' + (num % 10); + num /= 10; + } + while (num); + + for (i = 0; i < l / 2; i++) + { + ch = buf[i]; + buf[i] = buf[l - 1 - i]; + buf[l - 1 - i] = ch; + } + + return l; +} + +#define WRITE_LITERAL(X) memstream_write(&write_buffer, (X), sizeof(X) -1) +#define WRITE_INT(X) memstream_write(&write_buffer, buf, convertnum((X), buf)) + +static void write_cursor(int x, int y) +{ + char buf[32]; + WRITE_LITERAL("\033["); + WRITE_INT(y + 1); + WRITE_LITERAL(";"); + WRITE_INT(x + 1); + WRITE_LITERAL("H"); +} + +static void write_sgr(uint32_t fg, uint32_t bg) +{ + char buf[32]; + + if (outputmode != TB_OUTPUT_TRUECOLOR && fg == TB_DEFAULT && bg == TB_DEFAULT) + { + return; + } + + switch (outputmode) + { + case TB_OUTPUT_TRUECOLOR: + WRITE_LITERAL("\033[38;2;"); + WRITE_INT(fg >> 16 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(fg >> 8 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(fg & 0xFF); + WRITE_LITERAL(";48;2;"); + WRITE_INT(bg >> 16 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(bg >> 8 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(bg & 0xFF); + WRITE_LITERAL("m"); + break; + + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: + WRITE_LITERAL("\033["); + + if (fg != TB_DEFAULT) + { + WRITE_LITERAL("38;5;"); + WRITE_INT(fg); + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL(";"); + } + } + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL("48;5;"); + WRITE_INT(bg); + } + + WRITE_LITERAL("m"); + break; + + case TB_OUTPUT_NORMAL: + default: + WRITE_LITERAL("\033["); + + if (fg != TB_DEFAULT) + { + WRITE_LITERAL("3"); + WRITE_INT(fg - 1); + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL(";"); + } + } + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL("4"); + WRITE_INT(bg - 1); + } + + WRITE_LITERAL("m"); + break; + } +} + +static void cellbuf_init(struct cellbuf* buf, int width, int height) +{ + buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height); + assert(buf->cells); + buf->width = width; + buf->height = height; +} + +static void cellbuf_resize(struct cellbuf* buf, int width, int height) +{ + if (buf->width == width && buf->height == height) + { + return; + } + + int oldw = buf->width; + int oldh = buf->height; + struct tb_cell* oldcells = buf->cells; + + cellbuf_init(buf, width, height); + cellbuf_clear(buf); + + int minw = (width < oldw) ? width : oldw; + int minh = (height < oldh) ? height : oldh; + int i; + + for (i = 0; i < minh; ++i) + { + struct tb_cell* csrc = oldcells + (i * oldw); + struct tb_cell* cdst = buf->cells + (i * width); + memcpy(cdst, csrc, sizeof(struct tb_cell) * minw); + } + + free(oldcells); +} + +static void cellbuf_clear(struct cellbuf* buf) +{ + int i; + int ncells = buf->width * buf->height; + + for (i = 0; i < ncells; ++i) + { + buf->cells[i].ch = ' '; + buf->cells[i].fg = foreground; + buf->cells[i].bg = background; + } +} + +static void cellbuf_free(struct cellbuf* buf) +{ + free(buf->cells); +} + +static void get_term_size(int* w, int* h) +{ + struct winsize sz; + memset(&sz, 0, sizeof(sz)); + + ioctl(out_fileno, TIOCGWINSZ, &sz); + + if (w) + { + *w = sz.ws_col; + } + + if (h) + { + *h = sz.ws_row; + } +} + +static void update_term_size(void) +{ + struct winsize sz; + memset(&sz, 0, sizeof(sz)); + + ioctl(out_fileno, TIOCGWINSZ, &sz); + + termw = sz.ws_col; + termh = sz.ws_row; +} + +static void send_attr(uint32_t fg, uint32_t bg) +{ +#define LAST_ATTR_INIT 0xFFFFFFFF + static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT; + + if (fg != lastfg || bg != lastbg) + { + memstream_puts(&write_buffer, funcs[T_SGR0]); + uint32_t fgcol; + uint32_t bgcol; + + switch (outputmode) + { + case TB_OUTPUT_TRUECOLOR: + fgcol = fg; + bgcol = bg; + break; + + case TB_OUTPUT_256: + fgcol = fg & 0xFF; + bgcol = bg & 0xFF; + break; + + case TB_OUTPUT_216: + fgcol = fg & 0xFF; + + if (fgcol > 215) + { + fgcol = 7; + } + + bgcol = bg & 0xFF; + + if (bgcol > 215) + { + bgcol = 0; + } + + fgcol += 0x10; + bgcol += 0x10; + break; + + case TB_OUTPUT_GRAYSCALE: + fgcol = fg & 0xFF; + + if (fgcol > 23) + { + fgcol = 23; + } + + bgcol = bg & 0xFF; + + if (bgcol > 23) + { + bgcol = 0; + } + + fgcol += 0xe8; + bgcol += 0xe8; + break; + + case TB_OUTPUT_NORMAL: + default: + fgcol = fg & 0x0F; + bgcol = bg & 0x0F; + } + + if (fg & TB_BOLD) + { + memstream_puts(&write_buffer, funcs[T_BOLD]); + } + + if (bg & TB_BOLD) + { + memstream_puts(&write_buffer, funcs[T_BLINK]); + } + + if (fg & TB_UNDERLINE) + { + memstream_puts(&write_buffer, funcs[T_UNDERLINE]); + } + + if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) + { + memstream_puts(&write_buffer, funcs[T_REVERSE]); + } + + write_sgr(fgcol, bgcol); + + lastfg = fg; + lastbg = bg; + } +} + +static void send_char(int x, int y, uint32_t c) +{ + char buf[7]; + int bw = utf8_unicode_to_char(buf, c); + buf[bw] = '\0'; + + if (x - 1 != lastx || y != lasty) + { + write_cursor(x, y); + } + + lastx = x; + lasty = y; + + if (!c) + { + buf[0] = ' '; // replace 0 with whitespace + } + + memstream_puts(&write_buffer, buf); +} + +static void send_clear(void) +{ + send_attr(foreground, background); + memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]); + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) + { + write_cursor(cursor_x, cursor_y); + } + + memstream_flush(&write_buffer); + + // we need to invalidate cursor position too and these two vars are + // used only for simple cursor positioning optimization, cursor + // actually may be in the correct place, but we simply discard + // optimization once and it gives us simple solution for the case when + // cursor moved + lastx = LAST_COORD_INIT; + lasty = LAST_COORD_INIT; +} + +static void sigwinch_handler(int xxx) +{ + (void) xxx; + const int zzz = 1; + write(winch_fds[1], &zzz, sizeof(int)); +} + +static void update_size(void) +{ + update_term_size(); + cellbuf_resize(&back_buffer, termw, termh); + cellbuf_resize(&front_buffer, termw, termh); + cellbuf_clear(&front_buffer); + send_clear(); +} + +static int wait_fill_event(struct tb_event* event, struct timeval* timeout) +{ +#define ENOUGH_DATA_FOR_INPUT_PARSING 128 + int result; + char buf[ENOUGH_DATA_FOR_INPUT_PARSING]; + fd_set events; + memset(event, 0, sizeof(struct tb_event)); + + // try to extract event from input buffer, return on success + event->type = TB_EVENT_KEY; + + if (extract_event(event, &inbuf, inputmode)) + { + return event->type; + } + + // it looks like input buffer is incomplete, let's try the short path + size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in); + + if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in)) + { + clearerr(in); + } + + if (r > 0) + { + if (ringbuffer_free_space(&inbuf) < r) + { + return -1; + } + + ringbuffer_push(&inbuf, buf, r); + + if (extract_event(event, &inbuf, inputmode)) + { + return event->type; + } + } + + // no stuff in FILE's internal buffer, block in select + while (1) + { + FD_ZERO(&events); + FD_SET(in_fileno, &events); + FD_SET(winch_fds[0], &events); + int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno; + result = select(maxfd + 1, &events, 0, 0, timeout); + + if (!result) + { + return 0; + } + + if (FD_ISSET(in_fileno, &events)) + { + event->type = TB_EVENT_KEY; + size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in); + + if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in)) + { + clearerr(in); + } + + if (r == 0) + { + continue; + } + + // if there is no free space in input buffer, return error + if (ringbuffer_free_space(&inbuf) < r) + { + return -1; + } + + // fill buffer + ringbuffer_push(&inbuf, buf, r); + + if (extract_event(event, &inbuf, inputmode)) + { + return event->type; + } + } + + if (FD_ISSET(winch_fds[0], &events)) + { + event->type = TB_EVENT_RESIZE; + int zzz = 0; + read(winch_fds[0], &zzz, sizeof(int)); + buffer_size_change_request = 1; + get_term_size(&event->w, &event->h); + return TB_EVENT_RESIZE; + } + } +} diff --git a/termbox/termbox.h b/termbox/termbox.h @@ -0,0 +1,307 @@ +#ifndef H_TERMBOX +#define H_TERMBOX +#include <stdint.h> + +// shared objects +#if __GNUC__ >= 4 + #define SO_IMPORT __attribute__((visibility("default"))) +#else + #define SO_IMPORT +#endif + +// c++ +#ifdef __cplusplus +extern "C" { +#endif + +// Key constants. See also struct tb_event's key field. +// These are a safe subset of terminfo keys, which exist on all popular +// terminals. Termbox uses only them to stay truly portable. +#define TB_KEY_F1 (0xFFFF-0) +#define TB_KEY_F2 (0xFFFF-1) +#define TB_KEY_F3 (0xFFFF-2) +#define TB_KEY_F4 (0xFFFF-3) +#define TB_KEY_F5 (0xFFFF-4) +#define TB_KEY_F6 (0xFFFF-5) +#define TB_KEY_F7 (0xFFFF-6) +#define TB_KEY_F8 (0xFFFF-7) +#define TB_KEY_F9 (0xFFFF-8) +#define TB_KEY_F10 (0xFFFF-9) +#define TB_KEY_F11 (0xFFFF-10) +#define TB_KEY_F12 (0xFFFF-11) +#define TB_KEY_INSERT (0xFFFF-12) +#define TB_KEY_DELETE (0xFFFF-13) +#define TB_KEY_HOME (0xFFFF-14) +#define TB_KEY_END (0xFFFF-15) +#define TB_KEY_PGUP (0xFFFF-16) +#define TB_KEY_PGDN (0xFFFF-17) +#define TB_KEY_ARROW_UP (0xFFFF-18) +#define TB_KEY_ARROW_DOWN (0xFFFF-19) +#define TB_KEY_ARROW_LEFT (0xFFFF-20) +#define TB_KEY_ARROW_RIGHT (0xFFFF-21) +#define TB_KEY_MOUSE_LEFT (0xFFFF-22) +#define TB_KEY_MOUSE_RIGHT (0xFFFF-23) +#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24) +#define TB_KEY_MOUSE_RELEASE (0xFFFF-25) +#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26) +#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) + +// These are all ASCII code points below SPACE character and a BACKSPACE key. +#define TB_KEY_CTRL_TILDE 0x00 +#define TB_KEY_CTRL_2 0x00 // clash with 'CTRL_TILDE' +#define TB_KEY_CTRL_A 0x01 +#define TB_KEY_CTRL_B 0x02 +#define TB_KEY_CTRL_C 0x03 +#define TB_KEY_CTRL_D 0x04 +#define TB_KEY_CTRL_E 0x05 +#define TB_KEY_CTRL_F 0x06 +#define TB_KEY_CTRL_G 0x07 +#define TB_KEY_BACKSPACE 0x08 +#define TB_KEY_CTRL_H 0x08 // clash with 'CTRL_BACKSPACE' +#define TB_KEY_TAB 0x09 +#define TB_KEY_CTRL_I 0x09 // clash with 'TAB' +#define TB_KEY_CTRL_J 0x0A +#define TB_KEY_CTRL_K 0x0B +#define TB_KEY_CTRL_L 0x0C +#define TB_KEY_ENTER 0x0D +#define TB_KEY_CTRL_M 0x0D // clash with 'ENTER' +#define TB_KEY_CTRL_N 0x0E +#define TB_KEY_CTRL_O 0x0F +#define TB_KEY_CTRL_P 0x10 +#define TB_KEY_CTRL_Q 0x11 +#define TB_KEY_CTRL_R 0x12 +#define TB_KEY_CTRL_S 0x13 +#define TB_KEY_CTRL_T 0x14 +#define TB_KEY_CTRL_U 0x15 +#define TB_KEY_CTRL_V 0x16 +#define TB_KEY_CTRL_W 0x17 +#define TB_KEY_CTRL_X 0x18 +#define TB_KEY_CTRL_Y 0x19 +#define TB_KEY_CTRL_Z 0x1A +#define TB_KEY_ESC 0x1B +#define TB_KEY_CTRL_LSQ_BRACKET 0x1B // clash with 'ESC' +#define TB_KEY_CTRL_3 0x1B // clash with 'ESC' +#define TB_KEY_CTRL_4 0x1C +#define TB_KEY_CTRL_BACKSLASH 0x1C // clash with 'CTRL_4' +#define TB_KEY_CTRL_5 0x1D +#define TB_KEY_CTRL_RSQ_BRACKET 0x1D // clash with 'CTRL_5' +#define TB_KEY_CTRL_6 0x1E +#define TB_KEY_CTRL_7 0x1F +#define TB_KEY_CTRL_SLASH 0x1F // clash with 'CTRL_7' +#define TB_KEY_CTRL_UNDERSCORE 0x1F // clash with 'CTRL_7' +#define TB_KEY_SPACE 0x20 +#define TB_KEY_BACKSPACE2 0x7F +#define TB_KEY_CTRL_8 0x7F // clash with 'BACKSPACE2' + +// These are non-existing ones. +// #define TB_KEY_CTRL_1 clash with '1' +// #define TB_KEY_CTRL_9 clash with '9' +// #define TB_KEY_CTRL_0 clash with '0' + +// Alt modifier constant, see tb_event.mod field and tb_select_input_mode function. +// Mouse-motion modifier +#define TB_MOD_ALT 0x01 +#define TB_MOD_MOTION 0x02 + +// Colors (see struct tb_cell's fg and bg fields). +#define TB_DEFAULT 0x00 +#define TB_BLACK 0x01 +#define TB_RED 0x02 +#define TB_GREEN 0x03 +#define TB_YELLOW 0x04 +#define TB_BLUE 0x05 +#define TB_MAGENTA 0x06 +#define TB_CYAN 0x07 +#define TB_WHITE 0x08 + +// Attributes, it is possible to use multiple attributes by combining them +// using bitwise OR ('|'). Although, colors cannot be combined. But you can +// combine attributes and a single color. See also struct tb_cell's fg and bg +// fields. +#define TB_BOLD 0x01000000 +#define TB_UNDERLINE 0x02000000 +#define TB_REVERSE 0x04000000 + +// A cell, single conceptual entity on the terminal screen. The terminal screen +// is basically a 2d array of cells. It has the following fields: +// - 'ch' is a unicode character +// - 'fg' foreground color and attributes +// - 'bg' background color and attributes +struct tb_cell +{ + uint32_t ch; + uint32_t fg; + uint32_t bg; +}; + +#define TB_EVENT_KEY 1 +#define TB_EVENT_RESIZE 2 +#define TB_EVENT_MOUSE 3 + +// An event, single interaction from the user. The 'mod' and 'ch' fields are +// valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type' +// is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is +// TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY +// or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only +// one of them can be non-zero at a time. +struct tb_event +{ + uint8_t type; + uint8_t mod; // modifiers to either 'key' or 'ch' below + uint16_t key; // one of the TB_KEY_* constants + uint32_t ch; // unicode character + int32_t w; + int32_t h; + int32_t x; + int32_t y; +}; + +// Error codes returned by tb_init(). All of them are self-explanatory, except +// the pipe trap error. Termbox uses unix pipes in order to deliver a message +// from a signal handler (SIGWINCH) to the main event reading loop. Honestly in +// most cases you should just check the returned code as < 0. +#define TB_EUNSUPPORTED_TERMINAL -1 +#define TB_EFAILED_TO_OPEN_TTY -2 +#define TB_EPIPE_TRAP_ERROR -3 + +// Initializes the termbox library. This function should be called before any +// other functions. Function tb_init is same as tb_init_file("/dev/tty"). After successful initialization, the library must be +// finalized using the tb_shutdown() function. +SO_IMPORT int tb_init(void); +SO_IMPORT int tb_init_file(const char* name); +SO_IMPORT void tb_shutdown(void); + +// Returns the size of the internal back buffer (which is the same as +// terminal's window size in characters). The internal buffer can be resized +// after tb_clear() or tb_present() function calls. Both dimensions have an +// unspecified negative value when called before tb_init() or after +// tb_shutdown(). +SO_IMPORT int tb_width(void); +SO_IMPORT int tb_height(void); + +// Clears the internal back buffer using TB_DEFAULT color or the +// color/attributes set by tb_set_clear_attributes() function. +SO_IMPORT void tb_clear(void); +SO_IMPORT void tb_set_clear_attributes(uint32_t fg, uint32_t bg); + +// Synchronizes the internal back buffer with the terminal. +SO_IMPORT void tb_present(void); + +#define TB_HIDE_CURSOR -1 + +// Sets the position of the cursor. Upper-left character is (0, 0). If you pass +// TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor +// is hidden by default. +SO_IMPORT void tb_set_cursor(int cx, int cy); + +// Changes cell's parameters in the internal back buffer at the specified +// position. +SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell* cell); +SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, + uint32_t bg); + +// Copies the buffer from 'cells' at the specified position, assuming the +// buffer is a two-dimensional array of size ('w' x 'h'), represented as a +// one-dimensional buffer containing lines of cells starting from the top. +// (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own) +SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells); + +// Returns a pointer to internal cell back buffer. You can get its dimensions +// using tb_width() and tb_height() functions. The pointer stays valid as long +// as no tb_clear() and tb_present() calls are made. The buffer is +// one-dimensional buffer containing lines of cells starting from the top. +SO_IMPORT struct tb_cell* tb_cell_buffer(void); + +#define TB_INPUT_CURRENT 0 // 000 +#define TB_INPUT_ESC 1 // 001 +#define TB_INPUT_ALT 2 // 010 +#define TB_INPUT_MOUSE 4 // 100 + +// Sets the termbox input mode. Termbox has two input modes: +// 1. Esc input mode. +// When ESC sequence is in the buffer and it doesn't match any known +// ESC sequence => ESC means TB_KEY_ESC. +// 2. Alt input mode. +// When ESC sequence is in the buffer and it doesn't match any known +// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event. +// +// You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the +// modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes +// were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some +// reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it +// will behave as if only TB_INPUT_ESC was selected. +// +// If 'mode' is TB_INPUT_CURRENT, it returns the current input mode. +// +// Default termbox input mode is TB_INPUT_ESC. +SO_IMPORT int tb_select_input_mode(int mode); + +#define TB_OUTPUT_CURRENT 0 +#define TB_OUTPUT_NORMAL 1 +#define TB_OUTPUT_256 2 +#define TB_OUTPUT_216 3 +#define TB_OUTPUT_GRAYSCALE 4 +#define TB_OUTPUT_TRUECOLOR 5 + +// Sets the termbox output mode. Termbox has three output options: +// 1. TB_OUTPUT_NORMAL => [1..8] +// This mode provides 8 different colors: +// black, red, green, yellow, blue, magenta, cyan, white +// Shortcut: TB_BLACK, TB_RED, ... +// Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE +// +// Example usage: +// tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); +// +// 2. TB_OUTPUT_256 => [0..256] +// In this mode you can leverage the 256 terminal mode: +// 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL +// 0x08 - 0x0f: TB_* | TB_BOLD +// 0x10 - 0xe7: 216 different colors +// 0xe8 - 0xff: 24 different shades of grey +// +// Example usage: +// tb_change_cell(x, y, '@', 184, 240); +// tb_change_cell(x, y, '@', 0xb8, 0xf0); +// +// 3. TB_OUTPUT_216 => [0..216] +// This mode supports the 3rd range of the 256 mode only. +// But you don't need to provide an offset. +// +// 4. TB_OUTPUT_GRAYSCALE => [0..23] +// This mode supports the 4th range of the 256 mode only. +// But you dont need to provide an offset. +// +// 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xFFFFFF] +// This mode supports 24-bit true color. Format is 0xRRGGBB. +// +// Execute build/src/demo/output to see its impact on your terminal. +// +// If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode. +// +// Default termbox output mode is TB_OUTPUT_NORMAL. +SO_IMPORT int tb_select_output_mode(int mode); + +// Wait for an event up to 'timeout' milliseconds and fill the 'event' +// structure with it, when the event is available. Returns the type of the +// event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case +// there were no event during 'timeout' period. +SO_IMPORT int tb_peek_event(struct tb_event* event, int timeout); + +// Wait for an event forever and fill the 'event' structure with it, when the +// event is available. Returns the type of the event (one of TB_EVENT_ +// constants) or -1 if there was an error. +SO_IMPORT int tb_poll_event(struct tb_event* event); + +// Utility utf8 functions. +#define TB_EOF -1 +SO_IMPORT int utf8_char_length(char c); +SO_IMPORT int utf8_char_to_unicode(uint32_t* out, const char* c); +SO_IMPORT int utf8_unicode_to_char(char* out, uint32_t c); + +// c++ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/termbox/termplus.h b/termbox/termplus.h @@ -0,0 +1,90 @@ +#include <stdarg.h> +#include <stdio.h> + +#include "termbox.h" + +void tb_puts(const char *str, int x, int y, uint32_t fg, uint32_t bg); +void tb_printf(const char *str, int x, int y, uint32_t fg, uint32_t bg, ...); +int tb_drawbox(int ax, int ay, int bx, int by, uint32_t fg, uint32_t bg); +void tb_die(const char *fmt, ...); + +void +tb_puts(const char *str, int x, int y, uint32_t fg, uint32_t bg) +/* puts str string, first letter will be at x, y and color will be fg & bg */ +{ + while (*str) { + uint32_t uni; + str += utf8_char_to_unicode(&uni, str); + tb_change_cell(x, y, uni, fg, bg); + x++; + } +} + +void +tb_printf(const char *format, int x, int y, uint32_t fg, uint32_t bg, ...) +/* same as puts, but with format at the end (uses puts btw) */ +{ + va_list data; + char buf[BUFSIZ]; + va_start(data, bg); + vsnprintf(buf, BUFSIZ, format, data); + va_end(data); + tb_puts(buf, x, y, fg, bg); +} + +int +tb_drawbox(int ax, int ay, int bx, int by, uint32_t fg, uint32_t bg) +/* draws box from ax, ay to bx, by with fg and bg color */ +{ + int iter; + + if(ax >= bx || ay >= by) return -1; + + tb_change_cell(ax, ay, 0x250C, fg, bg); + tb_change_cell(bx, ay, 0x2510, fg, bg); + tb_change_cell(ax, by, 0x2514, fg, bg); + tb_change_cell(bx, by, 0x2518, fg, bg); + + iter = ax; + while(++iter < bx) + { + tb_change_cell(iter, ay, 0x2500, fg, bg); + tb_change_cell(iter, by, 0x2500, fg, bg); + } + + iter = ay; + while(++iter < by) + { + tb_change_cell(ax, iter, 0x2502, fg, bg); + tb_change_cell(bx, iter, 0x2502, fg, bg); + } + + tb_present(); + return 0; +} + +void +tb_die(const char *fmt, ...) +/* based on suckless' libsl die() + git.suckless.org/libsl + see /LICENSE_LIBSL for copyright details */ +{ + va_list ap; + tb_shutdown(); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if(fmt[0] && fmt[strlen(fmt) - 1] == ':') + { + fputc(' ', stderr); + perror(NULL); + } + else + { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/termbox/tools/astylerc b/termbox/tools/astylerc @@ -0,0 +1,27 @@ +--style=break +--indent=force-tab=4 +--indent-classes +--indent-switches +--indent-namespaces +--indent-after-parens +--indent-continuation=1 +--indent-preproc-block +--indent-preproc-define +--indent-preproc-cond +--indent-col1-comments +--min-conditional-indent=0 +--max-continuation-indent=40 +--break-blocks +--pad-oper +--pad-comma +--pad-header +--unpad-paren +--align-pointer=type +--align-reference=type +--break-one-line-headers +--add-braces +--attach-return-type +--attach-return-type-decl +--remove-comment-prefix +--max-code-length=80 +--mode=c diff --git a/termbox/tools/collect_terminfo.py b/termbox/tools/collect_terminfo.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python + +import sys, os, subprocess + +def escaped(s): + return s.replace("\033", "\\033") + +def tput(term, name): + try: + return subprocess.check_output(['tput', '-T%s' % term, name]).decode() + except subprocess.CalledProcessError as e: + return e.output.decode() + + +def w(s): + if s == None: + return + sys.stdout.write(s) + +terminals = { + 'xterm' : 'xterm', + 'rxvt-256color' : 'rxvt_256color', + 'rxvt-unicode' : 'rxvt_unicode', + 'linux' : 'linux', + 'Eterm' : 'eterm', + 'screen' : 'screen' +} + +keys = [ + "F1", "kf1", + "F2", "kf2", + "F3", "kf3", + "F4", "kf4", + "F5", "kf5", + "F6", "kf6", + "F7", "kf7", + "F8", "kf8", + "F9", "kf9", + "F10", "kf10", + "F11", "kf11", + "F12", "kf12", + "INSERT", "kich1", + "DELETE", "kdch1", + "HOME", "khome", + "END", "kend", + "PGUP", "kpp", + "PGDN", "knp", + "KEY_UP", "kcuu1", + "KEY_DOWN", "kcud1", + "KEY_LEFT", "kcub1", + "KEY_RIGHT", "kcuf1" +] + +funcs = [ + "T_ENTER_CA", "smcup", + "T_EXIT_CA", "rmcup", + "T_SHOW_CURSOR", "cnorm", + "T_HIDE_CURSOR", "civis", + "T_CLEAR_SCREEN", "clear", + "T_SGR0", "sgr0", + "T_UNDERLINE", "smul", + "T_BOLD", "bold", + "T_BLINK", "blink", + "T_REVERSE", "rev", + "T_ENTER_KEYPAD", "smkx", + "T_EXIT_KEYPAD", "rmkx" +] + +def iter_pairs(iterable): + iterable = iter(iterable) + while True: + yield (next(iterable), next(iterable)) + +def do_term(term, nick): + w("// %s\n" % term) + w("static const char *%s_keys[] = {\n\t" % nick) + for k, v in iter_pairs(keys): + w('"') + w(escaped(tput(term, v))) + w('",') + w(" 0\n};\n") + w("static const char *%s_funcs[] = {\n\t" % nick) + for k,v in iter_pairs(funcs): + w('"') + if v == "sgr": + w("\\033[3%d;4%dm") + elif v == "cup": + w("\\033[%d;%dH") + else: + w(escaped(tput(term, v))) + w('", ') + w("\n};\n\n") + +def do_terms(d): + w("static struct term {\n") + w("\tconst char *name;\n") + w("\tconst char **keys;\n") + w("\tconst char **funcs;\n") + w("} terms[] = {\n") + for k, v in d.items(): + w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v)) + w("\t{0, 0, 0},\n") + w("};\n") + +for k,v in terminals.items(): + do_term(k, v) + +do_terms(terminals) diff --git a/termbox/utf8.c b/termbox/utf8.c @@ -0,0 +1,106 @@ +#include "termbox.h" + +static const unsigned char utf8_length[256] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 +}; + +static const unsigned char utf8_mask[6] = +{ + 0x7F, + 0x1F, + 0x0F, + 0x07, + 0x03, + 0x01 +}; + +int utf8_char_length(char c) +{ + return utf8_length[(unsigned char)c]; +} + +int utf8_char_to_unicode(uint32_t* out, const char* c) +{ + if (*c == 0) + { + return TB_EOF; + } + + int i; + unsigned char len = utf8_char_length(*c); + unsigned char mask = utf8_mask[len - 1]; + uint32_t result = c[0] & mask; + + for (i = 1; i < len; ++i) + { + result <<= 6; + result |= c[i] & 0x3f; + } + + *out = result; + return (int)len; +} + +int utf8_unicode_to_char(char* out, uint32_t c) +{ + int len = 0; + int first; + int i; + + if (c < 0x80) + { + first = 0; + len = 1; + } + else if (c < 0x800) + { + first = 0xc0; + len = 2; + } + else if (c < 0x10000) + { + first = 0xe0; + len = 3; + } + else if (c < 0x200000) + { + first = 0xf0; + len = 4; + } + else if (c < 0x4000000) + { + first = 0xf8; + len = 5; + } + else + { + first = 0xfc; + len = 6; + } + + for (i = len - 1; i > 0; --i) + { + out[i] = (c & 0x3f) | 0x80; + c >>= 6; + } + + out[0] = c | first; + + return len; +} diff --git a/util.c b/util.c @@ -0,0 +1,55 @@ +/* See LICENSE_LIBSL file for copyright and license details. */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void +udie(const char *fmt, ...) { + va_list ap; + + fprintf(stderr, "\033[1;31m==> \033[0m"); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h @@ -0,0 +1,10 @@ +/* See LICENSE_LIBSL file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LEN(X) (sizeof(X) / sizeof((X)[0])) + +void *ecalloc(size_t nmemb, size_t size); +void die(const char *fmt, ...); +void udie(const char *fmt, ...);