Merge branch 'signal' into 'master'
buffyboard: track virtual terminals with /sys/class/tty/tty0/active Closes #32, #11, and #3 See merge request postmarketOS/buffybox!58
This commit is contained in:
commit
0ee2fb83d9
12 changed files with 283 additions and 335 deletions
|
|
@ -16,6 +16,8 @@ If a change only affects particular applications, they are listed in parentheses
|
||||||
- fix: Do not hang if graphics backend is not available (!57, thanks @vstoiakin)
|
- fix: Do not hang if graphics backend is not available (!57, thanks @vstoiakin)
|
||||||
- fix!(unl0kr): Disable software rotation due to regressed performance (!56)
|
- fix!(unl0kr): Disable software rotation due to regressed performance (!56)
|
||||||
- feat(buffyboard): Rotate the keyboard according to /sys/class/graphics/fbcon/rotate by default (!60, thanks @vstoiakin)
|
- feat(buffyboard): Rotate the keyboard according to /sys/class/graphics/fbcon/rotate by default (!60, thanks @vstoiakin)
|
||||||
|
- fix(buffyboard): Prevent overlap of the keyboard and the terminal (!58, thanks @vstoiakin)
|
||||||
|
- feat(buffyboard): Add a handler for SIGUSR1 to redraw the keyboard (!58, thanks @vstoiakin)
|
||||||
|
|
||||||
## 3.3.0 (2025-04-15)
|
## 3.3.0 (2025-04-15)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ default=breezy-light
|
||||||
|
|
||||||
#[quirks]
|
#[quirks]
|
||||||
#fbdev_force_refresh=true
|
#fbdev_force_refresh=true
|
||||||
|
#ignore_unused_terminals=false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Documentation=https://gitlab.postmarketos.org/postmarketOS/buffybox
|
Documentation=https://gitlab.postmarketos.org/postmarketOS/buffybox
|
||||||
|
After=getty.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=@bindir@/buffyboard
|
ExecStart=@bindir@/buffyboard
|
||||||
|
|
@ -36,3 +37,6 @@ SystemCallArchitectures=native
|
||||||
SystemCallFilter=@system-service
|
SystemCallFilter=@system-service
|
||||||
SystemCallFilter=~@privileged
|
SystemCallFilter=~@privileged
|
||||||
SystemCallFilter=~@resources
|
SystemCallFilter=~@resources
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,10 @@ static int parsing_handler(void* user_data, const char* section, const char* key
|
||||||
if (bbx_config_parse_bool(value, &(opts->quirks.fbdev_force_refresh))) {
|
if (bbx_config_parse_bool(value, &(opts->quirks.fbdev_force_refresh))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(key, "ignore_unused_terminals") == 0) {
|
||||||
|
if (bbx_config_parse_bool(value, &(opts->quirks.ignore_unused_terminals))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,6 +84,7 @@ void bb_config_init_opts(bb_config_opts *opts) {
|
||||||
opts->input.pointer = true;
|
opts->input.pointer = true;
|
||||||
opts->input.touchscreen = true;
|
opts->input.touchscreen = true;
|
||||||
opts->quirks.fbdev_force_refresh = false;
|
opts->quirks.fbdev_force_refresh = false;
|
||||||
|
opts->quirks.ignore_unused_terminals = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void bb_config_parse_directory(const char *path, bb_config_opts *opts) {
|
void bb_config_parse_directory(const char *path, bb_config_opts *opts) {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* If true and using the framebuffer backend, force a refresh on every draw operation */
|
/* If true and using the framebuffer backend, force a refresh on every draw operation */
|
||||||
bool fbdev_force_refresh;
|
bool fbdev_force_refresh;
|
||||||
|
/* If true, do not automatically update the layout of new terminals and wait for SIGUSR1 */
|
||||||
|
bool ignore_unused_terminals;
|
||||||
} bb_config_opts_quirks;
|
} bb_config_opts_quirks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
3
buffyboard/getty-buffyboard.conf
Normal file
3
buffyboard/getty-buffyboard.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[Service]
|
||||||
|
ExecStartPost=-/usr/bin/kill -s SIGUSR1 buffyboard
|
||||||
|
StandardError=journal
|
||||||
|
|
@ -19,13 +19,14 @@
|
||||||
#include "../shared/themes.h"
|
#include "../shared/themes.h"
|
||||||
#include "../squeek2lvgl/sq2lv.h"
|
#include "../squeek2lvgl/sq2lv.h"
|
||||||
|
|
||||||
#include <limits.h>
|
#include <sys/epoll.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <sys/time.h>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,37 +36,19 @@
|
||||||
bb_cli_opts cli_opts;
|
bb_cli_opts cli_opts;
|
||||||
bb_config_opts conf_opts;
|
bb_config_opts conf_opts;
|
||||||
|
|
||||||
static bool resize_terminals = false;
|
|
||||||
static lv_obj_t *keyboard = NULL;
|
static lv_obj_t *keyboard = NULL;
|
||||||
|
static sig_atomic_t redraw_requested = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static prototypes
|
* Static prototypes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the denominator of the keyboard height factor. The keyboard height is calculated
|
* Handle signals sent to the process.
|
||||||
* by dividing the display height by the denominator.
|
|
||||||
*
|
|
||||||
* @param width display width
|
|
||||||
* @param height display height
|
|
||||||
* @return denominator
|
|
||||||
*/
|
|
||||||
static int keyboard_height_denominator(int32_t width, int32_t height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle termination signals sent to the process.
|
|
||||||
*
|
*
|
||||||
* @param signum the signal's number
|
* @param signum the signal's number
|
||||||
*/
|
*/
|
||||||
static void sigaction_handler(int signum);
|
static void signal_handler(int signum);
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for the terminal resizing timer.
|
|
||||||
*
|
|
||||||
* @param timer the timer object
|
|
||||||
*/
|
|
||||||
static void terminal_resize_timer_cb(lv_timer_t *timer);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle LV_EVENT_VALUE_CHANGED events from the keyboard widget.
|
* Handle LV_EVENT_VALUE_CHANGED events from the keyboard widget.
|
||||||
|
|
@ -93,25 +76,16 @@ static void pop_checked_modifier_keys(void);
|
||||||
* Static functions
|
* Static functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int keyboard_height_denominator(int32_t width, int32_t height) {
|
static void signal_handler(int signum) {
|
||||||
return (height > width) ? 3 : 2;
|
if (signum == SIGUSR1) {
|
||||||
}
|
redraw_requested = true;
|
||||||
|
return;
|
||||||
static void sigaction_handler(int signum) {
|
|
||||||
LV_UNUSED(signum);
|
|
||||||
if (resize_terminals) {
|
|
||||||
bb_terminal_reset_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bb_terminal_reset_all();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void terminal_resize_timer_cb(lv_timer_t *timer) {
|
|
||||||
LV_UNUSED(timer);
|
|
||||||
if (resize_terminals) {
|
|
||||||
bb_terminal_shrink_current();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void keyboard_value_changed_cb(lv_event_t *event) {
|
static void keyboard_value_changed_cb(lv_event_t *event) {
|
||||||
lv_obj_t *kb = lv_event_get_target(event);
|
lv_obj_t *kb = lv_event_get_target(event);
|
||||||
|
|
||||||
|
|
@ -196,23 +170,9 @@ int main(int argc, char *argv[]) {
|
||||||
bb_config_parse_directory("/etc/buffyboard.conf.d", &conf_opts);
|
bb_config_parse_directory("/etc/buffyboard.conf.d", &conf_opts);
|
||||||
bb_config_parse_files(cli_opts.config_files, cli_opts.num_config_files, &conf_opts);
|
bb_config_parse_files(cli_opts.config_files, cli_opts.num_config_files, &conf_opts);
|
||||||
|
|
||||||
/* 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;
|
|
||||||
lv_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 */
|
/* Set up uinput device */
|
||||||
if (!bb_uinput_device_init(sq2lv_unique_scancodes, sq2lv_num_unique_scancodes)) {
|
if (!bb_uinput_device_init(sq2lv_unique_scancodes, sq2lv_num_unique_scancodes)) {
|
||||||
return 1;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialise LVGL and set up logging callback */
|
/* Initialise LVGL and set up logging callback */
|
||||||
|
|
@ -223,17 +183,19 @@ int main(int argc, char *argv[]) {
|
||||||
lv_display_t *disp = lv_linux_fbdev_create();
|
lv_display_t *disp = lv_linux_fbdev_create();
|
||||||
if (access("/dev/fb0", F_OK) != 0) {
|
if (access("/dev/fb0", F_OK) != 0) {
|
||||||
bbx_log(BBX_LOG_LEVEL_ERROR, "/dev/fb0 is not available");
|
bbx_log(BBX_LOG_LEVEL_ERROR, "/dev/fb0 is not available");
|
||||||
sigaction_handler(SIGTERM);
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
lv_linux_fbdev_set_file(disp, "/dev/fb0");
|
lv_linux_fbdev_set_file(disp, "/dev/fb0");
|
||||||
if (conf_opts.quirks.fbdev_force_refresh) {
|
if (conf_opts.quirks.fbdev_force_refresh) {
|
||||||
lv_linux_fbdev_set_force_refresh(disp, true);
|
lv_linux_fbdev_set_force_refresh(disp, true);
|
||||||
}
|
}
|
||||||
|
lv_display_set_physical_resolution(disp,
|
||||||
|
lv_display_get_horizontal_resolution(disp),
|
||||||
|
lv_display_get_vertical_resolution(disp));
|
||||||
|
|
||||||
/* Override display properties with command line options if necessary */
|
/* Override display properties with command line options if necessary */
|
||||||
lv_display_set_offset(disp, cli_opts.x_offset, cli_opts.y_offset);
|
lv_display_set_offset(disp, cli_opts.x_offset, cli_opts.y_offset);
|
||||||
if (cli_opts.hor_res > 0 || cli_opts.ver_res > 0) {
|
if (cli_opts.hor_res > 0 && cli_opts.ver_res > 0) {
|
||||||
lv_display_set_physical_resolution(disp, lv_display_get_horizontal_resolution(disp), lv_display_get_vertical_resolution(disp));
|
|
||||||
lv_display_set_resolution(disp, cli_opts.hor_res, cli_opts.ver_res);
|
lv_display_set_resolution(disp, cli_opts.hor_res, cli_opts.ver_res);
|
||||||
}
|
}
|
||||||
if (cli_opts.dpi > 0) {
|
if (cli_opts.dpi > 0) {
|
||||||
|
|
@ -241,27 +203,29 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set up display rotation */
|
/* Set up display rotation */
|
||||||
int32_t hor_res_phys = lv_display_get_horizontal_resolution(disp);
|
|
||||||
int32_t ver_res_phys = lv_display_get_vertical_resolution(disp);
|
|
||||||
lv_display_set_physical_resolution(disp, hor_res_phys, ver_res_phys);
|
|
||||||
lv_display_set_rotation(disp, cli_opts.rotation);
|
lv_display_set_rotation(disp, cli_opts.rotation);
|
||||||
|
|
||||||
|
const int32_t hor_res = lv_display_get_horizontal_resolution(disp);
|
||||||
|
const int32_t ver_res = lv_display_get_vertical_resolution(disp);
|
||||||
|
const int32_t keyboard_height = ver_res > hor_res ? ver_res / 3 : ver_res / 2;
|
||||||
|
const int32_t tty_height = ver_res - keyboard_height;
|
||||||
|
|
||||||
switch (cli_opts.rotation) {
|
switch (cli_opts.rotation) {
|
||||||
case LV_DISPLAY_ROTATION_0:
|
case LV_DISPLAY_ROTATION_0:
|
||||||
case LV_DISPLAY_ROTATION_180: {
|
case LV_DISPLAY_ROTATION_180:
|
||||||
int32_t denom = keyboard_height_denominator(hor_res_phys, ver_res_phys);
|
lv_display_set_resolution(disp, hor_res, keyboard_height);
|
||||||
lv_display_set_resolution(disp, hor_res_phys, ver_res_phys / denom);
|
lv_display_set_offset(disp, 0, cli_opts.rotation == LV_DISPLAY_ROTATION_0? tty_height : 0);
|
||||||
lv_display_set_offset(disp, 0, (cli_opts.rotation == LV_DISPLAY_ROTATION_0) ? (denom - 1) * ver_res_phys / denom : 0);
|
break;
|
||||||
break;
|
case LV_DISPLAY_ROTATION_90:
|
||||||
}
|
case LV_DISPLAY_ROTATION_270:
|
||||||
case LV_DISPLAY_ROTATION_90:
|
lv_display_set_resolution(disp, keyboard_height, hor_res);
|
||||||
case LV_DISPLAY_ROTATION_270: {
|
lv_display_set_offset(disp, cli_opts.rotation == LV_DISPLAY_ROTATION_90? tty_height : 0, 0);
|
||||||
int32_t denom = keyboard_height_denominator(ver_res_phys, hor_res_phys);
|
break;
|
||||||
lv_display_set_resolution(disp, hor_res_phys / denom, ver_res_phys);
|
|
||||||
lv_display_set_offset(disp, 0, (cli_opts.rotation == LV_DISPLAY_ROTATION_90) ? (denom - 1) * hor_res_phys / denom : 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prepare for terminal resizing and reset */
|
||||||
|
bb_terminal_init(tty_height - 8, hor_res, ver_res);
|
||||||
|
|
||||||
/* Start input device monitor and auto-connect available devices */
|
/* Start input device monitor and auto-connect available devices */
|
||||||
bbx_indev_start_monitor_and_autoconnect(false, conf_opts.input.pointer, conf_opts.input.touchscreen);
|
bbx_indev_start_monitor_and_autoconnect(false, conf_opts.input.pointer, conf_opts.input.touchscreen);
|
||||||
|
|
||||||
|
|
@ -286,13 +250,89 @@ int main(int argc, char *argv[]) {
|
||||||
/* Apply default keyboard layout */
|
/* Apply default keyboard layout */
|
||||||
sq2lv_switch_layout(keyboard, SQ2LV_LAYOUT_TERMINAL_US);
|
sq2lv_switch_layout(keyboard, SQ2LV_LAYOUT_TERMINAL_US);
|
||||||
|
|
||||||
/* Start timer for periodically resizing terminals */
|
/* Open the file to track virtual terminals */
|
||||||
lv_timer_create(terminal_resize_timer_cb, 1000, NULL);
|
int fd_tty = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
|
||||||
|
if (fd_tty < 0) {
|
||||||
|
perror("Can't open /sys/class/tty/tty0/active");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd_epoll = epoll_create1(EPOLL_CLOEXEC);
|
||||||
|
if (fd_epoll == -1) {
|
||||||
|
perror("epoll_create1() is failed");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct epoll_event event;
|
||||||
|
event.events = EPOLLIN|EPOLLET;
|
||||||
|
event.data.fd = fd_tty;
|
||||||
|
|
||||||
|
int r = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_tty, &event);
|
||||||
|
if (r == -1) {
|
||||||
|
perror("epoll_ctl() is failed");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set signal handlers */
|
||||||
|
struct sigaction action;
|
||||||
|
action.sa_handler = signal_handler;
|
||||||
|
action.sa_flags = 0;
|
||||||
|
sigfillset(&action.sa_mask);
|
||||||
|
sigaction(SIGINT, &action, NULL);
|
||||||
|
sigaction(SIGTERM, &action, NULL);
|
||||||
|
|
||||||
|
action.sa_flags = SA_RESTART;
|
||||||
|
sigemptyset(&action.sa_mask);
|
||||||
|
sigaction(SIGUSR1, &action, NULL);
|
||||||
|
|
||||||
|
sigset_t sigmask;
|
||||||
|
sigemptyset(&sigmask);
|
||||||
|
sigaddset(&sigmask, SIGUSR1);
|
||||||
|
|
||||||
|
sigprocmask(SIG_BLOCK, &sigmask, NULL);
|
||||||
|
|
||||||
|
sigemptyset(&sigmask);
|
||||||
|
|
||||||
/* Periodically run timer / task handler */
|
/* Periodically run timer / task handler */
|
||||||
while(1) {
|
while(1) {
|
||||||
uint32_t time_till_next = lv_timer_handler();
|
uint32_t time_till_next = lv_timer_handler();
|
||||||
usleep(time_till_next * 1000);
|
|
||||||
|
int r = epoll_pwait(fd_epoll, &event, 1, time_till_next, &sigmask);
|
||||||
|
if (r == 0)
|
||||||
|
continue;
|
||||||
|
if (r < 0) {
|
||||||
|
if (errno != EINTR) {
|
||||||
|
perror("epoll_wait() is failed");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (!redraw_requested)
|
||||||
|
continue;
|
||||||
|
redraw_requested = false;
|
||||||
|
} else if (conf_opts.quirks.ignore_unused_terminals) {
|
||||||
|
lseek(fd_tty, 0, SEEK_SET);
|
||||||
|
|
||||||
|
char buffer[8];
|
||||||
|
ssize_t size = read(fd_tty, buffer, sizeof(buffer));
|
||||||
|
if (size <= 0) {
|
||||||
|
bbx_log(BBX_LOG_LEVEL_WARNING, "Can't read /sys/class/tty/tty0/active");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buffer[size] = 0;
|
||||||
|
|
||||||
|
unsigned int tty;
|
||||||
|
if (sscanf(buffer, "tty%u", &tty) != 1) {
|
||||||
|
bbx_log(BBX_LOG_LEVEL_WARNING, "Unexpected value of /sys/class/tty/tty0/active");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bb_terminal_is_busy(tty)) {
|
||||||
|
bbx_log(BBX_LOG_LEVEL_VERBOSE, "Terminal %u isn't used, skip automatic update.", tty);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bb_terminal_shrink_current();
|
||||||
|
lv_obj_invalidate(keyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,10 @@ if depsystemd.found()
|
||||||
install_dir: system_unit_dir,
|
install_dir: system_unit_dir,
|
||||||
install_tag: 'buffyboard'
|
install_tag: 'buffyboard'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
install_data('getty-buffyboard.conf',
|
||||||
|
rename: 'buffyboard.conf',
|
||||||
|
install_dir: system_unit_dir / 'getty@.service.d',
|
||||||
|
install_tag: 'buffyboard'
|
||||||
|
)
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,14 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <math.h>
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <linux/kd.h>
|
||||||
#include <linux/vt.h>
|
#include <linux/vt.h>
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
|
@ -22,260 +20,121 @@
|
||||||
* Static variables
|
* Static variables
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int current_fd = -1;
|
static int32_t _tty_size;
|
||||||
static int current_vt = -1;
|
static int32_t _h_display_size;
|
||||||
static bool resized_vts[MAX_NR_CONSOLES];
|
static int32_t _v_display_size;
|
||||||
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
|
* Public functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool bb_terminal_init(float factor) {
|
void bb_terminal_init(int32_t tty_size, int32_t h_display_size, int32_t v_display_size) {
|
||||||
if (!reopen_current_terminal()) {
|
_tty_size = tty_size;
|
||||||
perror("Could not prepare for terminal resizing");
|
_h_display_size = h_display_size;
|
||||||
|
_v_display_size = v_display_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bb_terminal_shrink_current() {
|
||||||
|
int fd = open("/dev/tty0", O_RDONLY|O_NOCTTY);
|
||||||
|
if (fd < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* KDFONTOP returns EINVAL if we are not in the text mode,
|
||||||
|
so we can skip this check */
|
||||||
|
/*
|
||||||
|
int mode;
|
||||||
|
if (ioctl(fd, KDGETMODE, &mode) != 0)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
if (mode != KD_TEXT)
|
||||||
|
goto end;
|
||||||
|
*/
|
||||||
|
struct console_font_op cfo = {
|
||||||
|
.op = KD_FONT_OP_GET,
|
||||||
|
.width = UINT_MAX,
|
||||||
|
.height = UINT_MAX,
|
||||||
|
.charcount = UINT_MAX,
|
||||||
|
.data = NULL
|
||||||
|
};
|
||||||
|
if (ioctl(fd, KDFONTOP, &cfo) != 0)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
struct winsize size;
|
||||||
|
size.ws_row = _tty_size / cfo.height;
|
||||||
|
size.ws_col = _h_display_size / cfo.width;
|
||||||
|
ioctl(fd, TIOCSWINSZ, &size);
|
||||||
|
end:
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bb_terminal_reset_all() {
|
||||||
|
int fd = open("/dev/tty0", O_RDONLY|O_NOCTTY);
|
||||||
|
if (fd < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct vt_stat state;
|
||||||
|
if (ioctl(fd, VT_GETSTATE, &state) != 0) {
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
char buffer[sizeof("/dev/tty") + 2];
|
||||||
|
unsigned short mask = state.v_state >> 1;
|
||||||
|
unsigned int tty = 1;
|
||||||
|
|
||||||
|
for (; mask; mask >>= 1, tty++) {
|
||||||
|
if (mask & 0x01) {
|
||||||
|
sprintf(buffer, "/dev/tty%u", tty);
|
||||||
|
int tty_fd = open(buffer, O_RDONLY|O_NOCTTY);
|
||||||
|
if (tty_fd < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* KDFONTOP returns EINVAL if we are not in the text mode,
|
||||||
|
so we can skip this check */
|
||||||
|
/*
|
||||||
|
int mode;
|
||||||
|
if (ioctl(tty_fd, KDGETMODE, &mode) != 0)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
if (mode != KD_TEXT)
|
||||||
|
goto end;
|
||||||
|
*/
|
||||||
|
struct console_font_op cfo = {
|
||||||
|
.op = KD_FONT_OP_GET,
|
||||||
|
.width = UINT_MAX,
|
||||||
|
.height = UINT_MAX,
|
||||||
|
.charcount = UINT_MAX,
|
||||||
|
.data = NULL
|
||||||
|
};
|
||||||
|
if (ioctl(tty_fd, KDFONTOP, &cfo) != 0)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
struct winsize size;
|
||||||
|
size.ws_row = _v_display_size / cfo.height;
|
||||||
|
size.ws_col = _h_display_size / cfo.width;
|
||||||
|
ioctl(tty_fd, TIOCSWINSZ, &size);
|
||||||
|
end:
|
||||||
|
close(tty_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bb_terminal_is_busy(unsigned int tty) {
|
||||||
|
/* We can use any other terminal than `tty` here. */
|
||||||
|
int fd = open(tty == 1? "/dev/tty2" : "/dev/tty1", O_RDONLY|O_NOCTTY);
|
||||||
|
if (fd < 0)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
current_vt = get_active_terminal();
|
struct vt_stat state;
|
||||||
if (current_vt < 0) {
|
int r = ioctl(fd, VT_GETSTATE, &state);
|
||||||
perror("Could not prepare for terminal resizing");
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (r != 0)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
height_factor = factor;
|
return (state.v_state >> tty) & 0x01;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,30 @@
|
||||||
#define BB_TERMINAL_H
|
#define BB_TERMINAL_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare for resizing terminals by opening the current one.
|
* Prepare for resizing terminals.
|
||||||
*
|
*
|
||||||
* @param factor factor (between 0 and 1) by which to adapt terminal sizes
|
* @param tty_size vertical size of the tty area in pixels
|
||||||
* @return true if the operation was successful, false otherwise. No other bb_terminal_* functions
|
* @param h_display_size horizontal resolution of the display in pixels
|
||||||
* must be called if false is returned.
|
* @param v_display_size vertical resolution of the display in pixels
|
||||||
*/
|
*/
|
||||||
bool bb_terminal_init(float factor);
|
void bb_terminal_init(int32_t tty_size, int32_t h_display_size, int32_t v_display_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shrink the height of the active terminal by the current factor.
|
* Shrink the height of the active terminal.
|
||||||
*/
|
*/
|
||||||
void bb_terminal_shrink_current(void);
|
void bb_terminal_shrink_current();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-maximise the height of all previously resized terminals.
|
* Re-maximise the size of all active terminals.
|
||||||
*/
|
*/
|
||||||
void bb_terminal_reset_all(void);
|
void bb_terminal_reset_all();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the terminal is opened by some process
|
||||||
|
*/
|
||||||
|
bool bb_terminal_is_busy(unsigned int tty);
|
||||||
|
|
||||||
#endif /* BB_TERMINAL_H */
|
#endif /* BB_TERMINAL_H */
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,15 @@ result.
|
||||||
*-V, --version*
|
*-V, --version*
|
||||||
Print the unl0kr version and exit.
|
Print the unl0kr version and exit.
|
||||||
|
|
||||||
|
# NOTES
|
||||||
|
|
||||||
|
Some terminal commands, like _clear_ or _setfont_, can erase the keyboard or brake the layout of the terminal. In this case you should send a signal to Buffyboard or switch to another terminal to update the screen:
|
||||||
|
|
||||||
|
```
|
||||||
|
setfont solar24x32;/usr/bin/kill -s SIGUSR1 buffyboard
|
||||||
|
setfont solar24x32;chvt $((`fgconsole`+1));chvt $((`fgconsole`-1))
|
||||||
|
```
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
*Execute Buffyboard using the default config*
|
*Execute Buffyboard using the default config*
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,17 @@ for and, if found, merged in the following order:
|
||||||
after every draw operation. This has a negative performance impact.
|
after every draw operation. This has a negative performance impact.
|
||||||
Default: false.
|
Default: false.
|
||||||
|
|
||||||
|
*ignore_unused_terminals* = <true|false>
|
||||||
|
If true, buffyboard won't automatically update the layout of a new terminal and
|
||||||
|
draw the keyboard, if the terminal is not opened by any process. In this case
|
||||||
|
SIGUSR1 should be sent to buffyboard to update the layout. This quirk was introduced to resolve a race between buffyboard and systemd-logind according to the following scenario:
|
||||||
|
- A user switches to a new virtual terminal
|
||||||
|
- Buffyboard opens the terminal and changes the number of rows
|
||||||
|
- systemd-logind sees that the terminal is opened by some other process and don't start getty@.service
|
||||||
|
|
||||||
|
The race is resolved by enabling this option and installing a drop-in file
|
||||||
|
for getty@.service that sends SIGUSR1 to buffyboard. Default: true.
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
*buffyboard*(1)
|
*buffyboard*(1)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue