361 lines
11 KiB
C
361 lines
11 KiB
C
/**
|
|
* Copyright 2021 Johannes Marbach
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include "buffyboard.h"
|
|
#include "command_line.h"
|
|
#include "indev.h"
|
|
#include "sq2lv_layouts.h"
|
|
#include "terminal.h"
|
|
#include "uinput_device.h"
|
|
|
|
#include "lv_drivers/display/fbdev.h"
|
|
#include "lv_drivers/indev/libinput_drv.h"
|
|
|
|
#include "lvgl/lvgl.h"
|
|
|
|
#include "../squeek2lvgl/sq2lv.h"
|
|
|
|
#include <limits.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
/**
|
|
* Static variables
|
|
*/
|
|
|
|
bb_cli_opts cli_opts;
|
|
|
|
static bool resize_terminals = false;
|
|
static lv_obj_t *keyboard = NULL;
|
|
static lv_style_t style_text_normal;
|
|
|
|
|
|
/**
|
|
* Static prototypes
|
|
*/
|
|
|
|
/**
|
|
* Compute the denominator of the keyboard height factor. The keyboard height is calculated
|
|
* by dividing the display height by the denominator.
|
|
*
|
|
* @param width display width
|
|
* @param height display height
|
|
* @return denominator
|
|
*/
|
|
static int keyboard_height_denominator(lv_coord_t width, lv_coord_t height);
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param is_dark true if the dark theme should be applied, false if the light theme should be applied
|
|
*/
|
|
static void set_theme(bool is_dark);
|
|
|
|
/**
|
|
* Handle LV_EVENT_DRAW_PART_BEGIN events from the keyboard widget.
|
|
*
|
|
* @param event the event object
|
|
*/
|
|
static void keyboard_draw_part_begin_cb(lv_event_t *event);
|
|
|
|
/**
|
|
* 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);
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
static void emit_key_events(uint16_t btn_id, bool key_down, bool key_up);
|
|
|
|
/**
|
|
* Release any previously pressed modifier keys.
|
|
*/
|
|
static void pop_checked_modifier_keys(void);
|
|
|
|
|
|
/**
|
|
* Static functions
|
|
*/
|
|
|
|
static int keyboard_height_denominator(lv_coord_t width, lv_coord_t height) {
|
|
return (height > width) ? 3 : 2;
|
|
}
|
|
|
|
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, &font_32);
|
|
}
|
|
|
|
static void keyboard_draw_part_begin_cb(lv_event_t *event) {
|
|
lv_obj_t *obj = lv_event_get_target(event);
|
|
lv_btnmatrix_t *btnm = (lv_btnmatrix_t *)obj;
|
|
lv_obj_draw_part_dsc_t *dsc = lv_event_get_param(event);
|
|
|
|
if (dsc->part != LV_PART_ITEMS) {
|
|
return;
|
|
}
|
|
|
|
if (lv_btnmatrix_get_selected_btn(obj) == dsc->id) { /* key is held down */
|
|
if ((btnm->ctrl_bits[dsc->id] & SQ2LV_CTRL_MOD_INACTIVE) == SQ2LV_CTRL_MOD_INACTIVE) {
|
|
dsc->rect_dsc->bg_color = lv_palette_lighten(LV_PALETTE_TEAL, 1);
|
|
} else if ((btnm->ctrl_bits[dsc->id] & SQ2LV_CTRL_MOD_ACTIVE) == SQ2LV_CTRL_MOD_ACTIVE) {
|
|
dsc->rect_dsc->bg_color = lv_palette_lighten(LV_PALETTE_TEAL, 1);
|
|
} else if ((btnm->ctrl_bits[dsc->id] & SQ2LV_CTRL_NON_CHAR) == SQ2LV_CTRL_NON_CHAR) {
|
|
dsc->rect_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE_GREY, 3);
|
|
} else {
|
|
dsc->rect_dsc->bg_color = lv_palette_lighten(LV_PALETTE_BLUE_GREY, 1);
|
|
}
|
|
} else { /* key is not held down */
|
|
if ((btnm->ctrl_bits[dsc->id] & SQ2LV_CTRL_MOD_INACTIVE) == SQ2LV_CTRL_MOD_INACTIVE) {
|
|
dsc->rect_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE_GREY, 4);
|
|
} else if ((btnm->ctrl_bits[dsc->id] & SQ2LV_CTRL_MOD_ACTIVE) == SQ2LV_CTRL_MOD_ACTIVE) {
|
|
dsc->rect_dsc->bg_color = lv_palette_main(LV_PALETTE_TEAL);
|
|
} else if ((btnm->ctrl_bits[dsc->id] & SQ2LV_CTRL_NON_CHAR) == SQ2LV_CTRL_NON_CHAR) {
|
|
dsc->rect_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE_GREY, 4);
|
|
} else {
|
|
dsc->rect_dsc->bg_color = lv_palette_main(LV_PALETTE_BLUE_GREY);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void keyboard_value_changed_cb(lv_event_t *event) {
|
|
lv_obj_t *kb = lv_event_get_target(event);
|
|
|
|
uint16_t btn_id = lv_btnmatrix_get_selected_btn(kb);
|
|
if (btn_id == LV_BTNMATRIX_BTN_NONE) {
|
|
return;
|
|
}
|
|
|
|
if (sq2lv_is_layer_switcher(kb, btn_id)) {
|
|
pop_checked_modifier_keys();
|
|
sq2lv_switch_layer(kb, btn_id);
|
|
return;
|
|
}
|
|
|
|
/* Note that the LV_BTNMATRIX_CTRL_CHECKED logic is inverted because LV_KEYBOARD_CTRL_BTN_FLAGS already
|
|
* contains LV_BTNMATRIX_CTRL_CHECKED. As a result, pressing e.g. CTRL will _un_check the key. To account
|
|
* for this, we invert the meaning of "checked" here and elsewhere in the code. */
|
|
|
|
bool is_modifier = sq2lv_is_modifier(keyboard, btn_id);
|
|
bool is_checked = !lv_btnmatrix_has_btn_ctrl(keyboard, btn_id, LV_BTNMATRIX_CTRL_CHECKED);
|
|
|
|
/* 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. */
|
|
emit_key_events(btn_id, true, !is_modifier || !is_checked);
|
|
|
|
/* Pop any previously checked modifiers when a non-modifier key was pressed */
|
|
if (!is_modifier) {
|
|
pop_checked_modifier_keys();
|
|
}
|
|
}
|
|
|
|
static void emit_key_events(uint16_t btn_id, bool key_down, bool key_up) {
|
|
int num_scancodes = 0;
|
|
const int *scancodes = sq2lv_get_scancodes(keyboard, btn_id, &num_scancodes);
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pop_checked_modifier_keys(void) {
|
|
int num_modifiers = 0;
|
|
const int *modifier_idxs = sq2lv_get_modifier_indexes(keyboard, &num_modifiers);
|
|
|
|
for (int i = 0; i < num_modifiers; ++i) {
|
|
if (!lv_btnmatrix_has_btn_ctrl(keyboard, modifier_idxs[i], LV_BTNMATRIX_CTRL_CHECKED)) {
|
|
emit_key_events(modifier_idxs[i], false, true);
|
|
lv_btnmatrix_set_btn_ctrl(keyboard, modifier_idxs[i], LV_BTNMATRIX_CTRL_CHECKED);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Main
|
|
*/
|
|
|
|
int main(int argc, char *argv[]) {
|
|
/* Parse command line options */
|
|
bb_cli_parse_opts(argc, argv, &cli_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;
|
|
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;
|
|
}
|
|
|
|
/* Initialise lvgl */
|
|
lv_init();
|
|
|
|
/* Initialise framebuffer driver and query display size */
|
|
fbdev_init();
|
|
uint32_t hor_res_phys;
|
|
uint32_t ver_res_phys;
|
|
fbdev_get_sizes(&hor_res_phys, &ver_res_phys);
|
|
|
|
/* Initialise display driver */
|
|
static lv_disp_drv_t disp_drv;
|
|
lv_disp_drv_init(&disp_drv);
|
|
disp_drv.flush_cb = fbdev_flush;
|
|
disp_drv.rotated = cli_opts.rotation;
|
|
disp_drv.sw_rotate = true;
|
|
disp_drv.physical_hor_res = hor_res_phys;
|
|
disp_drv.physical_ver_res = ver_res_phys;
|
|
switch (cli_opts.rotation) {
|
|
case LV_DISP_ROT_NONE:
|
|
case LV_DISP_ROT_180: {
|
|
lv_coord_t denom = keyboard_height_denominator(hor_res_phys, ver_res_phys);
|
|
disp_drv.hor_res = hor_res_phys;
|
|
disp_drv.ver_res = ver_res_phys / denom;
|
|
disp_drv.offset_x = 0;
|
|
disp_drv.offset_y = (cli_opts.rotation == LV_DISP_ROT_NONE) ? (denom - 1) * ver_res_phys / denom : 0;
|
|
break;
|
|
}
|
|
case LV_DISP_ROT_90:
|
|
case LV_DISP_ROT_270: {
|
|
lv_coord_t denom = keyboard_height_denominator(ver_res_phys, hor_res_phys);
|
|
disp_drv.hor_res = hor_res_phys / denom;
|
|
disp_drv.ver_res = ver_res_phys;
|
|
disp_drv.offset_x = (cli_opts.rotation == LV_DISP_ROT_90) ? (denom - 1) * hor_res_phys / denom : 0;
|
|
disp_drv.offset_y = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Prepare display buffer */
|
|
const size_t buf_size = disp_drv.hor_res * disp_drv.ver_res / 10; /* At least 1/10 of the display size is recommended */
|
|
lv_disp_draw_buf_t disp_buf;
|
|
lv_color_t *buf = (lv_color_t *)malloc(buf_size * sizeof(lv_color_t));
|
|
lv_disp_draw_buf_init(&disp_buf, buf, NULL, buf_size);
|
|
disp_drv.draw_buf = &disp_buf;
|
|
|
|
/* Register display driver */
|
|
lv_disp_drv_register(&disp_drv);
|
|
|
|
/* Connect input devices */
|
|
bb_indev_auto_connect();
|
|
bb_indev_set_up_mouse_cursor();
|
|
|
|
/* Initialise theme and styles */
|
|
set_theme(true);
|
|
lv_style_init(&style_text_normal);
|
|
lv_style_set_text_font(&style_text_normal, &font_32);
|
|
|
|
/* Add keyboard */
|
|
keyboard = lv_keyboard_create(lv_scr_act());
|
|
// lv_btnmatrix_set_popovers(keyboard, true);
|
|
lv_obj_set_pos(keyboard, 0, 0);
|
|
lv_obj_set_size(keyboard, LV_HOR_RES, LV_VER_RES);
|
|
lv_obj_add_style(keyboard, &style_text_normal, 0);
|
|
|
|
/* Set up keyboard event handlers */
|
|
lv_obj_remove_event_cb(keyboard, lv_keyboard_def_event_cb);
|
|
lv_obj_add_event_cb(keyboard, keyboard_value_changed_cb, LV_EVENT_VALUE_CHANGED, NULL);
|
|
lv_obj_add_event_cb(keyboard, keyboard_draw_part_begin_cb, LV_EVENT_DRAW_PART_BEGIN, NULL);
|
|
|
|
/* Apply default keyboard layout */
|
|
sq2lv_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();
|
|
usleep(5000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Tick generation
|
|
*/
|
|
|
|
/**
|
|
* Generate tick for LVGL.
|
|
*
|
|
* @return tick in ms
|
|
*/
|
|
uint32_t bb_get_tick(void) {
|
|
static uint64_t start_ms = 0;
|
|
if (start_ms == 0) {
|
|
struct timeval tv_start;
|
|
gettimeofday(&tv_start, NULL);
|
|
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
|
|
}
|
|
|
|
struct timeval tv_now;
|
|
gettimeofday(&tv_now, NULL);
|
|
uint64_t now_ms;
|
|
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
|
|
|
|
uint32_t time_ms = now_ms - start_ms;
|
|
return time_ms;
|
|
}
|