Add support for dynamic layout switching (en / de)
This commit is contained in:
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ BIN = unl0kr
|
||||
|
||||
|
||||
#Collect the files to compile
|
||||
MAINSRC = ./main.c ./libinput_device_discovery.c ./libinput_multi.c ./libinput_xkb.c
|
||||
MAINSRC = ./main.c ./libinput_device_discovery.c ./libinput_multi.c ./libinput_xkb.c ./layouts.c ./montserrat_extended_32.c
|
||||
|
||||
include $(LVGL_DIR)/lvgl/lvgl.mk
|
||||
include $(LVGL_DIR)/lv_drivers/lv_drivers.mk
|
||||
|
15
README.md
15
README.md
@@ -21,6 +21,7 @@ The biggest obstacle is input processing. [lv_drivers] provides an evdev interfa
|
||||
- On-screen keyboard control via one or more hardware keyboard (including support for multiple layouts using XKB)
|
||||
- Works great on my laptop keyboard but occasionally drops keys on my Ergodox EZ)
|
||||
- On-screen keyboard control via touchscreen (tested on PinePhone)
|
||||
- Switching on-screen keyboard layout at runtime (layouts still to be refined, currently only supports US English and German)
|
||||
|
||||
## To do
|
||||
|
||||
@@ -38,7 +39,7 @@ Upstreamed contributions so far:
|
||||
- [Add full keyboard support to libinput/evdev driver] (⏳ in review)
|
||||
- [Automatic device discovery via libinput] (⏳ in review)
|
||||
|
||||
# Operation
|
||||
# Development
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -47,7 +48,7 @@ Upstreamed contributions so far:
|
||||
- [libinput]
|
||||
- [libxkbcommon]
|
||||
|
||||
## Testing
|
||||
## Building & running
|
||||
|
||||
For development and testing you can run the app in a VT. `sudo` is needed to access input device files.
|
||||
|
||||
@@ -57,6 +58,14 @@ $ sudo chvt 2
|
||||
$ sudo ./unl0kr
|
||||
```
|
||||
|
||||
## Changing fonts
|
||||
|
||||
Fonts need to be converted to C arrays before they can be used with [lvgl]. This is most conveniently done using the official [online font converter]. Useful unicode ranges for the conversion are 0x0020-0x007F (basic Latin) and 0x00A0-0x00FF (Latin-1 supplement). For the various `LV_SYMBOL_...` glyphs, make sure to also add [Font Awesome] with the following code points:
|
||||
|
||||
```
|
||||
0xF001, 0xF008, 0xF00B, 0xF00C, 0xF00D, 0xF011, 0xF013, 0xF015, 0xF019, 0xF01C, 0xF021, 0xF026, 0xF027, 0xF028, 0xF03E, 0xF0E0, 0xF304, 0xF043, 0xF048, 0xF04B, 0xF04C, 0xF04D, 0xF051, 0xF052, 0xF053, 0xF054, 0xF067, 0xF068, 0xF06E, 0xF070, 0xF071, 0xF074, 0xF077, 0xF078, 0xF079, 0xF07B, 0xF093, 0xF095, 0xF0C4, 0xF0C5, 0xF0C7, 0xF0C9, 0xF0E7, 0xF0EA, 0xF0F3, 0xF11C, 0xF124, 0xF158, 0xF1EB, 0xF240, 0xF241, 0xF242, 0xF243, 0xF244, 0xF287, 0xF293, 0xF2ED, 0xF55A, 0xF7C2, 0xF8A2
|
||||
```
|
||||
|
||||
# Acknowledgements
|
||||
|
||||
The [lv_port_linux_frame_buffer] project served as a starting point for the codebase. The mouse cursor image was taken from [lv_sim_emscripten].
|
||||
@@ -71,6 +80,8 @@ Unl0kr is licensed under the GNU General Public License as published by the Free
|
||||
[lv_sim_emscripten]: https://github.com/lvgl/lv_sim_emscripten/blob/master/mouse_cursor_icon.c]
|
||||
[libinput]: https://gitlab.freedesktop.org/libinput/libinput
|
||||
[libxkbcommon]: https://github.com/xkbcommon/libxkbcommon
|
||||
[online font converter]: https://lvgl.io/tools/fontconverter
|
||||
[Font Awesome]: https://lvgl.io/assets/others/FontAwesome5-Solid+Brands+Regular.woff
|
||||
[Add support for pointer devices to libinput driver]: https://github.com/lvgl/lv_drivers/pull/150
|
||||
[Add support for keypads to libinput driver]: https://github.com/lvgl/lv_drivers/pull/152
|
||||
[Don't compile example assets when disabled in lv_conf.h]: https://github.com/lvgl/lvgl/pull/2523
|
||||
|
152
layouts.c
Normal file
152
layouts.c
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "layouts.h"
|
||||
|
||||
/**
|
||||
* English (US)
|
||||
**/
|
||||
|
||||
#define NAME_ENGLISH_US "English (US)"
|
||||
|
||||
/* Lowercase layer */
|
||||
#define KEYS_LOWER_ENGLISH_US { \
|
||||
"1#", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", LV_SYMBOL_BACKSPACE, "\n", \
|
||||
"ABC", "a", "s", "d", "f", "g", "h", "j", "k", "l", LV_SYMBOL_NEW_LINE, "\n", \
|
||||
"_", "-", "z", "x", "c", "v", "b", "n", "m", ".", ",", ":", "\n", \
|
||||
LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" \
|
||||
}
|
||||
#define ATTRIBUTES_LOWER_ENGLISH_US { \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, LV_BTNMATRIX_CTRL_CHECKED | 7, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, LV_BTNMATRIX_CTRL_CHECKED | 7, \
|
||||
LV_BTNMATRIX_CTRL_CHECKED | 1, LV_BTNMATRIX_CTRL_CHECKED | 1, 1, 1, 1, 1, 1, 1, 1, LV_BTNMATRIX_CTRL_CHECKED | 1, LV_BTNMATRIX_CTRL_CHECKED | 1, LV_BTNMATRIX_CTRL_CHECKED | 1, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 \
|
||||
}
|
||||
|
||||
/* Uppercase layer */
|
||||
#define KEYS_UPPER_ENGLISH_US { \
|
||||
"1#", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", LV_SYMBOL_BACKSPACE, "\n", \
|
||||
"abc", "A", "S", "D", "F", "G", "H", "J", "K", "L", LV_SYMBOL_NEW_LINE, "\n", \
|
||||
"_", "-", "Z", "X", "C", "V", "B", "N", "M", ".", ",", ":", "\n", \
|
||||
LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" \
|
||||
}
|
||||
#define ATTRIBUTES_UPPER_ENGLISH_US { \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, LV_BTNMATRIX_CTRL_CHECKED | 7, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, LV_BTNMATRIX_CTRL_CHECKED | 7, \
|
||||
LV_BTNMATRIX_CTRL_CHECKED | 1, LV_BTNMATRIX_CTRL_CHECKED | 1, 1, 1, 1, 1, 1, 1, 1, LV_BTNMATRIX_CTRL_CHECKED | 1, LV_BTNMATRIX_CTRL_CHECKED | 1, LV_BTNMATRIX_CTRL_CHECKED | 1, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 \
|
||||
}
|
||||
|
||||
/* Symbol layer */
|
||||
#define KEYS_SYMBOL_ENGLISH_US { \
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n", \
|
||||
"abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n", \
|
||||
"\\", "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n", \
|
||||
LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" \
|
||||
}
|
||||
#define ATTRIBUTES_SYMBOL_ENGLISH_US { \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, LV_BTNMATRIX_CTRL_CHECKED | 2, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 \
|
||||
}
|
||||
|
||||
/**
|
||||
* German
|
||||
**/
|
||||
|
||||
#define NAME_GERMAN "German"
|
||||
|
||||
/* Lowercase layer */
|
||||
#define KEYS_LOWER_GERMAN { \
|
||||
"q", "w", "e", "r", "t", "z", "u", "i", "o", "p", "ü", "\n", \
|
||||
"a", "s", "d", "f", "g", "h", "j", "k", "l", "ö", "ä", "\n", \
|
||||
"ABC", "_", "y", "x", "c", "v", "b", "n", "m", "_", LV_SYMBOL_BACKSPACE, "\n", \
|
||||
"1#", LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" \
|
||||
}
|
||||
#define ATTRIBUTES_LOWER_GERMAN { \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 3, LV_BTNMATRIX_CTRL_HIDDEN | 1, 2, 2, 2, 2, 2, 2, 2, LV_BTNMATRIX_CTRL_HIDDEN | 1, LV_BTNMATRIX_CTRL_CHECKED | 3, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 7, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 \
|
||||
}
|
||||
|
||||
/* Uppercase layer */
|
||||
#define KEYS_UPPER_GERMAN { \
|
||||
"Q", "W", "E", "R", "T", "Z", "U", "I", "O", "P", "Ü", "\n", \
|
||||
"A", "S", "D", "F", "G", "H", "J", "K", "L", "Ö", "Ä", "\n", \
|
||||
"abc", "_", "Y", "X", "C", "V", "B", "N", "M", "_", LV_SYMBOL_BACKSPACE, "\n", \
|
||||
"1#", LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" \
|
||||
}
|
||||
#define ATTRIBUTES_UPPER_GERMAN { \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 3, LV_BTNMATRIX_CTRL_HIDDEN | 1, 2, 2, 2, 2, 2, 2, 2, LV_BTNMATRIX_CTRL_HIDDEN | 1, LV_BTNMATRIX_CTRL_CHECKED | 3, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 7, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 \
|
||||
}
|
||||
|
||||
/* Symbol layer */
|
||||
#define KEYS_SYMBOL_GERMAN { \
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n", \
|
||||
"abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n", \
|
||||
"\\", "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n", \
|
||||
LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" \
|
||||
}
|
||||
#define ATTRIBUTES_SYMBOL_GERMAN { \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, LV_BTNMATRIX_CTRL_CHECKED | 2, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
|
||||
LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 \
|
||||
}
|
||||
|
||||
/**
|
||||
* All layouts
|
||||
**/
|
||||
|
||||
static const char * const names =
|
||||
NAME_ENGLISH_US "\n"
|
||||
NAME_GERMAN;
|
||||
|
||||
static const char ** const keys_lower[] = {
|
||||
(const char * const [])KEYS_LOWER_ENGLISH_US,
|
||||
(const char * const [])KEYS_LOWER_GERMAN
|
||||
};
|
||||
|
||||
static const lv_btnmatrix_ctrl_t * const attributes_lower[] = {
|
||||
(lv_btnmatrix_ctrl_t[])ATTRIBUTES_LOWER_ENGLISH_US,
|
||||
(lv_btnmatrix_ctrl_t[])ATTRIBUTES_LOWER_GERMAN
|
||||
};
|
||||
|
||||
static const char ** const keys_upper[] = {
|
||||
(const char * const [])KEYS_UPPER_ENGLISH_US,
|
||||
(const char * const [])KEYS_UPPER_GERMAN
|
||||
};
|
||||
|
||||
static const lv_btnmatrix_ctrl_t *attributes_upper[] = {
|
||||
(lv_btnmatrix_ctrl_t[])ATTRIBUTES_UPPER_ENGLISH_US,
|
||||
(lv_btnmatrix_ctrl_t[])ATTRIBUTES_UPPER_GERMAN
|
||||
};
|
||||
|
||||
static const char ** const keys_symbol[] = {
|
||||
(const char * const [])KEYS_SYMBOL_ENGLISH_US,
|
||||
(const char * const [])KEYS_SYMBOL_GERMAN
|
||||
};
|
||||
|
||||
static const lv_btnmatrix_ctrl_t *attributes_symbol[] = {
|
||||
(lv_btnmatrix_ctrl_t[])ATTRIBUTES_SYMBOL_ENGLISH_US,
|
||||
(lv_btnmatrix_ctrl_t[])ATTRIBUTES_SYMBOL_GERMAN
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions
|
||||
**/
|
||||
|
||||
char *get_layout_names(void) {
|
||||
return names;
|
||||
}
|
||||
|
||||
void apply_layout(lv_obj_t *keyboard, layout_t layout) {
|
||||
if (layout < 0 || layout >= NUM_LAYOUTS) {
|
||||
return;
|
||||
}
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER, keys_lower[layout], attributes_lower[layout]);
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_TEXT_UPPER, keys_upper[layout], attributes_upper[layout]);
|
||||
lv_keyboard_set_map(keyboard, LV_KEYBOARD_MODE_SPECIAL, keys_symbol[layout], attributes_symbol[layout]);
|
||||
}
|
16
layouts.h
Normal file
16
layouts.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef LAYOUTS_H
|
||||
#define LAYOUTS_H
|
||||
|
||||
#include "lvgl/lvgl.h"
|
||||
|
||||
typedef enum {
|
||||
LAYOUT_ENGLISH_US = 0,
|
||||
LAYOUT_GERMAN = 1
|
||||
} layout_t;
|
||||
|
||||
#define NUM_LAYOUTS 2
|
||||
|
||||
char *get_layout_names(void);
|
||||
void apply_layout(lv_obj_t *keyboard, layout_t layout);
|
||||
|
||||
#endif /* LAYOUTS_H */
|
31
main.c
31
main.c
@@ -9,6 +9,7 @@
|
||||
#include "libinput_device_discovery.h"
|
||||
#include "libinput_multi.h"
|
||||
#include "libinput_xkb.h"
|
||||
#include "layouts.h"
|
||||
|
||||
// Mouse cursor image (from https://github.com/lvgl/lv_sim_emscripten/blob/master/mouse_cursor_icon.c)
|
||||
|
||||
@@ -115,7 +116,11 @@ lv_img_dsc_t mouse_cursor_icon = {
|
||||
.data = mouse_cursor_icon_map,
|
||||
};
|
||||
|
||||
// Keyboard event callbacks
|
||||
// Global widgets
|
||||
|
||||
lv_obj_t *keyboard = NULL;
|
||||
|
||||
// Event callbacks
|
||||
|
||||
void keyboard_event_ready_cb(lv_event_t *e);
|
||||
|
||||
@@ -132,6 +137,13 @@ void keyboard_event_cancel_cb(lv_event_t *e) {
|
||||
abort();
|
||||
}
|
||||
|
||||
void keymap_dropdown_event_cb(lv_event_t * e) {
|
||||
if(lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) {
|
||||
return;
|
||||
}
|
||||
apply_layout(keyboard, lv_dropdown_get_selected(lv_event_get_target(e)));
|
||||
}
|
||||
|
||||
// Main
|
||||
|
||||
int main(void)
|
||||
@@ -220,6 +232,9 @@ int main(void)
|
||||
|
||||
// Build the UI...
|
||||
|
||||
// Register fonts
|
||||
LV_FONT_DECLARE(montserrat_extended_32);
|
||||
|
||||
// Figure out a few numbers for sizing and positioning
|
||||
int row_height = ver_res / 6;
|
||||
if (4 * row_height > ver_res / 2) {
|
||||
@@ -228,13 +243,13 @@ int main(void)
|
||||
const int keyboard_height = 4 * row_height;
|
||||
|
||||
// Keyboard
|
||||
lv_obj_t *keyboard = lv_keyboard_create(lv_scr_act());
|
||||
keyboard = lv_keyboard_create(lv_scr_act());
|
||||
lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER);
|
||||
lv_obj_set_pos(keyboard, 0, 0);
|
||||
lv_obj_set_size(keyboard, hor_res, keyboard_height);
|
||||
lv_style_t style_keyboard;
|
||||
lv_style_init(&style_keyboard);
|
||||
lv_style_set_text_font(&style_keyboard, &lv_font_montserrat_32);
|
||||
lv_style_set_text_font(&style_keyboard, &montserrat_extended_32);
|
||||
lv_obj_add_style(keyboard, &style_keyboard, 0);
|
||||
|
||||
// Label
|
||||
@@ -245,7 +260,7 @@ int main(void)
|
||||
lv_obj_align(spangroup, LV_ALIGN_CENTER, 0, ver_res / 2 - keyboard_height);
|
||||
static lv_style_t style_spangroup;
|
||||
lv_style_init(&style_spangroup);
|
||||
lv_style_set_text_font(&style_spangroup, &lv_font_montserrat_32);
|
||||
lv_style_set_text_font(&style_spangroup, &montserrat_extended_32);
|
||||
lv_obj_add_style(spangroup, &style_spangroup, 0);
|
||||
lv_span_t *span1 = lv_spangroup_new_span(spangroup);
|
||||
lv_span_set_text(span1, "Password required to unlock ");
|
||||
@@ -263,7 +278,7 @@ int main(void)
|
||||
lv_obj_add_state(textarea, LV_STATE_FOCUSED);
|
||||
lv_style_t style_textarea;
|
||||
lv_style_init(&style_textarea);
|
||||
lv_style_set_text_font(&style_textarea, &lv_font_montserrat_32);
|
||||
lv_style_set_text_font(&style_textarea, &montserrat_extended_32);
|
||||
lv_obj_add_style(textarea, &style_textarea, 0);
|
||||
|
||||
// Route keyboard input into textarea
|
||||
@@ -279,6 +294,12 @@ int main(void)
|
||||
lv_obj_add_event_cb(keyboard, keyboard_event_cancel_cb, LV_EVENT_CANCEL, NULL);
|
||||
lv_obj_add_event_cb(keyboard, keyboard_event_ready_cb, LV_EVENT_READY, NULL);
|
||||
|
||||
// Keymap dropdown
|
||||
lv_obj_t *dropdown = lv_dropdown_create(lv_scr_act());
|
||||
lv_dropdown_set_options(dropdown, get_layout_names());
|
||||
lv_obj_align(dropdown, LV_ALIGN_TOP_RIGHT, -20, 20);
|
||||
lv_obj_add_event_cb(dropdown, keymap_dropdown_event_cb, LV_EVENT_ALL, NULL);
|
||||
|
||||
// Run lvgl in tickless mode
|
||||
while(1) {
|
||||
lv_task_handler();
|
||||
|
9031
montserrat_extended_32.c
Normal file
9031
montserrat_extended_32.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user