buffybox/buffyboard/main.c

340 lines
10 KiB
C
Raw Normal View History

2021-09-13 13:30:15 +02:00
/**
* Copyright 2021 Johannes Marbach
* SPDX-License-Identifier: GPL-3.0-or-later
2021-09-13 13:30:15 +02:00
*/
2024-01-12 10:00:05 +01:00
#include "buffyboard.h"
#include "command_line.h"
#include "config.h"
2021-09-13 13:30:15 +02:00
#include "sq2lv_layouts.h"
#include "terminal.h"
2021-09-13 13:30:15 +02:00
#include "uinput_device.h"
2021-09-19 21:06:03 +02:00
#include "lvgl/lvgl.h"
2024-03-28 14:31:27 +01:00
#include "../shared/indev.h"
2024-04-01 16:21:44 +02:00
#include "../shared/log.h"
#include "../shared/theme.h"
#include "../shared/themes.h"
2023-11-20 21:03:32 +01:00
#include "../squeek2lvgl/sq2lv.h"
2021-09-19 21:06:03 +02:00
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
2021-09-13 13:30:15 +02:00
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
2021-09-13 13:30:15 +02:00
/**
* Static variables
*/
bb_cli_opts cli_opts;
bb_config_opts conf_opts;
2021-09-13 13:30:15 +02:00
static lv_obj_t *keyboard = NULL;
static sig_atomic_t redraw_requested = false;
2021-09-13 13:30:15 +02:00
/**
* Static prototypes
*/
/**
* Handle signals sent to the process.
*
* @param signum the signal's number
*/
static void signal_handler(int signum);
2021-09-13 13:30:15 +02:00
/**
* Handle LV_EVENT_VALUE_CHANGED events from the keyboard widget.
*
* @param event the event object
*/
static void keyboard_value_changed_cb(lv_event_t *event);
2021-09-13 20:11:35 +02:00
/**
* Emit key down and up events for a key.
*
* @param btn_id button index corresponding to the key
* @param key_down true if a key down event should be emitted
* @param key_up true if a key up event should be emitted
*/
2021-09-19 21:06:03 +02:00
static void emit_key_events(uint16_t btn_id, bool key_down, bool key_up);
2021-09-13 20:11:35 +02:00
2021-09-13 20:54:52 +02:00
/**
* Release any previously pressed modifier keys.
*/
2021-09-19 21:06:03 +02:00
static void pop_checked_modifier_keys(void);
2021-09-13 20:54:52 +02:00
2021-09-13 13:30:15 +02:00
/**
* Static functions
*/
static void signal_handler(int signum) {
if (signum == SIGUSR1) {
redraw_requested = true;
return;
}
bb_terminal_reset_all();
exit(0);
}
2021-09-13 13:30:15 +02:00
static void keyboard_value_changed_cb(lv_event_t *event) {
2021-09-19 21:06:03 +02:00
lv_obj_t *kb = lv_event_get_target(event);
2021-09-13 13:30:15 +02:00
2024-02-19 13:20:20 +00:00
uint16_t btn_id = lv_buttonmatrix_get_selected_button(kb);
if (btn_id == LV_BUTTONMATRIX_BUTTON_NONE) {
2021-09-13 13:30:15 +02:00
return;
}
2021-09-19 21:06:03 +02:00
if (sq2lv_is_layer_switcher(kb, btn_id)) {
pop_checked_modifier_keys();
sq2lv_switch_layer(kb, btn_id);
2021-09-13 13:30:15 +02:00
return;
}
2024-02-19 13:20:20 +00:00
/* Note that the LV_BUTTONMATRIX_CTRL_CHECKED logic is inverted because LV_KEYBOARD_CTRL_BTN_FLAGS already
* contains LV_BUTTONMATRIX_CTRL_CHECKED. As a result, pressing e.g. CTRL will _un_check the key. To account
2021-09-13 20:54:52 +02:00
* for this, we invert the meaning of "checked" here and elsewhere in the code. */
2021-09-13 20:11:35 +02:00
2021-09-19 21:06:03 +02:00
bool is_modifier = sq2lv_is_modifier(keyboard, btn_id);
2024-02-19 13:20:20 +00:00
bool is_checked = !lv_buttonmatrix_has_button_ctrl(keyboard, btn_id, LV_BUTTONMATRIX_CTRL_CHECKED);
2021-09-13 20:11:35 +02:00
/* Emit key events. Suppress key up events for modifiers unless they were unchecked. For checked modifiers
* the key up events are sent with the next non-modifier key press. */
2021-09-19 21:06:03 +02:00
emit_key_events(btn_id, true, !is_modifier || !is_checked);
2021-09-13 20:11:35 +02:00
/* Pop any previously checked modifiers when a non-modifier key was pressed */
if (!is_modifier) {
2021-09-19 21:06:03 +02:00
pop_checked_modifier_keys();
2021-09-13 20:11:35 +02:00
}
}
2021-09-19 21:06:03 +02:00
static void emit_key_events(uint16_t btn_id, bool key_down, bool key_up) {
2021-09-13 13:30:15 +02:00
int num_scancodes = 0;
2021-09-23 14:42:48 +02:00
const int *scancodes = sq2lv_get_scancodes(keyboard, btn_id, &num_scancodes);
2021-09-13 13:30:15 +02:00
2021-09-13 20:11:35 +02:00
if (key_down) {
/* Emit key down events in forward order */
for (int i = 0; i < num_scancodes; ++i) {
bb_uinput_device_emit_key_down(scancodes[i]);
}
2021-09-13 13:30:15 +02:00
}
2021-09-13 20:11:35 +02:00
if (key_up) {
/* Emit key up events in backward order */
for (int i = num_scancodes - 1; i >= 0; --i) {
bb_uinput_device_emit_key_up(scancodes[i]);
}
2021-09-13 13:30:15 +02:00
}
}
2021-09-19 21:06:03 +02:00
static void pop_checked_modifier_keys(void) {
2021-09-13 20:54:52 +02:00
int num_modifiers = 0;
2021-09-23 14:42:48 +02:00
const int *modifier_idxs = sq2lv_get_modifier_indexes(keyboard, &num_modifiers);
2021-09-13 20:54:52 +02:00
for (int i = 0; i < num_modifiers; ++i) {
2024-02-19 13:20:20 +00:00
if (!lv_buttonmatrix_has_button_ctrl(keyboard, modifier_idxs[i], LV_BUTTONMATRIX_CTRL_CHECKED)) {
2021-09-19 21:06:03 +02:00
emit_key_events(modifier_idxs[i], false, true);
2024-02-19 13:20:20 +00:00
lv_buttonmatrix_set_button_ctrl(keyboard, modifier_idxs[i], LV_BUTTONMATRIX_CTRL_CHECKED);
2021-09-13 20:54:52 +02:00
}
}
}
2021-09-13 13:30:15 +02:00
/**
* Main
*/
int main(int argc, char *argv[]) {
/* Parse command line options */
bb_cli_parse_opts(argc, argv, &cli_opts);
2024-04-01 16:21:44 +02:00
/* Set up log level */
if (cli_opts.verbose) {
bbx_log_set_level(BBX_LOG_LEVEL_VERBOSE);
}
/* Parse config files */
bb_config_init_opts(&conf_opts);
2024-09-24 19:09:53 +00:00
bb_config_parse_file("/usr/share/buffyboard/buffyboard.conf", &conf_opts);
bb_config_parse_directory("/usr/share/buffyboard/buffyboard.conf.d", &conf_opts);
bb_config_parse_file("/etc/buffyboard.conf", &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);
2021-09-13 13:30:15 +02:00
/* Set up uinput device */
if (!bb_uinput_device_init(sq2lv_unique_scancodes, sq2lv_num_unique_scancodes)) {
return EXIT_FAILURE;
2021-09-13 13:30:15 +02:00
}
/* Initialise LVGL and set up logging callback */
lv_init();
lv_log_register_print_cb(bbx_log_print_cb);
2021-09-13 13:30:15 +02:00
2024-02-19 13:20:20 +00:00
/* Initialise display */
lv_display_t *disp = lv_linux_fbdev_create();
if (access("/dev/fb0", F_OK) != 0) {
bbx_log(BBX_LOG_LEVEL_ERROR, "/dev/fb0 is not available");
return EXIT_FAILURE;
}
2024-02-19 13:20:20 +00:00
lv_linux_fbdev_set_file(disp, "/dev/fb0");
if (conf_opts.quirks.fbdev_force_refresh) {
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));
2024-03-30 12:46:09 +00:00
/* Override display properties with command line options if necessary */
lv_display_set_offset(disp, cli_opts.x_offset, cli_opts.y_offset);
if (cli_opts.hor_res > 0 && cli_opts.ver_res > 0) {
2024-03-30 12:46:09 +00:00
lv_display_set_resolution(disp, cli_opts.hor_res, cli_opts.ver_res);
}
if (cli_opts.dpi > 0) {
lv_display_set_dpi(disp, cli_opts.dpi);
}
/* Set up display rotation */
2024-03-21 14:27:20 +01:00
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;
2024-03-21 14:27:20 +01:00
switch (cli_opts.rotation) {
case LV_DISPLAY_ROTATION_0:
case LV_DISPLAY_ROTATION_180:
lv_display_set_resolution(disp, hor_res, keyboard_height);
lv_display_set_offset(disp, 0, cli_opts.rotation == LV_DISPLAY_ROTATION_0? tty_height : 0);
break;
case LV_DISPLAY_ROTATION_90:
case LV_DISPLAY_ROTATION_270:
lv_display_set_resolution(disp, keyboard_height, hor_res);
lv_display_set_offset(disp, cli_opts.rotation == LV_DISPLAY_ROTATION_90? tty_height : 0, 0);
break;
2024-03-21 14:27:20 +01:00
}
2021-09-13 13:30:15 +02:00
/* Prepare for terminal resizing and reset */
bb_terminal_init(tty_height - 8, hor_res, ver_res);
2024-03-28 14:31:27 +01:00
/* Start input device monitor and auto-connect available devices */
2024-03-30 20:52:01 +01:00
bbx_indev_start_monitor_and_autoconnect(false, conf_opts.input.pointer, conf_opts.input.touchscreen);
2021-09-13 13:30:15 +02:00
/* Initialise theme */
2024-03-30 20:52:01 +01:00
bbx_theme_apply(bbx_themes_themes[conf_opts.theme.default_id]);
2025-04-15 16:17:52 +00:00
lv_theme_apply(lv_screen_active());
2021-09-13 13:30:15 +02:00
/* Add keyboard */
2025-04-15 16:17:52 +00:00
keyboard = lv_keyboard_create(lv_screen_active());
uint32_t num_keyboard_events = lv_obj_get_event_count(keyboard);
for(uint32_t i = 0; i < num_keyboard_events; ++i) {
if(lv_event_dsc_get_cb(lv_obj_get_event_dsc(keyboard, i)) == lv_keyboard_def_event_cb) {
lv_obj_remove_event(keyboard, i);
break;
}
}
lv_obj_add_event_cb(keyboard, keyboard_value_changed_cb, LV_EVENT_VALUE_CHANGED, NULL);
2021-09-13 13:30:15 +02:00
lv_obj_set_pos(keyboard, 0, 0);
lv_obj_set_size(keyboard, LV_HOR_RES, LV_VER_RES);
2024-03-30 20:52:01 +01:00
bbx_theme_prepare_keyboard(keyboard);
2021-09-13 13:30:15 +02:00
/* Apply default keyboard layout */
2021-09-19 21:06:03 +02:00
sq2lv_switch_layout(keyboard, SQ2LV_LAYOUT_TERMINAL_US);
2021-09-13 13:30:15 +02:00
/* Open the file to track virtual terminals */
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 */
2021-09-13 13:30:15 +02:00
while(1) {
uint32_t time_till_next = lv_timer_handler();
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);
2021-09-13 13:30:15 +02:00
}
return 0;
}