/** * 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 "cursor.h" #include "layout.h" #include "libinput_multi.h" #include "sq2lv_layouts.h" #include "uinput_device.h" #include "lvgl/lvgl.h" #include "lv_drivers/display/fbdev.h" #include "lv_drivers/indev/libinput_drv.h" #include #include #include #include #include /** * Custom fonts */ LV_FONT_DECLARE(montserrat_extended_32); /** * Static variables */ static lv_obj_t *keyboard = NULL; static lv_style_t style_text_normal; /** * Static prototypes */ /** * 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_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 keyboard keyboard widget * @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(lv_obj_t *keyboard, uint16_t btn_id, bool key_down, bool key_up); /** * Release any previously pressed modifier keys. * * @param keyboard keyboard widget */ static void pop_checked_modifier_keys(lv_obj_t *keyboard); /** * Static functions */ 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); } static void keyboard_value_changed_cb(lv_event_t *event) { lv_obj_t *obj = lv_event_get_target(event); uint16_t btn_id = lv_btnmatrix_get_selected_btn(obj); if (btn_id == LV_BTNMATRIX_BTN_NONE) { return; } if (bb_layout_is_layer_switcher(obj, btn_id)) { pop_checked_modifier_keys(keyboard); bb_layout_switch_layer(obj, 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 = bb_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(keyboard, 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(keyboard); } } static void emit_key_events(lv_obj_t *keyboard, uint16_t btn_id, bool key_down, bool key_up) { int num_scancodes = 0; int *scancodes = bb_layout_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(lv_obj_t *keyboard) { int num_modifiers = 0; int *modifier_idxs = bb_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(keyboard, modifier_idxs[i], false, true); lv_btnmatrix_set_btn_ctrl(keyboard, modifier_idxs[i], LV_BTNMATRIX_CTRL_CHECKED); } } } static void keyboard_draw_part_begin_cb(lv_event_t *event) { lv_obj_t *obj = lv_event_get_target(event); 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 (lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CHECKED)) { if (lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CHECKABLE)) { /* inactive modifiers */ dsc->rect_dsc->bg_color = lv_palette_lighten(LV_PALETTE_TEAL, 1); } else { /* non-letters */ dsc->rect_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE_GREY, 3); } } else { if (lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CHECKABLE)) { /* active modifiers */ dsc->rect_dsc->bg_color = lv_palette_lighten(LV_PALETTE_TEAL, 1); } else { /* letters */ dsc->rect_dsc->bg_color = lv_palette_lighten(LV_PALETTE_BLUE_GREY, 1); } } } else { /* key is not held down */ if (lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CHECKED)) { /* inactive modifiers & non-letters */ dsc->rect_dsc->bg_color = lv_palette_darken(LV_PALETTE_BLUE_GREY, 4); } else if (lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CHECKABLE)) { /* active modifiers */ dsc->rect_dsc->bg_color = lv_palette_main(LV_PALETTE_TEAL); } else { /* letters */ dsc->rect_dsc->bg_color = lv_palette_main(LV_PALETTE_BLUE_GREY); } } } /** * Main */ int main(void) { /* Set up uinput device */ if (!bb_uinput_device_init(sq2lv_unique_scancodes, sq2lv_num_unique_scancodes)) { return 1; } /* Initialise lvgl and framebuffer driver */ lv_init(); fbdev_init(); /* Query display size */ uint32_t hor_res; uint32_t ver_res; fbdev_get_sizes(&hor_res, &ver_res); /* Make sure to not cover the whole screen */ ver_res /= 3; // fbdev_set_offset(0, 2 * ver_res); /* Prepare display buffer */ const size_t buf_size = hor_res * 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); /* Initialise display driver */ static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.draw_buf = &disp_buf; disp_drv.flush_cb = fbdev_flush; disp_drv.hor_res = hor_res; disp_drv.ver_res = ver_res; lv_disp_drv_register(&disp_drv); /* Connect mice and trackpads */ #define MAX_POINTER_DEVICES 4 char *pointer_devices[MAX_POINTER_DEVICES] = { NULL, NULL, NULL, NULL }; lv_indev_drv_t pointer_indev_drvs[MAX_POINTER_DEVICES]; lv_indev_t *pointer_indevs[MAX_POINTER_DEVICES] = { NULL, NULL, NULL, NULL }; size_t num_pointer_devices = libinput_find_devs(LIBINPUT_CAPABILITY_POINTER, pointer_devices, MAX_POINTER_DEVICES, false); for (int i = 0; i < num_pointer_devices; ++i) { printf("Connecting pointer device %s\n", pointer_devices[i]); lv_indev_drv_init(&pointer_indev_drvs[i]); pointer_indev_drvs[i].type = LV_INDEV_TYPE_POINTER; pointer_indev_drvs[i].read_cb = libinput_multi_read; pointer_indev_drvs[i].long_press_repeat_time = USHRT_MAX; libinput_multi_init_driver(&pointer_indev_drvs[i]); libinput_multi_set_file(&pointer_indev_drvs[i], pointer_devices[i]); pointer_indevs[i] = lv_indev_drv_register(&pointer_indev_drvs[i]); } /* Set mouse cursor */ if (num_pointer_devices > 0) { lv_obj_t *cursor_obj = lv_img_create(lv_scr_act()); lv_img_set_src(cursor_obj, &bb_cursor_img_dsc); for (int i = 0; i < num_pointer_devices; ++i) { lv_indev_set_cursor(pointer_indevs[i], cursor_obj); } } /* Connect touchscreens */ #define MAX_TOUCHSCREENS 1 char *touchscreens[MAX_TOUCHSCREENS] = { NULL }; lv_indev_drv_t touchscreen_indev_drvs[MAX_TOUCHSCREENS]; lv_indev_t *touchscreen_indevs[MAX_TOUCHSCREENS] = { NULL }; size_t num_touchscreens = libinput_find_devs(LIBINPUT_CAPABILITY_TOUCH, touchscreens, MAX_TOUCHSCREENS, false); for (int i = 0; i < num_touchscreens; ++i) { printf("Connecting touchscreen device %s\n", touchscreens[i]); lv_indev_drv_init(&touchscreen_indev_drvs[i]); touchscreen_indev_drvs[i].type = LV_INDEV_TYPE_POINTER; touchscreen_indev_drvs[i].read_cb = libinput_multi_read; touchscreen_indev_drvs[i].long_press_repeat_time = USHRT_MAX; libinput_multi_init_driver(&touchscreen_indev_drvs[i]); libinput_multi_set_file(&touchscreen_indev_drvs[i], touchscreens[i]); touchscreen_indevs[i] = lv_indev_drv_register(&touchscreen_indev_drvs[i]); } /* Initialise theme and styles */ set_theme(true); lv_style_init(&style_text_normal); lv_style_set_text_font(&style_text_normal, &montserrat_extended_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, hor_res, 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 */ bb_layout_switch_layout(keyboard, SQ2LV_LAYOUT_TERMINAL_US); /* 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; }