commit 101a421d54e0a443b5497995e30073cb0dc14077
Author: Anton Konyahin <me@konyahin.xyz>
Date: Sun, 10 Mar 2024 17:13:47 +0300
initial commit
Diffstat:
A | LICENSE | | | 21 | +++++++++++++++++++++ |
A | Makefile | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 165 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | test.sh | | | 35 | +++++++++++++++++++++++++++++++++++ |
4 files changed, 260 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2024 Anton Konyahin me@konyahin.xyz
+
+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,39 @@
+.POSIX:
+.SUFFIXES:
+.PHONY: install uninstall clean
+
+BIN = flbng
+CC = cc
+CFLAGS = -Wall -Wextra -Werror -Os
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+OBJ = main.o
+
+all: $(BIN)
+
+$(BIN): $(OBJ)
+ $(CC) $(LDFLAGS) -o $(BIN) $(OBJ) $(LDLIBS)
+
+run: $(BIN)
+ ./$(BIN)
+
+test: $(BIN)
+ ./test.sh
+
+install: $(BIN)
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ install -m 775 $(BIN) $(DESTDIR)$(PREFIX)/bin/
+ install -m 644 $(BIN).1 $(DESTDIR)$(MANPREFIX)/man1/
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
+ rm -f $(DESTDIR)$(MANPREFIX)/man1/$(BIN).1
+
+clean:
+ rm -f $(BIN) *.o
+
+.SUFFIXES: .c .o
+.c.o:
+ $(CC) $(CFLAGS) -c $<
diff --git a/main.c b/main.c
@@ -0,0 +1,165 @@
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+struct screen_size {
+ unsigned short width;
+ unsigned short height;
+};
+
+struct data {
+ char **data;
+ size_t lines;
+ size_t max_width;
+};
+
+// global variable for terminal state restoring
+struct termios orig_term;
+
+void
+restoreTerminal() {
+ // show cursor
+ puts("\e[?25h");
+ if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig_term) < 0)
+ err(1, "can't restore terminal state");
+ // clear screen
+ puts("\x1b[2J");
+}
+
+void
+prepareTerminal() {
+ if (tcgetattr(STDOUT_FILENO, &orig_term) < 0)
+ err(1, "can't get terminal attributes");
+
+ atexit(restoreTerminal);
+
+ // havely inspired by https://github.com/antirez/kilo/blob/master/kilo.c
+ struct termios raw = orig_term;
+ raw.c_iflag &=
+ (unsigned) ~(BRKINT | INPCK | ISTRIP | ICRNL | IXON);
+ raw.c_oflag &= (unsigned) ~(OPOST);
+ raw.c_cflag |= (CS8);
+ raw.c_lflag &=
+ (unsigned) ~(ECHO | ICANON);
+ raw.c_cc[VMIN] = 0;
+ raw.c_cc[VTIME] = 1;
+
+ if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &raw) < 0)
+ err(1, "can't set terminal attributes");
+
+ // hide cursor
+ puts("\e[?25l");
+ // clear screen
+ puts("\x1b[2J");
+}
+
+void
+processInput() {
+ // read from /dev/tty, because standart input
+ // will be redirected
+ int input = open("/dev/tty", O_RDONLY);
+ char c = '\0';
+ while (1) {
+ ssize_t read_s = read(input, &c, 1);
+ if (read_s < 0)
+ err(1, "can't read from tty");
+ if (read_s == 0)
+ continue;
+ break;
+ }
+}
+
+struct screen_size
+getScreenSize() {
+ struct winsize size;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
+ return (struct screen_size) {
+ .width = size.ws_col,
+ .height = size.ws_row,
+ };
+}
+
+struct data
+getInput(struct screen_size s) {
+ struct data text;
+ text.data = calloc(sizeof(char*), s.height);
+ text.lines = 0;
+ text.max_width = 0;
+
+ while (1) {
+ char *line = NULL;
+ ssize_t len = 0;
+ size_t buf_len = 0;
+
+ if ((len = getline(&line, &buf_len, stdin)) < 0)
+ break;
+
+ // we can reach EOF here, if there is no delimiter on the last line
+ // if there is delimiter - we should remove it
+ if (!feof(stdin)) {
+ len -= 1;
+ line[len] = '\0';
+ }
+
+ if ((size_t) len > text.max_width) {
+ text.max_width = len;
+ if (len > s.width)
+ errx(1, "too long line for this terminal, should be shorter than %d", s.width);
+ }
+ text.data[text.lines] = line;
+ text.lines += 1;
+ if (text.lines > s.height)
+ errx(1, "too much lines for this terminal, should be less than %d", s.height);
+ }
+
+ if (ferror(stdin))
+ err(1, "can't read line from stdin");
+
+ if (text.lines == 0)
+ err(1, "empty input");
+
+ return text;
+}
+
+int
+main(void) {
+ // TODO: -h for help
+
+ struct screen_size s = getScreenSize();
+ struct data text = getInput(s);
+
+ size_t start_position = (s.height - text.lines) / 2;
+ int margin = (int) ((s.width - text.max_width) / 2);
+
+ prepareTerminal();
+
+ for (size_t i = 0; i < start_position; i++)
+ printf("\r\n");
+
+ for (size_t i = 0; i < text.lines; i++) {
+ if (margin)
+ printf("%*s", margin, " ");
+ printf("%s", text.data[i]);
+ if (i != text.lines - 1)
+ printf("\r\n");
+ }
+
+ for (size_t i = start_position + text.lines; i < s.height; i++)
+ printf("\r\n");
+
+ fflush(stdout);
+
+ processInput();
+
+ for (size_t i = 0; i < text.lines; i++)
+ free(text.data[i]);
+ free(text.data);
+
+ return 0;
+}
diff --git a/test.sh b/test.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env sh
+
+# no new line at the end of text
+printf "first normal line\nno new line at the end" | ./flbng
+
+# ascii art
+figlet flash bang test | ./flbng
+
+# random fortune text
+/usr/games/fortune | ./flbng
+
+# as much lines as terminal height
+lines() {
+ for i in $(seq $(tput lines))
+ do echo "$i"
+ done
+}
+lines | ./flbng
+
+# as much chars as terinal widht
+chars () {
+ for i in $(seq $(tput cols))
+ do printf "*"
+ done
+}
+chars | ./flbng
+
+./flbng <<EOF
+Some errors check. Should be:
+ flbng: too much lines for this terminal, should be less than X
+ flbng: too long line for this terminal, should be shorter than X
+EOF
+
+echo "0\n$(lines)" | ./flbng
+echo "*$(chars)" | ./flbng