From a5194e5a84db58dd0e851929c900ed40eb06113f Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Fri, 17 Sep 2021 14:36:25 +0200 Subject: [PATCH] Automatically resize active terminal to not overlap with keyboard --- .gitignore | 3 +- Makefile | 2 +- README.md | 2 +- main.c | 50 ++++++++- terminal.c | 294 +++++++++++++++++++++++++++++++++++++++++++++++++++++ terminal.h | 45 ++++++++ 6 files changed, 391 insertions(+), 5 deletions(-) create mode 100644 terminal.c create mode 100644 terminal.h diff --git a/.gitignore b/.gitignore index 56ded7b..a9863ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -*.o buffyboard +*.o +.vscode diff --git a/Makefile b/Makefile index c3e2cbb..f97e8d9 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmissing-prototypes -Wn LDFLAGS ?= -lm -linput BIN = buffyboard -MAINSRC = ./cursor.c ./layout.c ./libinput_multi.c ./main.c ./montserrat_extended_32.c ./sq2lv_layouts.c ./uinput_device.c +MAINSRC = ./cursor.c ./layout.c ./libinput_multi.c ./main.c ./montserrat_extended_32.c ./sq2lv_layouts.c ./terminal.c ./uinput_device.c include $(LVGL_DIR)/lvgl/lvgl.mk include $(LVGL_DIR)/lv_drivers/lv_drivers.mk diff --git a/README.md b/README.md index a9da5e4..24e2e34 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ While partially usable, buffyboard currently still is a work in progress. - Multi-layer keyboard layout including lowercase letters, uppercase letters, numbers and selected symbols (based on top three layers of [squeekboard's US terminal layout]) - Key chords with one or more modifiers terminated by a single non-modifier (e.g. `CTRL-c`) - Highlighting of active modifiers +- Automatic resizing (and later reset) of active VT to prevent overlap with keyboard ## To do - Show keyboard at the bottom of the screen instead of at the top (just need to set the offset once [lvgl/lv_drivers#164] is merged) -- Resize VT screen size to not overlap with keyboard - Support different screen rotations - Eliminate `libinput_multi.[ch]` once support for multiple input devices has been upstreamed to [lv_drivers] (see [lvgl/lv_drivers#151]) - Add remaining layers from [squeekboard's US terminal layout] (symbols and actions) diff --git a/main.c b/main.c index 03aefe3..559df8d 100644 --- a/main.c +++ b/main.c @@ -22,6 +22,7 @@ #include "layout.h" #include "libinput_multi.h" #include "sq2lv_layouts.h" +#include "terminal.h" #include "uinput_device.h" #include "lvgl/lvgl.h" @@ -29,6 +30,7 @@ #include "lv_drivers/indev/libinput_drv.h" #include +#include #include #include #include @@ -47,6 +49,7 @@ LV_FONT_DECLARE(montserrat_extended_32); * Static variables */ +static bool resize_terminals = false; static lv_obj_t *keyboard = NULL; static lv_style_t style_text_normal; @@ -55,6 +58,20 @@ static lv_style_t style_text_normal; * Static prototypes */ +/** + * Handle termination signals sent to the process. + * + * @param signum the signal's number + */ +static void sigaction_handler(int signum); + +/** + * Callback for the terminal resizing timer. + * + * @param timer the timer object + */ +static void terminal_resize_timer_cb(lv_timer_t *timer); + /** * Set the UI theme. * @@ -91,6 +108,19 @@ static void pop_checked_modifier_keys(lv_obj_t *keyboard); * Static functions */ +static void sigaction_handler(int signum) { + if (resize_terminals) { + bb_terminal_reset_all(); + } + exit(0); +} + +static void terminal_resize_timer_cb(lv_timer_t *timer) { + if (resize_terminals) { + bb_terminal_shrink_current(); + } +} + static void set_theme(bool is_dark) { lv_theme_default_init(NULL, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_CYAN), is_dark, &montserrat_extended_32); } @@ -195,8 +225,21 @@ static void keyboard_draw_part_begin_cb(lv_event_t *event) { * Main */ -int main(void) -{ +int main(void) { + /* Prepare for terminal resizing and reset */ + resize_terminals = bb_terminal_init(2.0f / 3.0f); + if (resize_terminals) { + /* Clean up on termination */ + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = sigaction_handler; + sigaction(SIGINT, &action, NULL); + sigaction(SIGTERM, &action, NULL); + + /* Resize current terminal */ + bb_terminal_shrink_current(); + } + /* Set up uinput device */ if (!bb_uinput_device_init(sq2lv_unique_scancodes, sq2lv_num_unique_scancodes)) { return 1; @@ -293,6 +336,9 @@ int main(void) /* Apply default keyboard layout */ bb_layout_switch_layout(keyboard, SQ2LV_LAYOUT_TERMINAL_US); + /* Start timer for periodically resizing terminals */ + lv_timer_create(terminal_resize_timer_cb, 1000, NULL); + /* Run lvgl in "tickless" mode */ while(1) { lv_task_handler(); diff --git a/terminal.c b/terminal.c new file mode 100644 index 0000000..96cb5b6 --- /dev/null +++ b/terminal.c @@ -0,0 +1,294 @@ +/** + * Copyright 2021 Johannes Marbach + * + * This file is part of buffyboard, hereafter referred to as the program. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "terminal.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + + +/** + * Static variables + */ + +static int current_fd = -1; +static int current_vt = -1; +static bool resized_vts[MAX_NR_CONSOLES]; +static float height_factor = 1; + +/** + * Static prototypes + */ + +/** + * Close the current file descriptor and reopen /dev/tty0. + * + * @return true if opening was successful, false otherwise + */ +static bool reopen_current_terminal(void); + +/** + * Close the current file descriptor. + */ +static void close_current_terminal(void); + +/** + * Get the currently active virtual terminal. + * + * @return number of the active VT (e.g. 7 for /dev/tty7) + */ +static int get_active_terminal(void); + +/** + * Retrieve a terminal's size. + * + * @param fd TTY file descriptor + * @param size pointer to winsize struct for writing the size into + * @return true if the operation was successful, false otherwise. On failure, errno will be set + * to the value set by the failed system call. + */ +static bool get_terminal_size(int fd, struct winsize *size); + +/** + * Update a terminal's size. + * + * @param fd TTY file descriptor + * @param size pointer to winsize struct for reading the new size from + * @return true if the operation was successful, false otherwise. On failure, errno will be set + * to the value set by the failed system call. + */ +static bool set_terminal_size(int fd, struct winsize *size); + +/** + * Shrink the height of a terminal by the current factor. + * + * @param fd TTY file descriptor + * @return true if the operation was successful, false otherwise + */ +static bool shrink_terminal(int fd); + +/** + * Reset the height of a terminal to the maximum. + * + * @param fd TTY file descriptor + * @param size pointer to winsize struct for writing the final size into + * @return true if the operation was successful, false otherwise + */ +static bool reset_terminal(int fd, struct winsize *size); + + +/** + * Static functions + */ + +static bool reopen_current_terminal(void) { + close_current_terminal(); + + current_fd = open("/dev/tty0", O_RDWR | O_NOCTTY); + if (current_fd < 0) { + perror("Could not open /dev/tty0"); + return false; + } + + return true; +} + +static void close_current_terminal(void) { + if (current_fd < 0) { + return; + } + + close(current_fd); + current_fd = -1; +} + +static int get_active_terminal(void) { + struct vt_stat stat; + if (ioctl(current_fd, VT_GETSTATE, &stat) != 0) { + perror("Could not retrieve current termimal state"); + return -1; + } + return stat.v_active; +} + +static bool get_terminal_size(int fd, struct winsize *size) { + if (ioctl(fd, TIOCGWINSZ, size) != 0) { + int errsv = errno; + perror("Could not retrieve current terminal size"); + errno = errsv; + return false; + } + return true; +} + +static bool set_terminal_size(int fd, struct winsize *size) { + if (ioctl(fd, TIOCSWINSZ, size) != 0) { + int errsv = errno; + perror("Could not update current terminal size"); + errno = errsv; + return false; + } + return true; +} + +static bool shrink_terminal(int fd) { + struct winsize size = { 0, 0, 0, 0 }; + + if (!reset_terminal(fd, &size)) { + perror("Could not shrink terminal size"); + return false; + } + + size.ws_row = floor((float)size.ws_row * height_factor); + if (!set_terminal_size(fd, &size)) { + perror("Could not shrink terminal size"); + return false; + } + + return true; +} + +static bool reset_terminal(int fd, struct winsize *size) { + if (!get_terminal_size(fd, size)) { + perror("Could not reset terminal size"); + return false; + } + + /* Test-resize by two rows. If the terminal is already maximised, this will fail and we can exit early. */ + size->ws_row += 2; + if (!set_terminal_size(fd, size)) { + bool is_max = (errno == EINVAL); + size->ws_row -= 2; + return is_max; + } + + size->ws_row = floor((float)size->ws_row / height_factor); + if (!set_terminal_size(fd, size)) { + if (errno != EINVAL) { + perror("Could not reset terminal size"); + return false; + } + + /* Size too large. Reduce by one row until it fits. */ + do { + size->ws_row -= 1; + } while (size->ws_row > 0 && !set_terminal_size(fd, size) && errno == EINVAL); + + if (errno != EINVAL || size->ws_row == 0) { + perror("Could not reset terminal size"); + return false; + } + } else { + /* Size fits but may not max out available space. Increase by one row until it doesn't fit anymore. */ + do { + size->ws_row += 1; + } while (set_terminal_size(fd, size)); + + if (errno != EINVAL) { + perror("Could not reset terminal size"); + return false; + } + + size->ws_row -= 1; + } + + return true; +} + + +/** + * Public functions + */ + +bool bb_terminal_init(float factor) { + if (!reopen_current_terminal()) { + perror("Could not prepare for terminal resizing"); + return false; + } + + current_vt = get_active_terminal(); + if (current_vt < 0) { + perror("Could not prepare for terminal resizing"); + return false; + } + + height_factor = factor; + return true; +} + +void bb_terminal_shrink_current(void) { + int active_vt = get_active_terminal(); + if (active_vt < 0) { + perror("Could not resize current terminal"); + return; + } + + if (active_vt < 0 || active_vt > MAX_NR_CONSOLES - 1) { + perror("Could not resize current terminal, index is out of bounds"); + return; + } + + if (resized_vts[active_vt - 1]) { + return; /* Already resized */ + } + + if (active_vt != current_vt) { + if (!reopen_current_terminal()) { + perror("Could not resize current terminal"); + return; + } + current_vt = active_vt; + } + + if (!shrink_terminal(current_fd)) { + perror("Could not resize current terminal"); + return; + } + + resized_vts[current_vt - 1] = true; +} + +void bb_terminal_reset_all(void) { + char device[16]; + struct winsize size = { 0, 0, 0, 0 }; + + for (int i = 0; i < MAX_NR_CONSOLES; ++i) { + if (!resized_vts[i]) { + continue; + } + + snprintf(device, 16, "/dev/tty%d", i + 1); + int fd = open(device, O_RDWR | O_NOCTTY); + if (fd < 0) { + perror("Could not reset TTY, unable to open TTY"); + continue; + } + + reset_terminal(fd, &size); + } +} diff --git a/terminal.h b/terminal.h new file mode 100644 index 0000000..77c96df --- /dev/null +++ b/terminal.h @@ -0,0 +1,45 @@ +/** + * Copyright 2021 Johannes Marbach + * + * This file is part of buffyboard, hereafter referred to as the program. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef BB_TERMINAL_H +#define BB_TERMINAL_H + +#include + +/** + * Prepare for resizing terminals by opening the current one. + * + * @param factor factor (between 0 and 1) by which to adapt terminal sizes + * @return true if the operation was successful, false otherwise. No other bb_terminal_* functions + * must be called if false is returned. + */ +bool bb_terminal_init(float factor); + +/** + * Shrink the height of the active terminal by the current factor. + */ +void bb_terminal_shrink_current(void); + +/** + * Re-maximise the height of all previously resized terminals. + */ +void bb_terminal_reset_all(void); + +#endif /* BB_TERMINAL_H */