diff --git a/README.md b/README.md
index 5d71342..5761ba5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# squeek2lvgl
-squeek2lvgl is a Python script that makes it possible to use [squeekboard] keyboard layouts with [LVGL]'s keyboard widget. To achieve this, [squeekboard]'s YAML syntax for layout definitions is converted into a C file (with an accompanying header) that can then be added to an [LVGL] project.
+squeek2lvgl is a Python script and an accompanying C library that make it possible to use [squeekboard] keyboard layouts with [LVGL]'s keyboard widget. To achieve this, [squeekboard]'s YAML syntax for layout definitions is converted into C files that, together with the library C files, can then be added to an [LVGL] project.
To access [squeekboard]'s layout files, squeek2lvgl shallowly clones the [squeekboard] git repository into a temporary directory and purges it before exiting.
@@ -38,6 +38,33 @@ $ pipenv run python squeek2lvgl.py --input us.yaml --output .
When the process has finished, `sq2lv_layouts.h` and `sq2lv_layouts.c` will have been written into the current directory. Check the [examples] folder for further details about the generated files.
+To facilitate usage, a minimalist C API is available in [sq2lv.h] and [sq2lv.c].
+
+### Integrating into a project
+
+Similar to [LVGL] squeek2lvgl should be added into a project as a git submodule. The generated C files assume that you have integrated [LVGL] as a submodule in the `./lvgl` folder. The C library files in turn assume that you have imported squeek2lvgl as a submodule one folder above the location of the generated files.
+
+The following is an example directory hierarchy:
+
+```
+$ tree .
+.
+├── lvgl # LVGL submodule
+│ ├── ...
+├── main.c # Project's own source files
+├── ...
+├── sq2lv_layouts.c # Layouts generated with squeek2lvgl
+├── sq2lv_layouts.h
+├── ...
+├── squeek2lvgl # squeek2lvgl submodule
+│ ├── sq2lv.c # squeek2lvgl library code
+│ ├── sq2lv.h
+│ ├── ...
+├── ...
+```
+
+Using the directory structure above, you can then add `sq2lv_layouts.c` and `squeek2lvgl/sq2lv.c` into your build process just like the rest of your project's sources.
+
## License
squeek2lvgl is licensed under 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.
@@ -48,3 +75,5 @@ squeek2lvgl is licensed under the GNU General Public License as published by the
[squeekboard]: https://gitlab.gnome.org/World/Phosh/squeekboard
[squeekboard's US layout]: https://gitlab.gnome.org/World/Phosh/squeekboard/-/blob/master/data/keyboards/us.yaml
[examples]: ./examples
+[sq2lv.h]: ./sq2lv.h
+[sq2lv.c]: ./sq2lv.c
diff --git a/examples/us-terminal-with-scancodes/sq2lv_layouts.h b/examples/us-terminal-with-scancodes/sq2lv_layouts.h
index e8cff27..fc7697b 100644
--- a/examples/us-terminal-with-scancodes/sq2lv_layouts.h
+++ b/examples/us-terminal-with-scancodes/sq2lv_layouts.h
@@ -7,6 +7,8 @@
#include "lvgl/lvgl.h"
+#define SQ2LV_SCANCODES_ENABLED 1
+
/* Layout IDs, values can be used as indexes into the sq2lv_layouts array */
typedef enum {
SQ2LV_LAYOUT_TERMINAL_US = 0
diff --git a/examples/us/sq2lv_layouts.h b/examples/us/sq2lv_layouts.h
index 8ae6969..40fae3a 100644
--- a/examples/us/sq2lv_layouts.h
+++ b/examples/us/sq2lv_layouts.h
@@ -7,6 +7,8 @@
#include "lvgl/lvgl.h"
+#define SQ2LV_SCANCODES_ENABLED 0
+
/* Layout IDs, values can be used as indexes into the sq2lv_layouts array */
typedef enum {
SQ2LV_LAYOUT_US = 0
diff --git a/sq2lv.c b/sq2lv.c
new file mode 100644
index 0000000..66a308b
--- /dev/null
+++ b/sq2lv.c
@@ -0,0 +1,231 @@
+/**
+ * Copyright 2021 Johannes Marbach
+ *
+ * This file is part of squeek2lvgl, 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 "sq2lv.h"
+
+
+/**
+ * Static variables
+ */
+
+static sq2lv_layout_id_t current_layout_id = -1;
+
+
+/**
+ * Static prototypes
+ */
+
+/**
+ * Convert a layer index to an LVGL keyboard mode.
+ *
+ * @param layer_index layer index to convert
+ * @return corresponding LVGL keyboard mode
+ */
+static lv_keyboard_mode_t layer_index_to_keyboard_mode(int layer_index);
+
+/**
+ * Convert an LVGL keyboard mode to a layer index.
+ *
+ * @param keyboard_mode LVGL keyboard mode to convert
+ * @return corresponding layer index
+ */
+static int keyboard_mode_to_layer_index(lv_keyboard_mode_t keyboard_mode);
+
+/**
+ * Get the layer index corresponding to a keyboard's current mode.
+ *
+ * @param keyboard keyboard widget
+ * @return layer index
+ */
+static int get_layer_index(lv_obj_t *keyboard);
+
+/**
+ * Get the index of the destination layer for a layer switcher key in the current layout.
+ *
+ * @param keyboard keyboard widget
+ * @param btn_id button index corresponding to the key
+ * @return the destination layer's index or -1 if the key is not a layer switcher
+ */
+static int get_destination_layer_index_for_layer_switcher(lv_obj_t *keyboard, uint16_t btn_id);
+
+
+/**
+ * Static functions
+ */
+
+static lv_keyboard_mode_t layer_index_to_keyboard_mode(int layer_index) {
+ switch (layer_index) {
+ case 0:
+ return LV_KEYBOARD_MODE_TEXT_LOWER;
+ case 1:
+ return LV_KEYBOARD_MODE_TEXT_UPPER;
+ case 2:
+ return LV_KEYBOARD_MODE_SPECIAL;
+ case 3:
+ return LV_KEYBOARD_MODE_NUMBER;
+ default:
+ return -1;
+ }
+}
+
+static int keyboard_mode_to_layer_index(lv_keyboard_mode_t keyboard_mode) {
+ switch (keyboard_mode) {
+ case LV_KEYBOARD_MODE_TEXT_LOWER:
+ return 0;
+ case LV_KEYBOARD_MODE_TEXT_UPPER:
+ return 1;
+ case LV_KEYBOARD_MODE_SPECIAL:
+ return 2;
+ case LV_KEYBOARD_MODE_NUMBER:
+ return 3;
+ default:
+ return -1;
+ }
+}
+
+static int get_layer_index(lv_obj_t *keyboard) {
+ return keyboard_mode_to_layer_index(lv_keyboard_get_mode(keyboard));
+}
+
+static int get_destination_layer_index_for_layer_switcher(lv_obj_t *keyboard, uint16_t btn_id) {
+ if (current_layout_id < 0 || current_layout_id >= sq2lv_num_layouts) {
+ return -1;
+ }
+
+ int layer_index = get_layer_index(keyboard);
+ if (layer_index < 0 || layer_index >= sq2lv_layouts[current_layout_id].num_layers) {
+ return -1;
+ }
+
+ for (int i = 0; i < sq2lv_layouts[current_layout_id].layers[layer_index].num_switchers; ++i) {
+ if (sq2lv_layouts[current_layout_id].layers[layer_index].switcher_idxs[i] == btn_id) {
+ return sq2lv_layouts[current_layout_id].layers[layer_index].switcher_dests[i];
+ }
+ }
+
+ return -1;
+}
+
+
+/**
+ * Public functions
+ */
+
+void sq2lv_switch_layout(lv_obj_t *keyboard, sq2lv_layout_id_t layout_id) {
+ if (layout_id < 0 || layout_id >= sq2lv_num_layouts) {
+ return;
+ }
+
+ /* Assign layers */
+ for (int i = 0; i < sq2lv_layouts[layout_id].num_layers; ++i) {
+ lv_keyboard_set_map(keyboard, layer_index_to_keyboard_mode(i),
+ sq2lv_layouts[layout_id].layers[i].keycaps,
+ sq2lv_layouts[layout_id].layers[i].attributes);
+ }
+
+ /* Switch to default layer if current layer doesn't exist in new layout */
+ int layer_index = get_layer_index(keyboard);
+ if (layer_index < 0 || layer_index >= sq2lv_layouts[layout_id].num_layers) {
+ lv_keyboard_set_mode(keyboard, layer_index_to_keyboard_mode(0));
+ }
+
+ current_layout_id = layout_id;
+}
+
+bool sq2lv_is_layer_switcher(lv_obj_t *keyboard, uint16_t btn_id) {
+ return get_destination_layer_index_for_layer_switcher(keyboard, btn_id) >= 0;
+}
+
+bool sq2lv_switch_layer(lv_obj_t *keyboard, uint16_t btn_id) {
+ int destination_layer_index = get_destination_layer_index_for_layer_switcher(keyboard, btn_id);
+ if (destination_layer_index < 0 || destination_layer_index >= sq2lv_layouts[current_layout_id].num_layers) {
+ return false;
+ }
+
+ lv_keyboard_set_mode(keyboard, layer_index_to_keyboard_mode(destination_layer_index));
+ return true;
+}
+
+bool sq2lv_is_modifier(lv_obj_t *keyboard, uint16_t btn_id) {
+ if (current_layout_id < 0 || current_layout_id >= sq2lv_num_layouts) {
+ return false;
+ }
+
+ int layer_index = get_layer_index(keyboard);
+ if (layer_index < 0 || layer_index >= sq2lv_layouts[current_layout_id].num_layers) {
+ return false;
+ }
+
+ for (int i = 0; i < sq2lv_layouts[current_layout_id].layers[layer_index].num_modifiers; ++i) {
+ if (sq2lv_layouts[current_layout_id].layers[layer_index].modifier_idxs[i] == btn_id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+const int * const sq2lv_get_modifier_indexes(lv_obj_t *keyboard, int *num_modifiers) {
+ if (current_layout_id < 0 || current_layout_id >= sq2lv_num_layouts) {
+ *num_modifiers = 0;
+ return NULL;
+ }
+
+ int layer_index = get_layer_index(keyboard);
+ if (layer_index < 0 || layer_index >= sq2lv_layouts[current_layout_id].num_layers) {
+ *num_modifiers = 0;
+ return NULL;
+ }
+
+ *num_modifiers = sq2lv_layouts[current_layout_id].layers[layer_index].num_modifiers;
+ if (*num_modifiers == 0) {
+ return NULL;
+ }
+
+ return &(sq2lv_layouts[current_layout_id].layers[layer_index].modifier_idxs[0]);
+}
+
+#if SQ2LV_SCANCODES_ENABLED
+const int * const sq2lv_get_scancodes(lv_obj_t *keyboard, uint16_t btn_id, int *num_scancodes) {
+ if (current_layout_id < 0 || current_layout_id >= sq2lv_num_layouts) {
+ *num_scancodes = 0;
+ return NULL;
+ }
+
+ int layer_index = get_layer_index(keyboard);
+ if (layer_index < 0 || layer_index >= sq2lv_layouts[current_layout_id].num_layers) {
+ *num_scancodes = 0;
+ return NULL;
+ }
+
+ if (btn_id >= sq2lv_layouts[current_layout_id].layers[layer_index].num_keys) {
+ *num_scancodes = 0;
+ return NULL;
+ }
+
+ *num_scancodes = sq2lv_layouts[current_layout_id].layers[layer_index].scancode_nums[btn_id];
+ if (*num_scancodes == 0) {
+ return NULL;
+ }
+
+ const int index = sq2lv_layouts[current_layout_id].layers[layer_index].scancode_idxs[btn_id];
+ return &(sq2lv_layouts[current_layout_id].layers[layer_index].scancodes[index]);
+}
+#endif /* SQ2LV_SCANCODES_ENABLED */
diff --git a/sq2lv.h b/sq2lv.h
new file mode 100644
index 0000000..e05e883
--- /dev/null
+++ b/sq2lv.h
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2021 Johannes Marbach
+ *
+ * This file is part of squeek2lvgl, 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 .
+ */
+
+
+#ifndef SQ2LV_H
+#define SQ2LV_H
+
+#include "../sq2lv_layouts.h"
+
+/**
+ * Apply a layout to a keyboard.
+ *
+ * @param keyboard keyboard widget
+ * @param layout_id layout ID
+ */
+void sq2lv_switch_layout(lv_obj_t *keyboard, sq2lv_layout_id_t layout_id);
+
+/**
+ * Check if a key acts as a layer switcher in the current layer.
+ *
+ * @param keyboard keyboard widget
+ * @param btn_id button index corresponding to the key
+ * @return true if a layer switch would happen if the key is pressed, false otherwise
+ */
+bool sq2lv_is_layer_switcher(lv_obj_t *keyboard, uint16_t btn_id);
+
+/**
+ * Attempt to perform a layer switch after pressing a key in the current layer.
+ *
+ * @param keyboard keyboard widget
+ * @param btn_id button index corresponding to the pressed key
+ * @return true if a layer switch was performed, false otherwise
+ */
+bool sq2lv_switch_layer(lv_obj_t *keyboard, uint16_t btn_id);
+
+/**
+ * Check if a key is a modifier in the current layer.
+ *
+ * @param keyboard keyboard widget
+ * @param btn_id button index corresponding to the key
+ * @return true if the key is a modifier, false otherwise
+ */
+bool sq2lv_is_modifier(lv_obj_t *keyboard, uint16_t btn_id);
+
+/**
+ * Get the button indexes for all modifier keys in the current layer.
+ *
+ * @param keyboard keyboard widget
+ * @param num_modifiers pointer to an integer into which the number of modifiers will be written
+ * @return pointer to the array of button indexes corresponding to modifier keys
+ */
+const int * const sq2lv_get_modifier_indexes(lv_obj_t *keyboard, int *num_modifiers);
+
+#if SQ2LV_SCANCODES_ENABLED
+/**
+ * Get scancodes associated with a key in the current layer.
+ *
+ * @param keyboard keyboard widget
+ * @param btn_id button index corresponding to the key
+ * @param num_scancodes pointer to an integer into which the number of scancodes will be written
+ * @return pointer into an array of scancodes at the appropriate index
+ */
+const int * const sq2lv_get_scancodes(lv_obj_t *keyboard, uint16_t btn_id, int *num_scancodes);
+#endif /* SQ2LV_SCANCODES_ENABLED */
+
+#endif /* SQ2LV_H */
diff --git a/squeek2lvgl.py b/squeek2lvgl.py
index 9d2c4b0..7266df0 100644
--- a/squeek2lvgl.py
+++ b/squeek2lvgl.py
@@ -622,6 +622,8 @@ if __name__ == '__main__':
h_builder = SourceFileBuilder()
h_builder.add_include('lvgl/lvgl.h')
h_builder.add_line()
+ h_builder.add_line(f'#define SQ2LV_SCANCODES_ENABLED {1 if args.generate_scancodes else 0}')
+ h_builder.add_line()
layouts = []
unique_scancodes = {}