diff --git a/buffyboard/buffyboard.conf b/buffyboard/buffyboard.conf index 9f77241..10d07cd 100644 --- a/buffyboard/buffyboard.conf +++ b/buffyboard/buffyboard.conf @@ -1,3 +1,6 @@ +[keyboard] +haptic_feedback=true + [theme] default=breezy-light diff --git a/buffyboard/config.c b/buffyboard/config.c index 939e9d1..90223f8 100644 --- a/buffyboard/config.c +++ b/buffyboard/config.c @@ -40,7 +40,13 @@ static int parsing_handler(void* user_data, const char* section, const char* key static int parsing_handler(void* user_data, const char* section, const char* key, const char* value) { bb_config_opts *opts = (bb_config_opts *)user_data; - if (strcmp(section, "theme") == 0) { + if (strcmp(section, "keyboard") == 0) { + if (strcmp(key, "haptic_feedback") == 0) { + if (bbx_config_parse_bool(value, &(opts->keyboard.haptic_feedback))) { + return 1; + } + } + } else if (strcmp(section, "theme") == 0) { if (strcmp(key, "default") == 0) { bbx_themes_theme_id_t id = bbx_themes_find_theme_with_name(value); if (id != BBX_THEMES_THEME_NONE) { @@ -80,6 +86,7 @@ static int parsing_handler(void* user_data, const char* section, const char* key */ void bb_config_init_opts(bb_config_opts *opts) { + opts->keyboard.haptic_feedback = true; opts->theme.default_id = BBX_THEMES_THEME_BREEZY_DARK; opts->input.pointer = true; opts->input.touchscreen = true; diff --git a/buffyboard/config.h b/buffyboard/config.h index 2a64006..57bd3bd 100644 --- a/buffyboard/config.h +++ b/buffyboard/config.h @@ -11,6 +11,14 @@ #include "sq2lv_layouts.h" +/** + * Options related to the keyboard + */ +typedef struct { + /* If true, vibrate on key presses */ + bool haptic_feedback; +} bb_config_opts_keyboard; + /** * Options related to the theme */ @@ -43,6 +51,8 @@ typedef struct { * Options parsed from config file(s) */ typedef struct { + /* Options related to the keyboard */ + bb_config_opts_keyboard keyboard; /* Options related to the theme */ bb_config_opts_theme theme; /* Options related to input devices */ diff --git a/buffyboard/main.c b/buffyboard/main.c index 9545f2f..093c32f 100644 --- a/buffyboard/main.c +++ b/buffyboard/main.c @@ -12,6 +12,7 @@ #include "lvgl/lvgl.h" +#include "../shared/force_feedback.h" #include "../shared/indev.h" #include "../shared/log.h" #include "../shared/theme.h" @@ -93,6 +94,8 @@ static void keyboard_value_changed_cb(lv_event_t *event) { return; } + bbx_force_feedback_play(); + if (sq2lv_is_layer_switcher(kb, btn_id)) { pop_checked_modifier_keys(); sq2lv_switch_layer(kb, btn_id); @@ -296,7 +299,8 @@ int main(int argc, char *argv[]) { /* Attach input devices and start monitoring for new ones */ struct bbx_indev_opts input_config = { .pointer = conf_opts.input.pointer, - .touchscreen = conf_opts.input.touchscreen + .touchscreen = conf_opts.input.touchscreen, + .force_feedback = conf_opts.keyboard.haptic_feedback }; if (bbx_indev_init(fd_epoll, &input_config) == 0) return EXIT_FAILURE; diff --git a/f0rmz/config.c b/f0rmz/config.c index 955dc2c..66628fa 100644 --- a/f0rmz/config.c +++ b/f0rmz/config.c @@ -59,6 +59,10 @@ static int parsing_handler(void* user_data, const char* section, const char* key if (bbx_config_parse_bool(value, &(opts->keyboard.popovers))) { return 1; } + } else if (strcmp(key, "haptic_feedback") == 0) { + if (bbx_config_parse_bool(value, &(opts->keyboard.haptic_feedback))) { + return 1; + } } } else if (strcmp(section, "theme") == 0) { if (strcmp(key, "default") == 0) { @@ -190,6 +194,7 @@ void f0_config_init_opts(f0_config_opts *opts) { opts->keyboard.autohide = true; opts->keyboard.layout_id = SQ2LV_LAYOUT_US; opts->keyboard.popovers = true; + opts->keyboard.haptic_feedback = true; opts->textarea.obscured = true; opts->textarea.bullet = LV_SYMBOL_BULLET; opts->input.keyboard = true; diff --git a/f0rmz/config.h b/f0rmz/config.h index 9f8dae5..317ea5e 100644 --- a/f0rmz/config.h +++ b/f0rmz/config.h @@ -33,6 +33,8 @@ typedef struct { sq2lv_layout_id_t layout_id; /* If true, display key popovers on press */ bool popovers; + /* If true, vibrate on key presses */ + bool haptic_feedback; } f0_config_opts_keyboard; /** diff --git a/f0rmz/main.c b/f0rmz/main.c index 68354e4..8db4dc3 100644 --- a/f0rmz/main.c +++ b/f0rmz/main.c @@ -8,6 +8,7 @@ #include "../shared/backends.h" #include "../shared/display.h" +#include "../shared/force_feedback.h" #include "../shared/header.h" #include "../shared/indev.h" #include "../shared/keyboard.h" @@ -37,19 +38,19 @@ f0_cli_opts cli_opts; f0_config_opts conf_opts; -bool is_alternate_theme = false; -bool is_keyboard_hidden = false; +static bool is_alternate_theme = false; +static bool is_keyboard_hidden = false; static char **field_values = NULL; -int current_field_index = 0; +static int current_field_index = 0; -lv_obj_t *form_container = NULL; -lv_obj_t *form_textarea = NULL; -lv_obj_t *keyboard = NULL; +static lv_obj_t *form_container = NULL; +static lv_obj_t *form_textarea = NULL; +static lv_obj_t *keyboard = NULL; -int32_t content_height_with_kb; -int32_t content_height_without_kb; +static int32_t content_height_with_kb; +static int32_t content_height_without_kb; /** * Static prototypes @@ -265,7 +266,7 @@ static void exit_failure(); * Static functions */ - static void intro_key_cb(lv_event_t *event) { +static void intro_key_cb(lv_event_t *event) { uint32_t key = lv_indev_get_key(lv_indev_active()); if (key == LV_KEY_ENTER) { get_started_btn_clicked_cb(event); @@ -409,6 +410,8 @@ static void keyboard_value_changed_cb(lv_event_t *event) { return; } + bbx_force_feedback_play(); + if (sq2lv_is_layer_switcher(kb, btn_id)) { sq2lv_switch_layer(kb, btn_id); return; @@ -889,7 +892,8 @@ int main(int argc, char *argv[]) { .keymap = &conf_opts.hw_keyboard, .keyboard = conf_opts.input.keyboard, .pointer = conf_opts.input.pointer, - .touchscreen = conf_opts.input.touchscreen + .touchscreen = conf_opts.input.touchscreen, + .force_feedback = conf_opts.keyboard.haptic_feedback }; if (bbx_indev_init(fd_epoll, &input_config) == 0) exit_failure(); diff --git a/man/buffyboard.1.scd b/man/buffyboard.1.scd index 93c931b..58969a3 100644 --- a/man/buffyboard.1.scd +++ b/man/buffyboard.1.scd @@ -54,7 +54,7 @@ result. # NOTES -Some terminal commands, like _clear_ or _setfont_, can erase the keyboard or brake the layout of the terminal. In this case you should send a signal to Buffyboard or switch to another terminal to update the screen: +Some terminal commands, like _clear_ or _setfont_, can erase the keyboard or break the layout of the terminal. In this case you should send a signal to Buffyboard or switch to another terminal to update the screen: ``` setfont solar24x32;/usr/bin/kill -s SIGUSR1 buffyboard diff --git a/man/buffyboard.conf.5.scd b/man/buffyboard.conf.5.scd index f9db834..72c09c6 100644 --- a/man/buffyboard.conf.5.scd +++ b/man/buffyboard.conf.5.scd @@ -25,6 +25,11 @@ for and, if found, merged in the following order: # OPTIONS +## Keyboard +*haptic_feedback* = + Enable or disable vibrations when pressing keys. + Default: true. + ## Theme *default* = Selects the default theme on boot. Can be changed at runtime to the diff --git a/man/unl0kr.conf.5.scd b/man/unl0kr.conf.5.scd index 25492dc..e202bfc 100644 --- a/man/unl0kr.conf.5.scd +++ b/man/unl0kr.conf.5.scd @@ -48,7 +48,11 @@ for and, if found, merged in the following order: *popovers* = Enable or disable key press popovers showing the selected key. - default: true. + Default: true. + +*haptic_feedback* = + Enable or disable vibrations when pressing keys. + Default: true. ## Textarea *obscured* = diff --git a/meson.build b/meson.build index 23a2abb..b803483 100644 --- a/meson.build +++ b/meson.build @@ -27,6 +27,7 @@ shared_sources = files( 'shared/fonts/font_32.c', 'shared/cli_common.c', 'shared/config.c', + 'shared/force_feedback.c', 'shared/indev.c', 'shared/log.c', 'shared/theme.c', diff --git a/shared/force_feedback.c b/shared/force_feedback.c new file mode 100644 index 0000000..612e46d --- /dev/null +++ b/shared/force_feedback.c @@ -0,0 +1,109 @@ +/** + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "force_feedback.h" + +#include "log.h" + +#include +#include +#include +#include +#include +#include + +#define bit_is_set(array, bit) ((array[bit / 8] >> bit % 8) & 0x01) + +/** + * Static variables + */ + +static int fd = -1; +static struct input_event ff_event = { + .type = EV_FF, + .code = 0, + .value = 1 +}; + +/** + * Public functions + */ + +void bbx_force_feedback_connect(struct udev_device* device) { + if (fd >= 0) + return; + + const char* node = udev_device_get_devnode(device); + + int fd_event = open(node, O_RDWR); + if (fd_event < 0) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Can't open %s", node); + return; + } + + int n_effects; + if (ioctl(fd_event, EVIOCGEFFECTS, &n_effects) == -1 || n_effects == 0) { + close(fd_event); + return; + } + + bbx_log(BBX_LOG_LEVEL_VERBOSE, "Found a force feedback device: %s", node); + + uint8_t features[(FF_CNT + 7) / 8]; + if (ioctl(fd_event, EVIOCGBIT(EV_FF, sizeof(features)), features) == -1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Can't request features of %s", node); + close(fd_event); + return; + } + + if (!bit_is_set(features, FF_RUMBLE)) { + bbx_log(BBX_LOG_LEVEL_VERBOSE, "%s does not support FF_RUMBLE", node); + close(fd_event); + return; + } + + struct ff_effect effect = { + .type = FF_RUMBLE, + .id = -1, + .direction = 0x0000, + .trigger.button = 0, + .trigger.interval = 0, + .replay.length = 50, + .replay.delay = 0, + .u.rumble.strong_magnitude = 0xC000, + .u.rumble.weak_magnitude = 0xC000 + }; + + if (ioctl(fd_event, EVIOCSFF, &effect) == -1) { + bbx_log(BBX_LOG_LEVEL_ERROR, "Can't upload the effect"); + close(fd_event); + return; + } + + ff_event.code = effect.id; + + if (bit_is_set(features, FF_GAIN)) { + struct input_event gain = { + .type = EV_FF, + .code = FF_GAIN, + .value = 0xFFFF + }; + + if (write(fd_event, &gain, sizeof(gain)) != sizeof(gain)) + bbx_log(BBX_LOG_LEVEL_WARNING, "Can't set the gain"); + } + + fd = fd_event; +} + +void bbx_force_feedback_play() { + if (fd < 0) + return; + + if (write(fd, &ff_event, sizeof(ff_event)) != sizeof(ff_event)) { + // Assume that the force feedback device was disconnected + close(fd); + fd = -1; + } +} diff --git a/shared/force_feedback.h b/shared/force_feedback.h new file mode 100644 index 0000000..4efd9ac --- /dev/null +++ b/shared/force_feedback.h @@ -0,0 +1,20 @@ +/** + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef BBX_FORCE_FEEDBACK_H +#define BBX_FORCE_FEEDBACK_H + +#include + +/** + * Try to connect a force feedback device. + */ +void bbx_force_feedback_connect(struct udev_device* device); + +/** + * Play a force feedback effect. + */ +void bbx_force_feedback_play(); + +#endif /* BBX_FORCE_FEEDBACK_H */ diff --git a/shared/indev.c b/shared/indev.c index 28db3a6..6bbee4a 100644 --- a/shared/indev.c +++ b/shared/indev.c @@ -6,6 +6,7 @@ #include "indev.h" #include "cursor/cursor.h" +#include "force_feedback.h" #include "log.h" #include "lvgl/lvgl.h" @@ -93,6 +94,7 @@ static struct { #endif uint8_t pointer : 1; uint8_t touchscreen : 1; + uint8_t force_feedback : 1; } options; /** @@ -641,6 +643,12 @@ static void attach_input_device(struct udev_device* device) { break; } + /* A device can be a force feedback device and an input device independently, + so we check for both capabilities. */ + + if (options.force_feedback) + bbx_force_feedback_connect(device); + struct libinput_device* dev = libinput_path_add_device(context_libinput, node); if (!dev) { bbx_log(BBX_LOG_LEVEL_WARNING, "libinput can't use %s", node); @@ -795,6 +803,7 @@ uint8_t bbx_indev_init(int fd_epoll, const struct bbx_indev_opts* opts) { #endif options.pointer = opts->pointer; options.touchscreen = opts->touchscreen; + options.force_feedback = opts->force_feedback; attach_input_devices(); diff --git a/shared/indev.h b/shared/indev.h index ece0c66..493ce99 100644 --- a/shared/indev.h +++ b/shared/indev.h @@ -20,6 +20,7 @@ struct bbx_indev_opts { #endif uint8_t pointer : 1; uint8_t touchscreen : 1; + uint8_t force_feedback : 1; }; #ifndef BBX_APP_BUFFYBOARD diff --git a/unl0kr/config.c b/unl0kr/config.c index 73c5098..292a5bf 100644 --- a/unl0kr/config.c +++ b/unl0kr/config.c @@ -71,6 +71,10 @@ static int parsing_handler(void* user_data, const char* section, const char* key if (bbx_config_parse_bool(value, &(opts->keyboard.popovers))) { return 1; } + } else if (strcmp(key, "haptic_feedback") == 0) { + if (bbx_config_parse_bool(value, &(opts->keyboard.haptic_feedback))) { + return 1; + } } } else if (strcmp(section, "textarea") == 0) { if (strcmp(key, "obscured") == 0) { @@ -161,6 +165,7 @@ void ul_config_init_opts(ul_config_opts *opts) { opts->keyboard.autohide = true; opts->keyboard.layout_id = SQ2LV_LAYOUT_US; opts->keyboard.popovers = true; + opts->keyboard.haptic_feedback = true; opts->textarea.obscured = true; opts->textarea.bullet = LV_SYMBOL_BULLET; opts->theme.default_id = BBX_THEMES_THEME_BREEZY_DARK; diff --git a/unl0kr/config.h b/unl0kr/config.h index d32fc70..72c33e5 100644 --- a/unl0kr/config.h +++ b/unl0kr/config.h @@ -39,6 +39,8 @@ typedef struct { sq2lv_layout_id_t layout_id; /* If true, display key popovers on press */ bool popovers; + /* If true, vibrate on key presses */ + bool haptic_feedback; } ul_config_opts_keyboard; /** diff --git a/unl0kr/main.c b/unl0kr/main.c index 70ebcd1..de7d3b6 100644 --- a/unl0kr/main.c +++ b/unl0kr/main.c @@ -10,6 +10,7 @@ #include "../shared/backends.h" #include "../shared/display.h" +#include "../shared/force_feedback.h" #include "../shared/header.h" #include "../shared/indev.h" #include "../shared/keyboard.h" @@ -334,6 +335,8 @@ static void keyboard_value_changed_cb(lv_event_t *event) { return; } + bbx_force_feedback_play(); + if (sq2lv_is_layer_switcher(kb, btn_id)) { sq2lv_switch_layer(kb, btn_id); return; @@ -447,7 +450,8 @@ int main(int argc, char *argv[]) { .keymap = &conf_opts.hw_keyboard, .keyboard = conf_opts.input.keyboard, .pointer = conf_opts.input.pointer, - .touchscreen = conf_opts.input.touchscreen + .touchscreen = conf_opts.input.touchscreen, + .force_feedback = conf_opts.keyboard.haptic_feedback }; if (bbx_indev_init(fd_epoll, &input_config) == 0) exit_failure(); diff --git a/unl0kr/unl0kr.conf b/unl0kr/unl0kr.conf index 70938ca..db85436 100644 --- a/unl0kr/unl0kr.conf +++ b/unl0kr/unl0kr.conf @@ -7,6 +7,7 @@ animations=true autohide=false layout=us popovers=true +haptic_feedback=true [textarea] obscured=true