Load configurable settings from configuration file

Closes: #7
This commit is contained in:
Johannes Marbach
2021-09-25 21:02:00 +02:00
parent 1ebc98476d
commit 1e6a04fec2
8 changed files with 335 additions and 46 deletions

View File

@@ -56,23 +56,34 @@ Below is a summary of contributions upstreamed thus far.
# Usage # 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 $ unl0kr --help
Usage: unl0kr [OPTION] Usage: unl0kr [OPTION]
Mandatory arguments to long options are mandatory for short options too. 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 -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 -h, --help Print this message and exit
-v, --verbose Enable more detailed logging output on STDERR -v, --verbose Enable more detailed logging output on STDERR
-V, --version Print the unl0kr version and exit -V, --version Print the unl0kr version and exit
``` ```
For an example configuration file, see [unl0kr.conf].
# Development # Development
## Dependencies ## Dependencies
- [inih]
- [lvgl] (git submodule / linked statically) - [lvgl] (git submodule / linked statically)
- [lv_drivers] (git submodule / linked statically) - [lv_drivers] (git submodule / linked statically)
- [squeek2lvgl] (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 [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(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 [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 [libinput]: https://gitlab.freedesktop.org/libinput/libinput
[libxkbcommon]: https://github.com/xkbcommon/libxkbcommon [libxkbcommon]: https://github.com/xkbcommon/libxkbcommon
[lv_drivers]: https://github.com/lvgl/lv_drivers [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 [osk-sdl]: https://gitlab.com/postmarketOS/osk-sdl
[squeek2lvgl]: https://gitlab.com/cherrypicker/squeek2lvgl [squeek2lvgl]: https://gitlab.com/cherrypicker/squeek2lvgl
[squeekboard layouts]: https://gitlab.gnome.org/World/Phosh/squeekboard/-/tree/master/data/keyboards [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 [v1 milestone]: https://gitlab.com/cherrypicker/unl0kr/-/milestones/1
[wiki]: https://gitlab.com/cherrypicker/unl0kr/-/wikis/home [wiki]: https://gitlab.com/cherrypicker/unl0kr/-/wikis/home

View File

@@ -20,6 +20,7 @@
#include "command_line.h" #include "command_line.h"
#include "log.h"
#include "unl0kr.h" #include "unl0kr.h"
#include <getopt.h> #include <getopt.h>
@@ -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 * @param opts pointer to the options struct
*/ */
@@ -49,6 +50,15 @@ static void print_usage();
*/ */
static void init_opts(ul_cli_opts *opts) { 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->hor_res = -1;
opts->ver_res = -1; opts->ver_res = -1;
opts->verbose = false; opts->verbose = false;
@@ -56,13 +66,23 @@ static void init_opts(ul_cli_opts *opts) {
static void print_usage() { static void print_usage() {
fprintf(stderr, fprintf(stderr,
/*-------------------------------- 78 CHARS --------------------------------*/
"Usage: unl0kr [OPTION]\n" "Usage: unl0kr [OPTION]\n"
"\n" "\n"
"Mandatory arguments to long options are mandatory for short options too.\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" " -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" " -h, --help Print this message and exit\n"
" -v, --verbose Enable more detailed logging output on STDERR\n" " -v, --verbose Enable more detailed logging output on STDERR\n"
" -V, --version Print the unl0kr version and exit\n"); " -V, --version Print the unl0kr version and exit\n");
/*-------------------------------- 78 CHARS --------------------------------*/
} }
@@ -74,6 +94,8 @@ void ul_cli_parse_opts(int argc, char *argv[], ul_cli_opts *opts) {
init_opts(opts); init_opts(opts);
struct option long_opts[] = { struct option long_opts[] = {
{ "config", required_argument, NULL, 'c' },
{ "config-override", required_argument, NULL, 'C' },
{ "geometry", required_argument, NULL, 'g' }, { "geometry", required_argument, NULL, 'g' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "verbose", no_argument, NULL, 'v' }, { "verbose", no_argument, NULL, 'v' },
@@ -83,11 +105,23 @@ void ul_cli_parse_opts(int argc, char *argv[], ul_cli_opts *opts) {
int opt, index = 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) { 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': case 'g':
if (sscanf(optarg, "%ix%i", &(opts->hor_res), &(opts->ver_res)) != 2) { 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); exit(EXIT_FAILURE);
} }
break; break;

View File

@@ -27,6 +27,10 @@
* Options parsed from command line arguments * Options parsed from command line arguments
*/ */
typedef struct { typedef struct {
/* Number of config files */
int num_config_files;
/* Paths of config file */
const char **config_files;
/* Horizontal display resolution */ /* Horizontal display resolution */
int hor_res; int hor_res;
/* Vertical display resolution */ /* Vertical display resolution */

144
config.c Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "log.h"
#include <ini.h>
#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);
}
}

75
config.h Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef UL_CONFIG_H
#define UL_CONFIG_H
#include "sq2lv_layouts.h"
#include <stdbool.h>
/**
* 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 */

66
main.c
View File

@@ -19,6 +19,7 @@
#include "command_line.h" #include "command_line.h"
#include "config.h"
#include "indev.h" #include "indev.h"
#include "log.h" #include "log.h"
#include "unl0kr.h" #include "unl0kr.h"
@@ -50,8 +51,11 @@ LV_FONT_DECLARE(montserrat_extended_32);
* Static variables * Static variables
*/ */
ul_cli_opts cli_opts;
ul_config_opts conf_opts;
bool is_dark_theme = false; bool is_dark_theme = false;
bool is_password_hidden = true; bool is_password_obscured = true;
bool is_keyboard_hidden = false; bool is_keyboard_hidden = false;
lv_obj_t *keyboard = NULL; 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. * Toggle between showing and hiding the password.
*/ */
static void toggle_password_hidden(void); static void toggle_password_obscured(void);
/** /**
* Show / hide the password. * Show / hide the password.
* *
* @param is_hidden true if the password should be hidden, false if it should be shown * @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. * 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) { static void toggle_pw_btn_clicked_cb(lv_event_t *event) {
toggle_password_hidden(); toggle_password_obscured();
} }
static void toggle_password_hidden(void) { static void toggle_password_obscured(void) {
is_password_hidden = !is_password_hidden; is_password_obscured = !is_password_obscured;
set_password_hidden(is_password_hidden); 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_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) { 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) { 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_t keyboard_anim;
lv_anim_init(&keyboard_anim); lv_anim_init(&keyboard_anim);
lv_anim_set_var(&keyboard_anim, keyboard); 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[]) { int main(int argc, char *argv[]) {
/* Parse command line options */ /* Parse command line options */
ul_cli_opts opts; ul_cli_parse_opts(argc, argv, &cli_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);
/* Set up log level */ /* Set up log level */
if (opts.verbose) { if (cli_opts.verbose) {
ul_log_set_level(UL_LOG_LEVEL_VERBOSE); ul_log_set_level(UL_LOG_LEVEL_VERBOSE);
} }
/* Announce ourselves */ /* Announce ourselves */
ul_log(UL_LOG_LEVEL_VERBOSE, "unl0kr %s", UL_VERSION); 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 */ /* Initialise LVGL and set up logging callback */
lv_init(); lv_init();
lv_log_register_print_cb(ul_log_print_cb); lv_log_register_print_cb(ul_log_print_cb);
@@ -360,11 +363,11 @@ exit(1);
fbdev_get_sizes(&hor_res, &ver_res); fbdev_get_sizes(&hor_res, &ver_res);
/* Override display size with command line options if necessary */ /* Override display size with command line options if necessary */
if (opts.hor_res > 0) { if (cli_opts.hor_res > 0) {
hor_res = LV_MIN(hor_res, opts.hor_res); hor_res = LV_MIN(hor_res, cli_opts.hor_res);
} }
if (opts.ver_res > 0) { if (cli_opts.ver_res > 0) {
ver_res = LV_MIN(ver_res, opts.ver_res); ver_res = LV_MIN(ver_res, cli_opts.ver_res);
} }
/* Prepare display buffer */ /* Prepare display buffer */
@@ -461,7 +464,7 @@ exit(1);
/* Route physical keyboard input into textarea */ /* Route physical keyboard input into textarea */
ul_indev_set_up_textarea_for_keyboard_input(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_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_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); lv_obj_set_size(toggle_pw_btn, 64, 64);
@@ -488,7 +491,6 @@ exit(1);
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_keyboard_set_mode(keyboard, LV_KEYBOARD_MODE_TEXT_LOWER);
lv_keyboard_set_textarea(keyboard, textarea); 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_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_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); 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_set_size(keyboard, hor_res, keyboard_height);
lv_obj_add_style(keyboard, &style_text_normal, 0); lv_obj_add_style(keyboard, &style_text_normal, 0);
/* Apply defaults */ /* Apply textarea options */
sq2lv_switch_layout(keyboard, 0); set_password_obscured(conf_opts.textarea.obscured);
set_password_hidden(is_password_hidden);
/* 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 */ /* Run lvgl in "tickless" mode */
while(1) { while(1) {

View File

@@ -28,6 +28,7 @@ add_project_arguments('-DUL_VERSION="@0@"'.format(meson.project_version()), lang
unl0kr_sources = [ unl0kr_sources = [
'command_line.c', 'command_line.c',
'config.c',
'cursor.c', 'cursor.c',
'indev.c', 'indev.c',
'log.c', 'log.c',
@@ -49,7 +50,8 @@ executable(
sources: unl0kr_sources + squeek2lvgl_sources + lvgl_sources + lv_drivers_sources, sources: unl0kr_sources + squeek2lvgl_sources + lvgl_sources + lv_drivers_sources,
include_directories: ['lvgl', 'lv_drivers'], include_directories: ['lvgl', 'lv_drivers'],
dependencies: [ dependencies: [
dependency('xkbcommon'), dependency('inih'),
dependency('libinput'), dependency('libinput'),
dependency('xkbcommon')
] ]
) )

9
unl0kr.conf Normal file
View File

@@ -0,0 +1,9 @@
[general]
animations=true
[textarea]
obscured=true
[keyboard]
popovers=true
layout=de