diff --git a/README.md b/README.md index 7464987..ad56595 100644 --- a/README.md +++ b/README.md @@ -56,23 +56,34 @@ Below is a summary of contributions upstreamed thus far. # Usage -For an overview of available command line options, run unl0kr with the `-h` or `--help` argument. +A man page is planned to be added with #6. For the time being, you can get an overview of available command line options by running unl0kr with the `-h` or `--help` argument. ``` $ unl0kr --help Usage: unl0kr [OPTION] Mandatory arguments to long options are mandatory for short options too. - -g, --geometry=NxM Force a display size of N horizontal times M vertical pixels - -h, --help Print this message and exit - -v, --verbose Enable more detailed logging output on STDERR - -V, --version Print the unl0kr version and exit + -c, --config=PATH Locaton of the main config file. Defaults to + /etc/unl0kr.conf. + -C, --config-override Location of the config override file. Values in + this file override values for the same keys in the + main config file. If specified multiple times, the + values from consecutive files will be merged in + order. + -g, --geometry=NxM Force a display size of N horizontal times M + vertical pixels + -h, --help Print this message and exit + -v, --verbose Enable more detailed logging output on STDERR + -V, --version Print the unl0kr version and exit ``` +For an example configuration file, see [unl0kr.conf]. + # Development ## Dependencies +- [inih] - [lvgl] (git submodule / linked statically) - [lv_drivers] (git submodule / linked statically) - [squeek2lvgl] (git submodule / linked statically) @@ -172,6 +183,7 @@ The [FontAwesome] font is licensed under the Open Font License version 1.1. [feat(msgbox): omit title label unless needed]: https://github.com/lvgl/lvgl/pull/2539 [fix(btnmatrix): make ORed values work correctly with lv_btnmatrix_has_btn_ctrl]: https://github.com/lvgl/lvgl/pull/2571 [fix(examples) don't compile assets unless needed]: https://github.com/lvgl/lvgl/pull/2523 +[inih]: https://github.com/benhoyt/inih [libinput]: https://gitlab.freedesktop.org/libinput/libinput [libxkbcommon]: https://github.com/xkbcommon/libxkbcommon [lv_drivers]: https://github.com/lvgl/lv_drivers @@ -183,5 +195,6 @@ The [FontAwesome] font is licensed under the Open Font License version 1.1. [osk-sdl]: https://gitlab.com/postmarketOS/osk-sdl [squeek2lvgl]: https://gitlab.com/cherrypicker/squeek2lvgl [squeekboard layouts]: https://gitlab.gnome.org/World/Phosh/squeekboard/-/tree/master/data/keyboards +[unl0kr.conf]: ./unl0kr.conf [v1 milestone]: https://gitlab.com/cherrypicker/unl0kr/-/milestones/1 [wiki]: https://gitlab.com/cherrypicker/unl0kr/-/wikis/home diff --git a/command_line.c b/command_line.c index e47207a..51c38fb 100644 --- a/command_line.c +++ b/command_line.c @@ -20,6 +20,7 @@ #include "command_line.h" +#include "log.h" #include "unl0kr.h" #include @@ -32,7 +33,7 @@ */ /** - * Initialise a command line options struct with default values. + * Initialise a command line options struct with default values and exit on failure. * * @param opts pointer to the options struct */ @@ -49,6 +50,15 @@ static void print_usage(); */ static void init_opts(ul_cli_opts *opts) { + opts->num_config_files = 1; + + opts->config_files = malloc(sizeof(char *)); + if (!opts->config_files) { + ul_log(UL_LOG_LEVEL_ERROR, "Could not allocate memory for config file paths"); + exit(EXIT_FAILURE); + } + opts->config_files[0] = "/etc/unl0kr.conf"; + opts->hor_res = -1; opts->ver_res = -1; opts->verbose = false; @@ -56,13 +66,23 @@ static void init_opts(ul_cli_opts *opts) { static void print_usage() { fprintf(stderr, + /*-------------------------------- 78 CHARS --------------------------------*/ "Usage: unl0kr [OPTION]\n" "\n" "Mandatory arguments to long options are mandatory for short options too.\n" - " -g, --geometry=NxM Force a display size of N horizontal times M vertical pixels\n" - " -h, --help Print this message and exit\n" - " -v, --verbose Enable more detailed logging output on STDERR\n" - " -V, --version Print the unl0kr version and exit\n"); + " -c, --config=PATH Locaton of the main config file. Defaults to\n" + " /etc/unl0kr.conf.\n" + " -C, --config-override Location of the config override file. Values in\n" + " this file override values for the same keys in the\n" + " main config file. If specified multiple times, the\n" + " values from consecutive files will be merged in\n" + " order.\n" + " -g, --geometry=NxM Force a display size of N horizontal times M\n" + " vertical pixels\n" + " -h, --help Print this message and exit\n" + " -v, --verbose Enable more detailed logging output on STDERR\n" + " -V, --version Print the unl0kr version and exit\n"); + /*-------------------------------- 78 CHARS --------------------------------*/ } @@ -74,20 +94,34 @@ void ul_cli_parse_opts(int argc, char *argv[], ul_cli_opts *opts) { init_opts(opts); struct option long_opts[] = { - { "geometry", required_argument, NULL, 'g' }, - { "help", no_argument, NULL, 'h' }, - { "verbose", no_argument, NULL, 'v' }, - { "version", no_argument, NULL, 'V' }, + { "config", required_argument, NULL, 'c' }, + { "config-override", required_argument, NULL, 'C' }, + { "geometry", required_argument, NULL, 'g' }, + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 } }; int opt, index = 0; - while ((opt = getopt_long(argc, argv, "g:hvV", long_opts, &index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:C:g:hvV", long_opts, &index)) != -1) { switch (opt) { + case 'c': + opts->config_files[0] = optarg; + break; + case 'C': + opts->config_files = realloc(opts->config_files, (opts->num_config_files + 1) * sizeof(char *)); + if (!opts->config_files) { + ul_log(UL_LOG_LEVEL_ERROR, "Could not allocate memory for config file paths"); + exit(EXIT_FAILURE); + } + opts->config_files[opts->num_config_files] = optarg; + opts->num_config_files++; + break; case 'g': if (sscanf(optarg, "%ix%i", &(opts->hor_res), &(opts->ver_res)) != 2) { - fprintf(stderr, "Error: invalid geometry argument \"%s\"\n", optarg); + ul_log(UL_LOG_LEVEL_ERROR, "Invalid geometry argument \"%s\"\n", optarg); exit(EXIT_FAILURE); } break; diff --git a/command_line.h b/command_line.h index 8da867c..beb357d 100644 --- a/command_line.h +++ b/command_line.h @@ -27,6 +27,10 @@ * Options parsed from command line arguments */ typedef struct { + /* Number of config files */ + int num_config_files; + /* Paths of config file */ + const char **config_files; /* Horizontal display resolution */ int hor_res; /* Vertical display resolution */ diff --git a/config.c b/config.c new file mode 100644 index 0000000..edb10b1 --- /dev/null +++ b/config.c @@ -0,0 +1,144 @@ +/** + * Copyright 2021 Johannes Marbach + * + * This file is part of unl0kr, 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 "config.h" + +#include "log.h" + +#include + +#include "squeek2lvgl/sq2lv.h" + + +/** + * Static prototypes + */ + +/** + * Initialise a config options struct with default values. + * + * @param opts pointer to the options struct + */ +static void init_opts(ul_config_opts *opts); + +/** + * Parse options from a configuration file. + * + * @param path path to configuration file + * @param opts pointer for writing the parsed options into + */ +static void parse_file(const char *path, ul_config_opts *opts); + +/** + * Handle parsing events from INIH. + * + * @param user_data pointer to user data + * @param section current section name + * @param key option key + * @param value option value + * @return 0 on error, non-0 otherwise + */ +static int parsing_handler(void* user_data, const char* section, const char* key, const char* value); + +/** + * Attempt to parse a boolean value. + * + * @param value string to parse + * @param result pointer to write result into if parsing is successful + * @return true on success, false otherwise + */ +static bool parse_bool(const char *value, bool *result); + + +/** + * Static functions + */ + +static void init_opts(ul_config_opts *opts) { + opts->general.animations = false; + opts->textarea.obscured = true; + opts->keyboard.layout_id = SQ2LV_LAYOUT_US; + opts->keyboard.popovers = false; +} + +static void parse_file(const char *path, ul_config_opts *opts) { + if (ini_parse(path, parsing_handler, opts) != 0) { + ul_log(UL_LOG_LEVEL_ERROR, "Ignoring invalid config file %s", path); + } +} + +static int parsing_handler(void* user_data, const char* section, const char* key, const char* value) { + ul_config_opts *opts = (ul_config_opts *)user_data; + + if (strcmp(section, "general") == 0) { + if (strcmp(key, "animations") == 0) { + if (parse_bool(value, &(opts->general.animations))) { + return 1; + } + } + } else if (strcmp(section, "textarea") == 0) { + if (strcmp(key, "obscured") == 0) { + if (parse_bool(value, &(opts->textarea.obscured))) { + return 1; + } + } + } else if (strcmp(section, "keyboard") == 0) { + if (strcmp(key, "popovers") == 0) { + if (parse_bool(value, &(opts->keyboard.popovers))) { + return 1; + } + } else if (strcmp(key, "layout") == 0) { + sq2lv_layout_id_t id = sq2lv_find_layout_with_short_name(value); + if (id != SQ2LV_LAYOUT_NONE) { + opts->keyboard.layout_id = id; + return 1; + } + }; + } + + ul_log(UL_LOG_LEVEL_ERROR, "Ignoring invalid config value \"%s\" for key \"%s\" in section \"%s\"", value, key, section); + return 1; /* Return 1 (true) so that we can use the return value of ini_parse exclusively for file-level errors (e.g. file not found) */ +} + +static bool parse_bool(const char *value, bool *result) { + if (strcmp(value, "true") == 0) { + *result = true; + return true; + } + + if (strcmp(value, "false") == 0) { + *result = false; + return true; + } + + return false; +} + + +/** + * Public functions + */ + +void ul_config_parse(const char **files, int num_files, ul_config_opts *opts) { + init_opts(opts); + for (int i = 0; i < num_files; ++i) { + parse_file(files[i], opts); + } +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..4e1ef0b --- /dev/null +++ b/config.h @@ -0,0 +1,75 @@ +/** + * Copyright 2021 Johannes Marbach + * + * This file is part of unl0kr, 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 UL_CONFIG_H +#define UL_CONFIG_H + +#include "sq2lv_layouts.h" + +#include + +/** + * General options + */ +typedef struct { + /* If true, use animations */ + bool animations; +} ul_config_opts_general; + +/** + * Options related to the password textarea + */ +typedef struct { + /* If true, disguise the entered text with dots */ + bool obscured; +} ul_config_opts_textarea; + +/** + * Options related to the keyboard + */ +typedef struct { + /* Keyboard layout */ + sq2lv_layout_id_t layout_id; + /* If true, display key popovers on press */ + bool popovers; +} ul_config_opts_keyboard; + +/** + * Options parsed from config file(s) + */ +typedef struct { + /* General options */ + ul_config_opts_general general; + /* Options related to the password textarea */ + ul_config_opts_textarea textarea; + /* Options related to the keyboard */ + ul_config_opts_keyboard keyboard; +} ul_config_opts; + +/** + * Parse options from one or more configuration files. + * + * @param files paths to configuration files + * @param num_files number of configuration files + * @param opts pointer for writing the parsed options into + */ +void ul_config_parse(const char **files, int num_files, ul_config_opts *opts); + +#endif /* UL_CONFIG_H */ diff --git a/main.c b/main.c index 581200b..83d23e0 100644 --- a/main.c +++ b/main.c @@ -19,6 +19,7 @@ #include "command_line.h" +#include "config.h" #include "indev.h" #include "log.h" #include "unl0kr.h" @@ -50,8 +51,11 @@ LV_FONT_DECLARE(montserrat_extended_32); * Static variables */ +ul_cli_opts cli_opts; +ul_config_opts conf_opts; + bool is_dark_theme = false; -bool is_password_hidden = true; +bool is_password_obscured = true; bool is_keyboard_hidden = false; lv_obj_t *keyboard = NULL; @@ -92,14 +96,14 @@ static void toggle_pw_btn_clicked_cb(lv_event_t *event); /** * Toggle between showing and hiding the password. */ -static void toggle_password_hidden(void); +static void toggle_password_obscured(void); /** * Show / hide the password. * * @param is_hidden true if the password should be hidden, false if it should be shown */ -static void set_password_hidden(bool is_hidden); +static void set_password_obscured(bool is_obscured); /** * Handle LV_EVENT_CLICKED events from the show/hide keyboard toggle button. @@ -196,17 +200,17 @@ static void set_theme(bool is_dark) { } static void toggle_pw_btn_clicked_cb(lv_event_t *event) { - toggle_password_hidden(); + toggle_password_obscured(); } -static void toggle_password_hidden(void) { - is_password_hidden = !is_password_hidden; - set_password_hidden(is_password_hidden); +static void toggle_password_obscured(void) { + is_password_obscured = !is_password_obscured; + set_password_obscured(is_password_obscured); } -static void set_password_hidden(bool is_hidden) { +static void set_password_obscured(bool is_obscured) { lv_obj_t *textarea = lv_keyboard_get_textarea(keyboard); - lv_textarea_set_password_mode(textarea, is_hidden); + lv_textarea_set_password_mode(textarea, is_obscured); } static void toggle_kb_btn_clicked_cb(lv_event_t *event) { @@ -219,6 +223,11 @@ static void toggle_keyboard_hidden(void) { } static void set_keyboard_hidden(bool is_hidden) { + if (!conf_opts.general.animations) { + lv_obj_set_y(keyboard, is_hidden ? lv_obj_get_height(keyboard) : 0); + return; + } + lv_anim_t keyboard_anim; lv_anim_init(&keyboard_anim); lv_anim_set_var(&keyboard_anim, keyboard); @@ -330,25 +339,19 @@ static void keyboard_ready_cb(lv_event_t *event) { int main(int argc, char *argv[]) { /* Parse command line options */ - ul_cli_opts opts; - ul_cli_parse_opts(argc, argv, &opts); - - /* Parse config file */ - ul_config_opts config; - ul_config_init(&config); - ul_config_parse(opts.config, &config); - -printf("anim %d, pops %d, layout %d\n", config.animations, config.popovers, config.layout_id); -exit(1); + ul_cli_parse_opts(argc, argv, &cli_opts); /* Set up log level */ - if (opts.verbose) { + if (cli_opts.verbose) { ul_log_set_level(UL_LOG_LEVEL_VERBOSE); } /* Announce ourselves */ ul_log(UL_LOG_LEVEL_VERBOSE, "unl0kr %s", UL_VERSION); + /* Parse config files */ + ul_config_parse(cli_opts.config_files, cli_opts.num_config_files, &conf_opts); + /* Initialise LVGL and set up logging callback */ lv_init(); lv_log_register_print_cb(ul_log_print_cb); @@ -360,11 +363,11 @@ exit(1); fbdev_get_sizes(&hor_res, &ver_res); /* Override display size with command line options if necessary */ - if (opts.hor_res > 0) { - hor_res = LV_MIN(hor_res, opts.hor_res); + if (cli_opts.hor_res > 0) { + hor_res = LV_MIN(hor_res, cli_opts.hor_res); } - if (opts.ver_res > 0) { - ver_res = LV_MIN(ver_res, opts.ver_res); + if (cli_opts.ver_res > 0) { + ver_res = LV_MIN(ver_res, cli_opts.ver_res); } /* Prepare display buffer */ @@ -461,7 +464,7 @@ exit(1); /* Route physical keyboard input into textarea */ ul_indev_set_up_textarea_for_keyboard_input(textarea); - /* Show / hide password button */ + /* Reveal / obscure password button */ lv_obj_t *toggle_pw_btn = lv_btn_create(lv_scr_act()); lv_obj_align(toggle_pw_btn, LV_ALIGN_CENTER, (hor_res - 60 > 512 ? 512 : hor_res - 60) / 2 + 32, ver_res / 2 - keyboard_height - 3 * row_height / 2); lv_obj_set_size(toggle_pw_btn, 64, 64); @@ -488,7 +491,6 @@ exit(1); keyboard = lv_keyboard_create(lv_scr_act()); lv_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER); lv_keyboard_set_textarea(keyboard, textarea); - // lv_btnmatrix_set_popovers(keyboard, true); lv_obj_remove_event_cb(keyboard, lv_keyboard_def_event_cb); lv_obj_add_event_cb(keyboard, keyboard_draw_part_begin_cb, LV_EVENT_DRAW_PART_BEGIN, NULL); lv_obj_add_event_cb(keyboard, keyboard_value_changed_cb, LV_EVENT_VALUE_CHANGED, NULL); @@ -497,9 +499,15 @@ exit(1); lv_obj_set_size(keyboard, hor_res, keyboard_height); lv_obj_add_style(keyboard, &style_text_normal, 0); - /* Apply defaults */ - sq2lv_switch_layout(keyboard, 0); - set_password_hidden(is_password_hidden); + /* Apply textarea options */ + set_password_obscured(conf_opts.textarea.obscured); + + /* Apply keyboard options */ + sq2lv_switch_layout(keyboard, conf_opts.keyboard.layout_id); + lv_dropdown_set_selected(layout_dropdown, conf_opts.keyboard.layout_id); + if (conf_opts.keyboard.popovers) { + // lv_keyboard_set_popovers(keyboard, true); + } /* Run lvgl in "tickless" mode */ while(1) { diff --git a/meson.build b/meson.build index f389253..748254a 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ add_project_arguments('-DUL_VERSION="@0@"'.format(meson.project_version()), lang unl0kr_sources = [ 'command_line.c', + 'config.c', 'cursor.c', 'indev.c', 'log.c', @@ -49,7 +50,8 @@ executable( sources: unl0kr_sources + squeek2lvgl_sources + lvgl_sources + lv_drivers_sources, include_directories: ['lvgl', 'lv_drivers'], dependencies: [ - dependency('xkbcommon'), + dependency('inih'), dependency('libinput'), + dependency('xkbcommon') ] ) diff --git a/unl0kr.conf b/unl0kr.conf new file mode 100644 index 0000000..2c288a1 --- /dev/null +++ b/unl0kr.conf @@ -0,0 +1,9 @@ +[general] +animations=true + +[textarea] +obscured=true + +[keyboard] +popovers=true +layout=de