From 9ec3d678c4a2098240d7cf4b0943f7ba100f69df Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sat, 12 Sep 2020 18:34:38 +0200 Subject: [PATCH 01/15] change atomic commit behaviour - do blocking atomic commits instead of nonblocking ones with pageflip events --- src/compositor.c | 41 ++++++++++++++++++++++++++++++++++++++++- src/flutter-pi.c | 2 +- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/compositor.c b/src/compositor.c index 27f5327a..ca6faada 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -747,6 +747,31 @@ static bool on_create_backing_store( return true; } +struct simulated_page_flip_event_data { + unsigned int sec; + unsigned int usec; +}; + +extern void on_pageflip_event( + int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + void *userdata +); + +static int execute_simulate_page_flip_event(void *userdata) { + struct simulated_page_flip_event_data *data; + + data = userdata; + + on_pageflip_event(flutterpi.drm.drmdev->fd, 0, data->sec, data->usec, NULL); + + free(data); + + return 0; +} + /// PRESENT FUNCS static bool on_present_layers( const FlutterLayer **layers, @@ -769,7 +794,7 @@ static bool on_present_layers( eglMakeCurrent(flutterpi.egl.display, flutterpi.egl.surface, flutterpi.egl.surface, flutterpi.egl.root_context); eglSwapBuffers(flutterpi.egl.display, flutterpi.egl.surface); - req_flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + req_flags = 0 /* DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK*/; if (compositor->has_applied_modeset == false) { ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); if (ok != 0) return false; @@ -1002,9 +1027,23 @@ static bool on_present_layers( eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); drmdev_atomic_req_commit(req, req_flags, NULL); + uint64_t time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); + drmdev_destroy_atomic_req(req); cpset_unlock(&compositor->cbs); + + struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); + if (data == NULL) { + return false; + } + + data->sec = time / 1000000000llu; + data->usec = (time % 1000000000llu) / 1000; + + flutterpi_post_platform_task(execute_simulate_page_flip_event, data); + + return true; } int compositor_on_page_flip( diff --git a/src/flutter-pi.c b/src/flutter-pi.c index dc8e2202..2ac7b9fb 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1022,7 +1022,7 @@ static int init_main_loop(void) { * DISPLAY INITIALIZATION * **************************/ /// Called on the main thread when a pageflip ocurred. -static void on_pageflip_event( +void on_pageflip_event( int fd, unsigned int frame, unsigned int sec, From 7aff7f61aa1738d13405284851e178ac779fbc07 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Fri, 18 Sep 2020 03:18:31 +0200 Subject: [PATCH 02/15] commit blockingly if nonblocking is not working --- include/compositor.h | 11 +++++++++++ src/compositor.c | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/include/compositor.h b/include/compositor.h index c8cb2299..8555e053 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -115,6 +115,17 @@ struct compositor { uint32_t gem_bo_handle; uint32_t *buffer; } cursor; + + /** + * If true, @ref on_present_layers will commit blockingly. + * + * It will also schedule a simulated page flip event on the main thread + * afterwards so the frame queue works. + * + * If false, @ref on_present_layers will commit nonblocking using page flip events, + * like usual. + */ + bool do_blocking_atomic_commits; }; /* diff --git a/src/compositor.c b/src/compositor.c index cf18a622..d8c17517 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -50,6 +50,7 @@ struct compositor compositor = { .has_applied_modeset = false, .should_create_window_surface_backing_store = true, .stale_rendertargets = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), + .do_blocking_atomic_commits = false }; static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { @@ -1061,23 +1062,40 @@ static bool on_present_layers( } eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + do_commit: + if (compositor->do_blocking_atomic_commits) { + req_flags &= ~(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); + } else { + req_flags |= DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; + } + + ok = drmdev_atomic_req_commit(req, req_flags, NULL); + if ((compositor->do_blocking_atomic_commits == false) && (ok < 0) && (errno == EBUSY)) { + printf("[compositor] Non-blocking drmModeAtomicCommit failed with EBUSY.\n" + " Future drmModeAtomicCommits will be executed blockingly.\n" + " This may have have an impact on performance.\n"); + + compositor->do_blocking_atomic_commits = true; + goto do_commit; + } - drmdev_atomic_req_commit(req, req_flags, NULL); - uint64_t time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); + if (compositor->do_blocking_atomic_commits) { + uint64_t time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); - drmdev_destroy_atomic_req(req); + struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); + if (data == NULL) { + return false; + } - cpset_unlock(&compositor->cbs); + data->sec = time / 1000000000llu; + data->usec = (time % 1000000000llu) / 1000; - struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); - if (data == NULL) { - return false; + flutterpi_post_platform_task(execute_simulate_page_flip_event, data); } - data->sec = time / 1000000000llu; - data->usec = (time % 1000000000llu) / 1000; - - flutterpi_post_platform_task(execute_simulate_page_flip_event, data); + drmdev_destroy_atomic_req(req); + cpset_unlock(&compositor->cbs); return true; } From 582f486b43aa756ba9b2fc43f71035e5bf1e48d4 Mon Sep 17 00:00:00 2001 From: Mutsutoshi Yoshimoto Date: Fri, 18 Sep 2020 18:12:40 +0900 Subject: [PATCH 03/15] Set to video_uri if an asset is specified. --- src/plugins/omxplayer_video_player.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c index e78168b1..926c139a 100644 --- a/src/plugins/omxplayer_video_player.c +++ b/src/plugins/omxplayer_video_player.c @@ -1087,7 +1087,11 @@ static int on_create( player->player_id = omxpvidpp.next_unused_player_id++; player->mgr = mgr; - strncpy(player->video_uri, uri, sizeof(player->video_uri)); + if (asset != NULL) { + snprintf(player->video_uri, ,sizeof(player->video_uri), "%s/%s", flutterpi.flutter.asset_bundle_path, asset); + } else { + strncpy(player->video_uri, uri, sizeof(player->video_uri)); + } mgr->player = player; From c61833dcfd18e4297b34c7de3bfb7a7cb6b589bc Mon Sep 17 00:00:00 2001 From: Mutsutoshi Yoshimoto Date: Fri, 18 Sep 2020 18:14:48 +0900 Subject: [PATCH 04/15] Set has_rotation to true. --- src/flutter-pi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index dc72e2d2..9a66d324 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -2547,6 +2547,7 @@ static bool parse_cmd_args(int argc, char **argv) { } flutterpi.view.rotation = rotation; + flutterpi.view.has_rotation = true; break; case 'd': ; From 9f74f287f12859a1b934357ca6e62a3e342ac6fa Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Fri, 9 Oct 2020 00:56:32 +0200 Subject: [PATCH 05/15] search for a DRM encoder if none is configured --- src/flutter-pi.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 9a66d324..dfb3e969 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1327,12 +1327,27 @@ static int init_display(void) { fprintf(stderr, "[flutter-pi] WARNING: display has non-square pixels. Non-square-pixels are not supported by flutter.\n"); } } - + for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { if (encoder->encoder->encoder_id == connector->connector->encoder_id) { break; } } + + if (encoder == NULL) { + for (int i = 0; i < connector->connector->count_encoders; i++, encoder = NULL) { + for_each_encoder_in_drmdev(flutterpi.drm.drmdev, encoder) { + if (encoder->encoder->encoder_id == connector->connector->encoders[i]) { + break; + } + } + + if (encoder->encoder->possible_crtcs) { + // only use this encoder if there's a crtc we can use with it + break; + } + } + } if (encoder == NULL) { fprintf(stderr, "[flutter-pi] Could not find a suitable DRM encoder.\n"); @@ -1345,6 +1360,15 @@ static int init_display(void) { } } + if (crtc == NULL) { + for_each_crtc_in_drmdev(flutterpi.drm.drmdev, crtc) { + if (encoder->encoder->possible_crtcs & crtc->bitmask) { + // find a CRTC that is possible to use with this encoder + break; + } + } + } + if (crtc == NULL) { fprintf(stderr, "[flutter-pi] Could not find a suitable DRM CRTC.\n"); return EINVAL; From 449c73fe07261c69dca985e41413cd527cfbe80d Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Fri, 9 Oct 2020 00:58:12 +0200 Subject: [PATCH 06/15] fix typo --- src/plugins/omxplayer_video_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/omxplayer_video_player.c b/src/plugins/omxplayer_video_player.c index 926c139a..5c779147 100644 --- a/src/plugins/omxplayer_video_player.c +++ b/src/plugins/omxplayer_video_player.c @@ -1088,7 +1088,7 @@ static int on_create( player->player_id = omxpvidpp.next_unused_player_id++; player->mgr = mgr; if (asset != NULL) { - snprintf(player->video_uri, ,sizeof(player->video_uri), "%s/%s", flutterpi.flutter.asset_bundle_path, asset); + snprintf(player->video_uri, sizeof(player->video_uri), "%s/%s", flutterpi.flutter.asset_bundle_path, asset); } else { strncpy(player->video_uri, uri, sizeof(player->video_uri)); } From f2e3c3194883e08cf39daa691ded584380605cbb Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 11 Oct 2020 19:46:53 +0200 Subject: [PATCH 07/15] better keyboard support --- Makefile | 6 +- include/console_keyboard.h | 191 ------ include/flutter-pi.h | 6 +- include/keyboard.h | 120 ++++ include/plugins/raw_keyboard.h | 27 +- include/plugins/text_input.h | 26 +- src/console_keyboard.c | 279 -------- src/flutter-pi.c | 169 ++--- src/keyboard.c | 369 +++++++++++ src/plugins/raw_keyboard.c | 204 +++--- src/plugins/text_input.c | 1125 +++++++++++++++++++++----------- 11 files changed, 1496 insertions(+), 1026 deletions(-) delete mode 100644 include/console_keyboard.h create mode 100644 include/keyboard.h delete mode 100644 src/console_keyboard.c create mode 100644 src/keyboard.c diff --git a/Makefile b/Makefile index 9a569e0e..948e172c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd libinput libudev) \ +REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libsystemd libinput libudev xkbcommon) \ -DBUILD_TEXT_INPUT_PLUGIN \ -DBUILD_TEST_PLUGIN \ -DBUILD_OMXPLAYER_VIDEO_PLAYER_PLUGIN \ @@ -6,7 +6,7 @@ REAL_CFLAGS = -I./include $(shell pkg-config --cflags gbm libdrm glesv2 egl libs $(CFLAGS) REAL_LDFLAGS = \ - $(shell pkg-config --libs gbm libdrm glesv2 egl libsystemd libinput libudev) \ + $(shell pkg-config --libs gbm libdrm glesv2 egl libsystemd libinput libudev xkbcommon) \ -lrt \ -lpthread \ -ldl \ @@ -17,12 +17,12 @@ REAL_LDFLAGS = \ SOURCES = src/flutter-pi.c \ src/platformchannel.c \ src/pluginregistry.c \ - src/console_keyboard.c \ src/texture_registry.c \ src/compositor.c \ src/modesetting.c \ src/collection.c \ src/cursor.c \ + src/keyboard.c \ src/plugins/services.c \ src/plugins/testplugin.c \ src/plugins/text_input.c \ diff --git a/include/console_keyboard.h b/include/console_keyboard.h deleted file mode 100644 index b3b70f41..00000000 --- a/include/console_keyboard.h +++ /dev/null @@ -1,191 +0,0 @@ -#ifndef _CONSOLE_KEYBOARD_H -#define _CONSOLE_KEYBOARD_H - -#include -#include - -// small subset of the GLFW key ids. -// (only the ones needed for text input) - -typedef enum { - GLFW_KEY_UNKNOWN = -1, - GLFW_KEY_SPACE = 32, - GLFW_KEY_APOSTROPHE = 39, - GLFW_KEY_COMMA = 44, - GLFW_KEY_MINUS = 45, - GLFW_KEY_PERIOD = 46, - GLFW_KEY_SLASH = 47, - GLFW_KEY_0 = 48, - GLFW_KEY_1 = 49, - GLFW_KEY_2 = 50, - GLFW_KEY_3 = 51, - GLFW_KEY_4 = 52, - GLFW_KEY_5 = 53, - GLFW_KEY_6 = 54, - GLFW_KEY_7 = 55, - GLFW_KEY_8 = 56, - GLFW_KEY_9 = 57, - GLFW_KEY_SEMICOLON = 59, - GLFW_KEY_EQUAL = 61, - GLFW_KEY_A = 65, - GLFW_KEY_B = 66, - GLFW_KEY_C = 67, - GLFW_KEY_D = 68, - GLFW_KEY_E = 69, - GLFW_KEY_F = 70, - GLFW_KEY_G = 71, - GLFW_KEY_H = 72, - GLFW_KEY_I = 73, - GLFW_KEY_J = 74, - GLFW_KEY_K = 75, - GLFW_KEY_L = 76, - GLFW_KEY_M = 77, - GLFW_KEY_N = 78, - GLFW_KEY_O = 79, - GLFW_KEY_P = 80, - GLFW_KEY_Q = 81, - GLFW_KEY_R = 82, - GLFW_KEY_S = 83, - GLFW_KEY_T = 84, - GLFW_KEY_U = 85, - GLFW_KEY_V = 86, - GLFW_KEY_W = 87, - GLFW_KEY_X = 88, - GLFW_KEY_Y = 89, - GLFW_KEY_Z = 90, - GLFW_KEY_LEFT_BRACKET = 91, - GLFW_KEY_BACKSLASH = 92, - GLFW_KEY_RIGHT_BRACKET = 93, - GLFW_KEY_GRAVE_ACCENT = 96, - GLFW_KEY_WORLD_1 = 161, - GLFW_KEY_WORLD_2 = 162, - GLFW_KEY_ESCAPE = 256, - GLFW_KEY_ENTER = 257, - GLFW_KEY_TAB = 258, - GLFW_KEY_BACKSPACE = 259, - GLFW_KEY_INSERT = 260, - GLFW_KEY_DELETE = 261, - GLFW_KEY_RIGHT = 262, - GLFW_KEY_LEFT = 263, - GLFW_KEY_DOWN = 264, - GLFW_KEY_UP = 265, - GLFW_KEY_PAGE_UP = 266, - GLFW_KEY_PAGE_DOWN = 267, - GLFW_KEY_HOME = 268, - GLFW_KEY_END = 269, - GLFW_KEY_CAPS_LOCK = 280, - GLFW_KEY_SCROLL_LOCK = 281, - GLFW_KEY_NUM_LOCK = 282, - GLFW_KEY_PRINT_SCREEN = 283, - GLFW_KEY_PAUSE = 284, - GLFW_KEY_F1 = 290, - GLFW_KEY_F2 = 291, - GLFW_KEY_F3 = 292, - GLFW_KEY_F4 = 293, - GLFW_KEY_F5 = 294, - GLFW_KEY_F6 = 295, - GLFW_KEY_F7 = 296, - GLFW_KEY_F8 = 297, - GLFW_KEY_F9 = 298, - GLFW_KEY_F10 = 299, - GLFW_KEY_F11 = 300, - GLFW_KEY_F12 = 301, - GLFW_KEY_F13 = 302, - GLFW_KEY_F14 = 303, - GLFW_KEY_F15 = 304, - GLFW_KEY_F16 = 305, - GLFW_KEY_F17 = 306, - GLFW_KEY_F18 = 307, - GLFW_KEY_F19 = 308, - GLFW_KEY_F20 = 309, - GLFW_KEY_F21 = 310, - GLFW_KEY_F22 = 311, - GLFW_KEY_F23 = 312, - GLFW_KEY_F24 = 313, - GLFW_KEY_F25 = 314, - GLFW_KEY_KP_0 = 320, - GLFW_KEY_KP_1 = 321, - GLFW_KEY_KP_2 = 322, - GLFW_KEY_KP_3 = 323, - GLFW_KEY_KP_4 = 324, - GLFW_KEY_KP_5 = 325, - GLFW_KEY_KP_6 = 326, - GLFW_KEY_KP_7 = 327, - GLFW_KEY_KP_8 = 328, - GLFW_KEY_KP_9 = 329, - GLFW_KEY_KP_DECIMAL = 330, - GLFW_KEY_KP_DIVIDE = 331, - GLFW_KEY_KP_MULTIPLY = 332, - GLFW_KEY_KP_SUBTRACT = 333, - GLFW_KEY_KP_ADD = 334, - GLFW_KEY_KP_ENTER = 335, - GLFW_KEY_KP_EQUAL = 336, - GLFW_KEY_LEFT_SHIFT = 340, - GLFW_KEY_LEFT_CONTROL = 341, - GLFW_KEY_LEFT_ALT = 342, - GLFW_KEY_LEFT_SUPER = 343, - GLFW_KEY_RIGHT_SHIFT = 344, - GLFW_KEY_RIGHT_CONTROL = 345, - GLFW_KEY_RIGHT_ALT = 346, - GLFW_KEY_RIGHT_SUPER = 347, - GLFW_KEY_MENU = 348, -} glfw_key; - -#define GLFW_KEY_LAST 348 - -typedef enum { - GLFW_RELEASE = 0, - GLFW_PRESS = 1, - GLFW_REPEAT = 2 -} glfw_key_action; - -typedef enum { - GLFW_MOD_SHIFT = 1, - GLFW_MOD_CONTROL = 2, - GLFW_MOD_ALT = 4, - GLFW_MOD_SUPER = 8, - GLFW_MOD_CAPS_LOCK = 16, - GLFW_MOD_NUM_LOCK = 32 -} glfw_keymod; - -#define GLFW_KEYMOD_FOR_KEY(keycode) \ - (((keycode == GLFW_KEY_LEFT_SHIFT) || (keycode == GLFW_KEY_RIGHT_SHIFT)) ? GLFW_MOD_SHIFT : \ - ((keycode == GLFW_KEY_LEFT_CONTROL) || (keycode == GLFW_KEY_RIGHT_CONTROL)) ? GLFW_MOD_CONTROL : \ - ((keycode == GLFW_KEY_LEFT_ALT) || (keycode == GLFW_KEY_RIGHT_ALT)) ? GLFW_MOD_ALT : \ - ((keycode == GLFW_KEY_LEFT_SUPER) || (keycode == GLFW_KEY_RIGHT_SUPER)) ? GLFW_MOD_SUPER : \ - (keycode == GLFW_KEY_CAPS_LOCK) ? GLFW_MOD_CAPS_LOCK : \ - (keycode == GLFW_KEY_NUM_LOCK) ? GLFW_MOD_NUM_LOCK : 0); - -#define GLFW_KEY_IS_RIGHTSIDED(keycode) \ - ((keycode == GLFW_KEY_RIGHT_SHIFT) ? true : \ - (keycode == GLFW_KEY_RIGHT_CONTROL) ? true : \ - (keycode == GLFW_KEY_RIGHT_ALT) ? true : \ - (keycode == GLFW_KEY_RIGHT_SUPER) ? true : false) - -typedef uint8_t glfw_keymod_map; - -extern char *glfw_key_control_sequence[GLFW_KEY_LAST+1]; -extern glfw_key evdev_code_glfw_key[KEY_CNT]; - -#define EVDEV_KEY_TO_GLFW_KEY(key) evdev_code_glfw_key[key] - - -int console_flush_stdin(void); -int console_make_raw(void); -int console_restore(void); - -/// tries to parse the console input represented by the string `input` -/// as a keycode () -size_t utf8_symbol_length(char *c); - -static inline char *utf8_symbol_at(char *utf8str, unsigned int symbol_index) { - for (; symbol_index && *utf8str; symbol_index--) - utf8str += utf8_symbol_length(utf8str); - - return symbol_index? NULL : utf8str; -} - -glfw_key console_try_get_key(char *input, char **input_out); -char *console_try_get_utf8char(char *input, char **input_out); - -#endif \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 60f8b212..7820f23c 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -26,6 +26,7 @@ #include #include +#include #define LOAD_EGL_PROC(flutterpi_struct, name) \ do { \ @@ -394,13 +395,15 @@ struct flutterpi { struct { bool use_paths; bool disable_text_input; + glob_t input_devices_glob; # ifndef BUILD_WITHOUT_UDEV_SUPPORT struct libudev libudev; # endif struct libinput *libinput; sd_event_source *libinput_event_source; - sd_event_source *stdin_event_source; + struct keyboard_config *keyboard_config; + int64_t next_unused_flutter_device_id; double cursor_x, cursor_y; } input; @@ -452,6 +455,7 @@ extern struct flutterpi flutterpi; struct input_device_data { int64_t flutter_device_id_offset; + struct keyboard_state *keyboard_state; double x, y; int64_t buttons; uint64_t timestamp; diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 00000000..16eb3a8a --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,120 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#include + +struct keyboard_config { + struct xkb_context *context; + struct xkb_keymap *default_keymap; + struct xkb_compose_table *default_compose_table; +}; + +struct keyboard_state { + struct keyboard_config *config; + struct xkb_state *state; + struct xkb_state *plain_state; + struct xkb_compose_state *compose_state; + int n_iso_level2; + int n_iso_level3; + int n_iso_level5; +}; + +struct keyboard_modifier_state { + bool ctrl:1; + bool shift:1; + bool alt:1; + bool meta:1; + bool capslock:1; + bool numlock:1; + bool scrolllock:1; +}; + +#define KEY_RELEASE 0 +#define KEY_PRESS 1 +#define KEY_REPEAT 2 + +struct keyboard_config *keyboard_config_new(void); + +void keyboard_config_destroy(struct keyboard_config *config); + +struct keyboard_state *keyboard_state_new( + struct keyboard_config *config, + struct xkb_keymap *keymap_override, + struct xkb_compose_table *compose_table_override +); + +void keyboard_state_destroy( + struct keyboard_state *state +); + +int keyboard_state_process_key_event( + struct keyboard_state *state, + uint16_t evdev_keycode, + int32_t evdev_value, + xkb_keysym_t *keysym_out, + uint32_t *codepoint_out +); + +uint32_t keyboard_state_get_plain_codepoint( + struct keyboard_state *state, + uint16_t evdev_keycode, + int32_t evdev_value +); + +static inline bool keyboard_state_is_ctrl_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE); +} + +static inline bool keyboard_state_is_shift_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE); +} + +static inline bool keyboard_state_is_alt_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE); +} + +static inline bool keyboard_state_is_meta_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE); +} + +static inline bool keyboard_state_is_capslock_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_EFFECTIVE); +} + +static inline bool keyboard_state_is_numlock_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, XKB_MOD_NAME_NUM, XKB_STATE_MODS_EFFECTIVE); +} + +static inline bool keyboard_state_is_scrolllock_active( + struct keyboard_state *state +) { + return xkb_state_mod_name_is_active(state->state, "Mod3", XKB_STATE_MODS_EFFECTIVE); +} + +static inline struct keyboard_modifier_state keyboard_state_get_meta_state( + struct keyboard_state *state +) { + return (struct keyboard_modifier_state) { + .ctrl = keyboard_state_is_ctrl_active(state), + .shift = keyboard_state_is_shift_active(state), + .alt = keyboard_state_is_alt_active(state), + .meta = keyboard_state_is_meta_active(state), + .capslock = keyboard_state_is_capslock_active(state), + .numlock = keyboard_state_is_numlock_active(state), + .scrolllock = keyboard_state_is_scrolllock_active(state) + }; +} + +#endif \ No newline at end of file diff --git a/include/plugins/raw_keyboard.h b/include/plugins/raw_keyboard.h index b552f9e1..75585f78 100644 --- a/include/plugins/raw_keyboard.h +++ b/include/plugins/raw_keyboard.h @@ -1,11 +1,34 @@ #ifndef _KEY_EVENT_H #define _KEY_EVENT_H +#include +#include + #define KEY_EVENT_CHANNEL "flutter/keyevent" -#include +int rawkb_send_android_keyevent( + uint32_t flags, + uint32_t code_point, + unsigned int key_code, + uint32_t plain_code_point, + uint32_t scan_code, + uint32_t meta_state, + uint32_t source, + uint16_t vendor_id, + uint16_t product_id, + uint16_t device_id, + int repeat_count, + bool is_down, + char *character +); -int rawkb_on_keyevent(glfw_key key, uint32_t scan_code, glfw_key_action action); +int rawkb_send_gtk_keyevent( + uint32_t unicode_scalar_values, + uint32_t key_code, + uint32_t scan_code, + uint32_t modifiers, + bool is_down +); int rawkb_init(void); int rawkb_deinit(void); diff --git a/include/plugins/text_input.h b/include/plugins/text_input.h index d6e8f849..21bf8d91 100644 --- a/include/plugins/text_input.h +++ b/include/plugins/text_input.h @@ -1,7 +1,7 @@ #ifndef _TEXT_INPUT_H #define _TEXT_INPUT_H -#include +#include #define TEXT_INPUT_CHANNEL "flutter/textinput" @@ -16,6 +16,8 @@ enum text_input_type { kInputTypeEmailAddress, kInputTypeUrl, kInputTypeVisiblePassword, + kInputTypeName, + kInputTypeAddress }; enum text_input_action { @@ -40,24 +42,16 @@ struct text_input_configuration { enum text_input_action input_action; }; -int textin_sync_editing_state(void); -int textin_perform_action(enum text_input_action action); -int textin_on_connection_closed(void); - -// TextInput model functions (updating the text editing state) -bool textin_delete_selected(void); -bool textin_add_utf8_char(char *c); -bool textin_backspace(void); -bool textin_delete(void); -bool textin_move_cursor_to_beginning(void); -bool textin_move_cursor_to_end(void); -bool textin_move_cursor_forward(void); -bool textin_move_cursor_back(void); +enum floating_cursor_drag_state { + kFloatingCursorDragStateStart, + kFloatingCursorDragStateUpdate, + kFloatingCursorDragStateEnd +}; // parses the input string as linux terminal input and calls the TextInput model functions // accordingly. -int textin_on_utf8_char(char *c); -int textin_on_key(glfw_key key); +int textin_on_utf8_char(uint8_t *c); +int textin_on_xkb_keysym(xkb_keysym_t keysym); int textin_init(void); int textin_deinit(void); diff --git a/src/console_keyboard.c b/src/console_keyboard.c deleted file mode 100644 index c7da6d87..00000000 --- a/src/console_keyboard.c +++ /dev/null @@ -1,279 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -char *glfw_key_control_sequence[GLFW_KEY_LAST+1] = { - NULL, - [GLFW_KEY_ENTER] = "\n", - [GLFW_KEY_TAB] = "\t", - [GLFW_KEY_BACKSPACE] = "\x7f", - NULL, - [GLFW_KEY_DELETE] = "\e[3~", - [GLFW_KEY_RIGHT] = "\e[C", - [GLFW_KEY_LEFT] = "\e[D", - NULL, - [GLFW_KEY_PAGE_UP] = "\e[5~", - [GLFW_KEY_PAGE_DOWN] = "\e[6~", - [GLFW_KEY_HOME] = "\e[1~", - [GLFW_KEY_END] = "\e[4~", - NULL, - - // function keys - [GLFW_KEY_F1] = "\eOP", "\eOQ", "\eOR", "\eOS", - "\e[15~", "\e[17~", "\e[18~", "\e[19~", - "\e[20~", "\e[21~", "\e[23~", "\e[24~" -}; - -#define EVDEV_TO_GLFW(keyname) [KEY_##keyname] = GLFW_KEY_##keyname -#define EVDEV_TO_GLFW_RENAME(linux_keyname, glfw_keyname) [KEY_##linux_keyname] = GLFW_KEY_##glfw_keyname - -glfw_key evdev_code_glfw_key[KEY_CNT] = { - EVDEV_TO_GLFW(SPACE), - EVDEV_TO_GLFW(APOSTROPHE), - EVDEV_TO_GLFW(COMMA), - EVDEV_TO_GLFW(MINUS), - EVDEV_TO_GLFW_RENAME(DOT, PERIOD), - EVDEV_TO_GLFW(SLASH), - EVDEV_TO_GLFW(0), - EVDEV_TO_GLFW(1), - EVDEV_TO_GLFW(2), - EVDEV_TO_GLFW(3), - EVDEV_TO_GLFW(4), - EVDEV_TO_GLFW(5), - EVDEV_TO_GLFW(6), - EVDEV_TO_GLFW(7), - EVDEV_TO_GLFW(8), - EVDEV_TO_GLFW(9), - EVDEV_TO_GLFW(SEMICOLON), - EVDEV_TO_GLFW(EQUAL), - EVDEV_TO_GLFW(A), - EVDEV_TO_GLFW(B), - EVDEV_TO_GLFW(C), - EVDEV_TO_GLFW(D), - EVDEV_TO_GLFW(E), - EVDEV_TO_GLFW(F), - EVDEV_TO_GLFW(G), - EVDEV_TO_GLFW(H), - EVDEV_TO_GLFW(I), - EVDEV_TO_GLFW(J), - EVDEV_TO_GLFW(K), - EVDEV_TO_GLFW(L), - EVDEV_TO_GLFW(M), - EVDEV_TO_GLFW(N), - EVDEV_TO_GLFW(O), - EVDEV_TO_GLFW(P), - EVDEV_TO_GLFW(Q), - EVDEV_TO_GLFW(R), - EVDEV_TO_GLFW(S), - EVDEV_TO_GLFW(T), - EVDEV_TO_GLFW(U), - EVDEV_TO_GLFW(V), - EVDEV_TO_GLFW(W), - EVDEV_TO_GLFW(X), - EVDEV_TO_GLFW(Y), - EVDEV_TO_GLFW(Z), - EVDEV_TO_GLFW_RENAME(LEFTBRACE, LEFT_BRACKET), - EVDEV_TO_GLFW(BACKSLASH), - EVDEV_TO_GLFW_RENAME(RIGHTBRACE, RIGHT_BRACKET), - EVDEV_TO_GLFW_RENAME(GRAVE, GRAVE_ACCENT), - EVDEV_TO_GLFW_RENAME(ESC, ESCAPE), - EVDEV_TO_GLFW(ENTER), - EVDEV_TO_GLFW(TAB), - EVDEV_TO_GLFW(BACKSPACE), - EVDEV_TO_GLFW(INSERT), - EVDEV_TO_GLFW(DELETE), - EVDEV_TO_GLFW(RIGHT), - EVDEV_TO_GLFW(LEFT), - EVDEV_TO_GLFW(DOWN), - EVDEV_TO_GLFW(UP), - EVDEV_TO_GLFW_RENAME(PAGEUP, PAGE_UP), - EVDEV_TO_GLFW_RENAME(PAGEDOWN, PAGE_DOWN), - EVDEV_TO_GLFW(HOME), - EVDEV_TO_GLFW(END), - EVDEV_TO_GLFW_RENAME(CAPSLOCK, CAPS_LOCK), - EVDEV_TO_GLFW_RENAME(SCROLLLOCK, SCROLL_LOCK), - EVDEV_TO_GLFW_RENAME(NUMLOCK, NUM_LOCK), - EVDEV_TO_GLFW_RENAME(SYSRQ, PRINT_SCREEN), - EVDEV_TO_GLFW(PAUSE), - EVDEV_TO_GLFW(F1), - EVDEV_TO_GLFW(F2), - EVDEV_TO_GLFW(F3), - EVDEV_TO_GLFW(F4), - EVDEV_TO_GLFW(F5), - EVDEV_TO_GLFW(F6), - EVDEV_TO_GLFW(F7), - EVDEV_TO_GLFW(F8), - EVDEV_TO_GLFW(F9), - EVDEV_TO_GLFW(F10), - EVDEV_TO_GLFW(F11), - EVDEV_TO_GLFW(F12), - EVDEV_TO_GLFW(F13), - EVDEV_TO_GLFW(F14), - EVDEV_TO_GLFW(F15), - EVDEV_TO_GLFW(F16), - EVDEV_TO_GLFW(F17), - EVDEV_TO_GLFW(F18), - EVDEV_TO_GLFW(F19), - EVDEV_TO_GLFW(F20), - EVDEV_TO_GLFW(F21), - EVDEV_TO_GLFW(F22), - EVDEV_TO_GLFW(F23), - EVDEV_TO_GLFW(F24), - EVDEV_TO_GLFW_RENAME(KP0, KP_0), - EVDEV_TO_GLFW_RENAME(KP1, KP_1), - EVDEV_TO_GLFW_RENAME(KP2, KP_2), - EVDEV_TO_GLFW_RENAME(KP3, KP_3), - EVDEV_TO_GLFW_RENAME(KP4, KP_4), - EVDEV_TO_GLFW_RENAME(KP5, KP_5), - EVDEV_TO_GLFW_RENAME(KP6, KP_6), - EVDEV_TO_GLFW_RENAME(KP7, KP_7), - EVDEV_TO_GLFW_RENAME(KP8, KP_8), - EVDEV_TO_GLFW_RENAME(KP9, KP_9), - EVDEV_TO_GLFW_RENAME(KPDOT, KP_DECIMAL), - EVDEV_TO_GLFW_RENAME(KPSLASH, KP_DIVIDE), - EVDEV_TO_GLFW_RENAME(KPASTERISK, KP_MULTIPLY), - EVDEV_TO_GLFW_RENAME(KPMINUS, KP_SUBTRACT), - EVDEV_TO_GLFW_RENAME(KPPLUS, KP_ADD), - EVDEV_TO_GLFW_RENAME(KPENTER, KP_ENTER), - //CONVERSION(KP_EQUAL), // what is the equivalent of KP_EQUAL? is it - EVDEV_TO_GLFW_RENAME(LEFTSHIFT, LEFT_SHIFT), - EVDEV_TO_GLFW_RENAME(LEFTCTRL, LEFT_CONTROL), - EVDEV_TO_GLFW_RENAME(LEFTALT, LEFT_ALT), - EVDEV_TO_GLFW_RENAME(LEFTMETA, LEFT_SUPER), - EVDEV_TO_GLFW_RENAME(RIGHTSHIFT, RIGHT_SHIFT), - EVDEV_TO_GLFW_RENAME(RIGHTCTRL, RIGHT_CONTROL), - EVDEV_TO_GLFW_RENAME(RIGHTALT, RIGHT_ALT), - EVDEV_TO_GLFW_RENAME(RIGHTMETA, RIGHT_SUPER), - EVDEV_TO_GLFW(MENU), // could also be that the linux equivalent is KEY_COMPOSE -}; - -#undef EVDEV_TO_GLFW -#undef EVDEV_TO_GLFW_RENAME - -int console_flush_stdin(void) { - int ok; - - ok = tcflush(STDIN_FILENO, TCIFLUSH); - if (ok == -1) { - perror("could not flush stdin"); - return errno; - } - - return 0; -} - -struct termios original_config; -bool is_raw = false; - -int console_make_raw(void) { - struct termios config; - int ok; - - if (is_raw) return 0; - - ok = tcgetattr(STDIN_FILENO, &config); - if (ok == -1) { - perror("could not get terminal attributes"); - return errno; - } - - original_config = config; - - config.c_lflag &= ~(ECHO | ICANON); - - //config.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); - //config.c_oflag &= ~OPOST; - //config.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - //config.c_cflag &= ~(CSIZE | PARENB); - //config.c_cflag |= CS8; - - ok = tcsetattr(STDIN_FILENO, TCSANOW, &config); - if (ok == -1) { - perror("could not set terminal attributes"); - return errno; - } - - return 0; -} - -int console_restore(void) { - int ok; - - if (!is_raw) return 0; - - ok = tcsetattr(STDIN_FILENO, TCSANOW, &original_config); - if (ok == -1) { - perror("could not set terminal attributes"); - return errno; - } - - is_raw = false; - - return 0; -} - -size_t utf8_symbol_length(char *c) { - uint8_t first = ((uint8_t*) c)[0]; - uint8_t second = ((uint8_t*) c)[1]; - uint8_t third = ((uint8_t*) c)[2]; - uint8_t fourth = ((uint8_t*) c)[3]; - - if (first <= 0b01111111) { - // ASCII - return 1; - } else if (((first >> 5) == 0b110) && ((second >> 6) == 0b10)) { - // 2-byte UTF8 - return 2; - } else if (((first >> 4) == 0b1110) && ((second >> 6) == 0b10) && ((third >> 6) == 0b10)) { - // 3-byte UTF8 - return 3; - } else if (((first >> 3) == 0b11110) && ((second >> 6) == 0b10) && ((third >> 6) == 0b10) && ((fourth >> 6) == 0b10)) { - // 4-byte UTF8 - return 4; - } - - return 0; -} - -glfw_key console_try_get_key(char *input, char **input_out) { - if (input_out) - *input_out = input; - - for (glfw_key key = 0; key <= GLFW_KEY_LAST; key++) { - if (glfw_key_control_sequence[key] == NULL) - continue; - - if (strcmp(input, glfw_key_control_sequence[key]) == 0) { - if (input_out) - *input_out += strlen(glfw_key_control_sequence[key]); - - return key; - } - } - - return GLFW_KEY_UNKNOWN; -} - -char *console_try_get_utf8char(char *input, char **input_out) { - if (input_out) - *input_out = input; - - size_t length = utf8_symbol_length(input); - - if ((length == 1) && !isprint(*input)) - return NULL; - - if (length == 0) - return NULL; - - *input_out += length; - - return input; -} \ No newline at end of file diff --git a/src/flutter-pi.c b/src/flutter-pi.c index dfb3e969..26640b04 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -44,7 +44,7 @@ #include #include -#include +#include #include #include #include @@ -1890,6 +1890,12 @@ static void libinput_interface_on_close(int fd, void *userdata) { } static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct libinput_event_keyboard *keyboard_event; + struct libinput_event_pointer *pointer_event; + struct libinput_event_touch *touch_event; + struct input_device_data *data; + enum libinput_event_type type; + struct libinput_device *device; struct libinput_event *event; FlutterPointerEvent pointer_events[64]; FlutterEngineResult result; @@ -1903,12 +1909,12 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void } while (event = libinput_get_event(flutterpi.input.libinput), event != NULL) { - enum libinput_event_type type = libinput_event_get_type(event); + type = libinput_event_get_type(event); if (type == LIBINPUT_EVENT_DEVICE_ADDED) { - struct libinput_device *device = libinput_event_get_device(event); + device = libinput_event_get_device(event); - struct input_device_data *data = calloc(1, sizeof(*data)); + data = calloc(1, sizeof(*data)); data->flutter_device_id_offset = flutterpi.input.next_unused_flutter_device_id; libinput_device_set_user_data(device, data); @@ -1947,11 +1953,12 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void .buttons = 0 }; } + } else if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + data->keyboard_state = keyboard_state_new(flutterpi.input.keyboard_config, NULL, NULL); } } else if (LIBINPUT_EVENT_IS_TOUCH(type)) { - struct libinput_event_touch *touch_event = libinput_event_get_touch_event(event); - - struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); + touch_event = libinput_event_get_touch_event(event); + data = libinput_device_get_user_data(libinput_event_get_device(event)); if ((type == LIBINPUT_EVENT_TOUCH_DOWN) || (type == LIBINPUT_EVENT_TOUCH_MOTION) || (type == LIBINPUT_EVENT_TOUCH_UP)) { int slot = libinput_event_touch_get_slot(touch_event); @@ -2006,8 +2013,8 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void } } } else if (LIBINPUT_EVENT_IS_POINTER(type)) { - struct libinput_event_pointer *pointer_event = libinput_event_get_pointer_event(event); - struct input_device_data *data = libinput_device_get_user_data(libinput_event_get_device(event)); + pointer_event = libinput_event_get_pointer_event(event); + data = libinput_device_get_user_data(libinput_event_get_device(event)); if (type == LIBINPUT_EVENT_POINTER_MOTION) { double dx = libinput_event_pointer_get_dx(pointer_event); @@ -2139,14 +2146,75 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void } } else if (LIBINPUT_EVENT_IS_KEYBOARD(type)) { - struct libinput_event_keyboard *keyboard_event = libinput_event_get_keyboard_event(event); + struct keyboard_modifier_state mods; + enum libinput_key_state key_state; + xkb_keysym_t keysym; + uint32_t codepoint, plain_codepoint; + uint16_t evdev_keycode; - uint32_t keycode = libinput_event_keyboard_get_key(keyboard_event); - enum libinput_key_state state = libinput_event_keyboard_get_key_state(keyboard_event); + keyboard_event = libinput_event_get_keyboard_event(event); + data = libinput_device_get_user_data(libinput_event_get_device(event)); + evdev_keycode = libinput_event_keyboard_get_key(keyboard_event); + key_state = libinput_event_keyboard_get_key_state(keyboard_event); + + mods = keyboard_state_get_meta_state(data->keyboard_state); + + ok = keyboard_state_process_key_event( + data->keyboard_state, + evdev_keycode, + (int32_t) key_state, + &keysym, + &codepoint + ); - glfw_key glfw_key = evdev_code_glfw_key[keycode]; - - rawkb_on_keyevent(glfw_key, keycode, state == LIBINPUT_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE); + printf("[key event] keycode: 0x%04X, type: %s, keysym: 0x%08X, codepoint: 0x%08X\n", evdev_keycode, key_state? "down" : " up ", keysym, codepoint); + + plain_codepoint = keyboard_state_get_plain_codepoint(data->keyboard_state, evdev_keycode, 1); + + rawkb_send_gtk_keyevent( + plain_codepoint, + (uint32_t) keysym, + evdev_keycode + 8, + keyboard_state_is_shift_active(data->keyboard_state) + | (keyboard_state_is_capslock_active(data->keyboard_state) << 1) + | (keyboard_state_is_ctrl_active(data->keyboard_state) << 2) + | (keyboard_state_is_alt_active(data->keyboard_state) << 3) + | (keyboard_state_is_numlock_active(data->keyboard_state) << 4) + | (keyboard_state_is_meta_active(data->keyboard_state) << 28), + key_state + ); + + if (codepoint) { + if (codepoint < 0x80) { + if (isprint(codepoint)) { + textin_on_utf8_char((uint8_t[1]) {codepoint}); + } + } else if (codepoint < 0x800) { + textin_on_utf8_char((uint8_t[2]) { + 0xc0 | (codepoint >> 6), + 0x80 | (codepoint & 0x3f) + }); + } else if (codepoint < 0x10000) { + if (!(codepoint >= 0xD800 && codepoint < 0xE000) && !(codepoint == 0xFFFF)) { + textin_on_utf8_char((uint8_t[3]) { + 0xe0 | (codepoint >> 12), + 0x80 | ((codepoint >> 6) & 0x3f), + 0x80 | (codepoint & 0x3f) + }); + } + } else if (codepoint < 0x110000) { + textin_on_utf8_char((uint8_t[4]) { + 0xf0 | (codepoint >> 18), + 0x80 | ((codepoint >> 12) & 0x3f), + 0x80 | ((codepoint >> 6) & 0x3f), + 0x80 | (codepoint & 0x3f) + }); + } + } + + if (keysym) { + textin_on_xkb_keysym(keysym); + } } libinput_event_destroy(event); @@ -2167,38 +2235,6 @@ static int on_libinput_ready(sd_event_source *s, int fd, uint32_t revents, void return 0; } -static int on_stdin_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - static char buffer[4096]; - glfw_key key; - char *cursor; - char *c; - int ok; - - ok = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); - if (ok == -1) { - perror("[flutter-pi] Could not read from stdin"); - return errno; - } else if (ok == 0) { - fprintf(stderr, "[flutter-pi] WARNING: reached EOF for stdin\n"); - return EBADF; - } - - buffer[ok] = '\0'; - - cursor = buffer; - while (*cursor) { - if (key = console_try_get_key(cursor, &cursor), key != GLFW_KEY_UNKNOWN) { - textin_on_key(key); - } else if (c = console_try_get_utf8char(cursor, &cursor), c != NULL) { - textin_on_utf8_char(c); - } else { - // neither a char nor a (function) key. we don't know when - // we can start parsing the buffer again, so just stop here - break; - } - } -} - static struct libinput *try_create_udev_backed_libinput(void) { #ifdef BUILD_WITHOUT_UDEV_SUPPORT return NULL; @@ -2385,11 +2421,15 @@ static struct libinput *try_create_path_backed_libinput(void) { } static int init_user_input(void) { - sd_event_source *libinput_event_source, *stdin_event_source; + sd_event_source *libinput_event_source; + struct keyboard_config *kbdcfg; struct libinput *libinput; int ok; + libinput_event_source = NULL; + kbdcfg = NULL; libinput = NULL; + if (flutterpi.input.use_paths == false) { libinput = try_create_udev_backed_libinput(); } @@ -2398,7 +2438,6 @@ static int init_user_input(void) { libinput = try_create_path_backed_libinput(); } - libinput_event_source = NULL; if (libinput != NULL) { ok = sd_event_add_io( flutterpi.event_loop, @@ -2423,36 +2462,20 @@ static int init_user_input(void) { # endif return -ok; } + + if (flutterpi.input.disable_text_input == false) { + kbdcfg = keyboard_config_new(); + if (kbdcfg == NULL) { + fprintf(stderr, "[flutter-pi] Could not initialize keyboard configuration. Flutter-pi will run without text/raw keyboard input.\n"); + } + } } else { fprintf(stderr, "[flutter-pi] Could not initialize input. Flutter-pi will run without user input.\n"); } - stdin_event_source = NULL; - if (flutterpi.input.disable_text_input == false) { - ok = sd_event_add_io( - flutterpi.event_loop, - &stdin_event_source, - STDIN_FILENO, - EPOLLIN, - on_stdin_ready, - NULL - ); - if (ok < 0) { - fprintf(stderr, "[flutter-pi] Could not add callback for console input. sd_event_add_io: %s\n", strerror(-ok)); - } - - ok = console_make_raw(); - if (ok == 0) { - console_flush_stdin(); - } else { - fprintf(stderr, "[flutter-pi] Could not make stdin raw. Flutter-pi will run without text input support.\n"); - sd_event_source_unrefp(&stdin_event_source); - } - } - flutterpi.input.libinput = libinput; flutterpi.input.libinput_event_source = libinput_event_source; - flutterpi.input.stdin_event_source = stdin_event_source; + flutterpi.input.keyboard_config = kbdcfg; return 0; } diff --git a/src/keyboard.c b/src/keyboard.c new file mode 100644 index 00000000..a7b86234 --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,369 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + + +static int find_var_offset_in_string(const char *varname, const char *buffer, regmatch_t *match) { + regmatch_t matches[2]; + char *pattern; + int ok; + + ok = asprintf(&pattern, "%s=\"([^\"]*)\"", varname); + if (ok < 0) { + ok = ENOMEM; + goto fail_set_match; + } + + regex_t regex; + ok = regcomp(®ex, pattern, REG_EXTENDED); + if (ok != 0) { + //pregexerr("regcomp", ok, ®ex); + ok = EINVAL; + goto fail_set_match; + } + + ok = regexec(®ex, buffer, 2, matches, 0); + if (ok == REG_NOMATCH) { + ok = EINVAL; + goto fail_free_regex; + } + + if (match != NULL) { + *match = matches[1]; + } + + return 0; + + fail_free_regex: + regfree(®ex); + + fail_set_match: + if (match != NULL) { + match->rm_so = -1; + match->rm_eo = -1; + } + + fail_return_ok: + return ok; +} + +static char *get_value_allocated(const char *varname, const char *buffer) { + regmatch_t match; + char *allocated; + int ok, match_length; + + ok = find_var_offset_in_string(varname, buffer, &match); + if (ok != 0) { + fprintf(stderr, "Error finding variable in buffer: find_var_offset_in_string: %s\n", strerror(ok)); + errno = ok; + return NULL; + } else if ((match.rm_so == -1) || (match.rm_eo == -1)) { + fprintf(stderr, "Could not find variable in buffer.\n"); + errno = EINVAL; + return NULL; + } + + match_length = match.rm_eo - match.rm_so; + + allocated = malloc(match_length + 1); + if (allocated == NULL) { + errno = ENOMEM; + return NULL; + } + + strncpy(allocated, buffer + match.rm_so, match_length); + + allocated[match_length] = '\0'; + + return allocated; +} + +static char *load_file(const char *path) { + struct stat s; + int ok, fd; + + ok = open(path, O_RDONLY); + if (ok < 0) { + goto fail_return_null; + } else { + fd = ok; + } + + ok = fstat(fd, &s); + if (ok < 0) { + goto fail_close; + } + + char *buffer = malloc(s.st_size + 1); + if (buffer == NULL) { + errno = ENOMEM; + goto fail_close; + } + + int result = read(fd, buffer, s.st_size); + if (result < 0) { + goto fail_close; + } else if (result == 0) { + errno = EINVAL; + goto fail_close; + } else if (result < s.st_size) { + errno = EINVAL; + goto fail_close; + } + + close(fd); + + buffer[s.st_size] = '\0'; + + return buffer; + + + fail_close: + close(fd); + + fail_return_null: + return NULL; +} + +static struct xkb_keymap *load_default_keymap(struct xkb_context *context) { + char *file = load_file("/etc/default/keyboard"); + if (file == NULL) { + perror("[keyboard] Could not load default keyboard configuration from \"/etc/default/keyboard\". "); + return NULL; + } + + char *xkbmodel = get_value_allocated("XKBMODEL", file); + char *xkblayout = get_value_allocated("XKBLAYOUT", file); + char *xkbvariant = get_value_allocated("XKBVARIANT", file); + char *xkboptions = get_value_allocated("XKBOPTIONS", file); + + free(file); + + struct xkb_rule_names names = { + .rules = NULL, + .model = xkbmodel, + .layout = xkblayout, + .variant = xkbvariant, + .options = xkboptions + }; + + struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); + + if (xkbmodel != NULL) free(xkbmodel); + if (xkblayout != NULL) free(xkblayout); + if (xkbvariant != NULL) free(xkbvariant); + if (xkboptions != NULL) free(xkboptions); + + if (keymap == NULL) { + fprintf(stderr, "[keyboard] Could not load default keymap.\n"); + } + + return keymap; +} + +static struct xkb_compose_table *load_default_compose_table(struct xkb_context *context) { + setlocale(LC_ALL, ""); + return xkb_compose_table_new_from_locale(context, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS); +} + + +struct keyboard_config *keyboard_config_new(void) { + struct keyboard_config *cfg; + struct xkb_compose_table *compose_table; + struct xkb_context *ctx; + struct xkb_keymap *keymap; + struct xkb_state *plain_state; + int ok; + + cfg = malloc(sizeof *cfg); + if (cfg == NULL) { + errno = ENOMEM; + goto fail_return_null; + } + + ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (ctx == NULL) { + goto fail_free_cfg; + } + + compose_table = load_default_compose_table(ctx); + if (compose_table == NULL) { + goto fail_free_context; + } + + keymap = load_default_keymap(ctx); + if (keymap == NULL) { + goto fail_free_compose_table; + } + + cfg->context = ctx; + cfg->default_compose_table = compose_table; + cfg->default_keymap = keymap; + + return cfg; + + fail_free_keymap: + xkb_keymap_unref(keymap); + + fail_free_compose_table: + xkb_compose_table_unref(compose_table); + + fail_free_context: + xkb_context_unref(ctx); + + fail_free_cfg: + free(cfg); + + fail_return_null: + return NULL; +} + +void keyboard_config_destroy(struct keyboard_config *config) { + xkb_keymap_unref(config->default_keymap); + xkb_compose_table_unref(config->default_compose_table); + xkb_context_unref(config->context); + free(config); +} + + +struct keyboard_state *keyboard_state_new( + struct keyboard_config *config, + struct xkb_keymap *keymap_override, + struct xkb_compose_table *compose_table_override +) { + struct keyboard_state *state; + struct xkb_compose_state *compose_state; + struct xkb_state *xkb_state, *plain_xkb_state; + + state = malloc(sizeof *state); + if (state == NULL) { + errno = ENOMEM; + goto fail_return_null; + } + + xkb_state = xkb_state_new(keymap_override != NULL ? keymap_override : config->default_keymap); + if (xkb_state == NULL) { + goto fail_free_state; + } + + plain_xkb_state = xkb_state_new(keymap_override != NULL ? keymap_override : config->default_keymap); + if (plain_xkb_state == NULL) { + goto fail_free_xkb_state; + } + + compose_state = xkb_compose_state_new(compose_table_override != NULL ? compose_table_override : config->default_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); + if (compose_state == NULL) { + goto fail_free_xkb_state; + } + + state->config = config; + state->state = xkb_state; + state->plain_state = plain_xkb_state; + state->compose_state = compose_state; + + return state; + + fail_free_plain_xkb_state: + xkb_state_unref(plain_xkb_state); + + fail_free_xkb_state: + xkb_state_unref(xkb_state); + + fail_free_state: + free(state); + + fail_return_null: + return NULL; +} + +void keyboard_state_destroy( + struct keyboard_state *state +) { + xkb_compose_state_unref(state->compose_state); + xkb_state_unref(state->plain_state); + xkb_state_unref(state->state); + free(state); +} + +int keyboard_state_process_key_event( + struct keyboard_state *state, + uint16_t evdev_keycode, + int32_t evdev_value, + xkb_keysym_t *keysym_out, + uint32_t *codepoint_out +) { + enum xkb_compose_feed_result feed_result; + enum xkb_compose_status compose_status; + xkb_keycode_t xkb_keycode; + xkb_keysym_t keysym; + uint32_t codepoint; + + /** + * evdev_value = 0: release + * evdev_value = 1: press + * evdev_value = 2: repeat + */ + + keysym = 0; + codepoint = 0; + xkb_keycode = evdev_keycode + 8; + + if (evdev_value) { + keysym = xkb_state_key_get_one_sym(state->state, xkb_keycode); + + feed_result = xkb_compose_state_feed(state->compose_state, keysym); + compose_status = xkb_compose_state_get_status(state->compose_state); + if (feed_result == XKB_COMPOSE_FEED_ACCEPTED && compose_status == XKB_COMPOSE_COMPOSING) { + keysym = XKB_KEY_NoSymbol; + } + + if (compose_status == XKB_COMPOSE_COMPOSED) { + keysym = xkb_compose_state_get_one_sym(state->compose_state); + xkb_compose_state_reset(state->compose_state); + } else if (compose_status == XKB_COMPOSE_CANCELLED) { + xkb_compose_state_reset(state->compose_state); + } + + codepoint = xkb_keysym_to_utf32(keysym); + } + + xkb_state_update_key(state->state, xkb_keycode, (enum xkb_key_direction) evdev_value); + + if (keysym_out) *keysym_out = keysym; + if (codepoint_out) *codepoint_out = codepoint; + + return 0; +} + +uint32_t keyboard_state_get_plain_codepoint( + struct keyboard_state *state, + uint16_t evdev_keycode, + int32_t evdev_value +) { + xkb_keycode_t xkb_keycode = evdev_keycode + 8; + + if (evdev_value) { + return xkb_state_key_get_utf32(state->plain_state, xkb_keycode); + } + + return 0; +} \ No newline at end of file diff --git a/src/plugins/raw_keyboard.c b/src/plugins/raw_keyboard.c index 3649641a..9d158d37 100644 --- a/src/plugins/raw_keyboard.c +++ b/src/plugins/raw_keyboard.c @@ -7,36 +7,80 @@ #include #include #include +#include #include -static struct { - // same as mods, just that it differentiates between left and right-sided modifiers. - uint16_t leftright_mods; - glfw_keymod_map mods; - bool initialized; -} raw_keyboard = {0}; - -static int send_glfw_keyevent(uint32_t code_point, glfw_key key_code, uint32_t scan_code, glfw_keymod_map mods, bool is_down) { +int rawkb_send_android_keyevent( + uint32_t flags, + uint32_t code_point, + unsigned int key_code, + uint32_t plain_code_point, + uint32_t scan_code, + uint32_t meta_state, + uint32_t source, + uint16_t vendor_id, + uint16_t product_id, + uint16_t device_id, + int repeat_count, + bool is_down, + char *character +) { + /** + * keymap: android + * flags: flags + * codePoint: code_point + * keyCode: key_code + * plainCodePoint: plain_code_point + * scanCode: scan_code + * metaState: meta_state + * source: source + * vendorId: vendor_id + * productId: product_id + * deviceId: device_id + * repeatCount: repeatCount, + * type: is_down? "keydown" : "keyup" + * character: character + */ + return platch_send( KEY_EVENT_CHANNEL, &(struct platch_obj) { .codec = kJSONMessageCodec, .json_value = { .type = kJsonObject, - .size = 7, - .keys = (char*[7]) { - "keymap", "toolkit", "unicodeScalarValues", "keyCode", "scanCode", - "modifiers", "type" + .size = 14, + .keys = (char*[14]) { + "keymap", + "flags", + "codePoint", + "keyCode", + "plainCodePoint", + "scanCode", + "metaState", + "source", + "vendorId", + "productId", + "deviceId", + "repeatCount", + "type", + "character" }, - .values = (struct json_value[7]) { - {.type = kJsonString, .string_value = "linux"}, - {.type = kJsonString, .string_value = "glfw"}, - {.type = kJsonNumber, .number_value = code_point}, - {.type = kJsonNumber, .number_value = key_code}, - {.type = kJsonNumber, .number_value = scan_code}, - {.type = kJsonNumber, .number_value = mods}, - {.type = kJsonString, .string_value = is_down? "keydown" : "keyup"} + .values = (struct json_value[14]) { + /* keymap */ {.type = kJsonString, .string_value = "android"}, + /* flags */ {.type = kJsonNumber, .number_value = flags}, + /* codePoint */ {.type = kJsonNumber, .number_value = code_point}, + /* keyCode */ {.type = kJsonNumber, .number_value = key_code}, + /* plainCodePoint */ {.type = kJsonNumber, .number_value = code_point}, + /* scanCode */ {.type = kJsonNumber, .number_value = scan_code}, + /* metaState */ {.type = kJsonNumber, .number_value = meta_state}, + /* source */ {.type = kJsonNumber, .number_value = source}, + /* vendorId */ {.type = kJsonNumber, .number_value = vendor_id}, + /* productId */ {.type = kJsonNumber, .number_value = product_id}, + /* deviceId */ {.type = kJsonNumber, .number_value = device_id}, + /* repeatCount */ {.type = kJsonNumber, .number_value = repeat_count}, + /* type */ {.type = kJsonString, .string_value = is_down? "keydown" : "keyup"}, + /* character */ {.type = character? kJsonString : kJsonNull, .string_value = character} } } }, @@ -46,88 +90,60 @@ static int send_glfw_keyevent(uint32_t code_point, glfw_key key_code, uint32_t s ); } -int rawkb_on_keyevent(glfw_key key, uint32_t scan_code, glfw_key_action action) { - glfw_keymod_map mods_after = raw_keyboard.mods; - uint16_t lrmods_after = raw_keyboard.leftright_mods; - glfw_keymod mod; - bool send; - - if (!raw_keyboard.initialized) return 0; - - // flutter's glfw key adapter does not distinguish between left- and right-sided modifier keys. - // so we implicitly combine the state of left and right-sided keys - mod = GLFW_KEYMOD_FOR_KEY(key); - send = !mod; - - if (mod && ((action == GLFW_PRESS) || (action == GLFW_RELEASE))) { - lrmods_after = raw_keyboard.leftright_mods; - - switch (mod) { - case GLFW_MOD_SHIFT: - case GLFW_MOD_CONTROL: - case GLFW_MOD_ALT: - case GLFW_MOD_SUPER: ; - uint16_t sided_mod = mod; - - if (GLFW_KEY_IS_RIGHTSIDED(key)) - sided_mod = sided_mod << 8; +int rawkb_send_gtk_keyevent( + uint32_t unicode_scalar_values, + uint32_t key_code, + uint32_t scan_code, + uint32_t modifiers, + bool is_down +) { + /** + * keymap: linux + * toolkit: glfw + * unicodeScalarValues: code_point + * keyCode: key_code + * scanCode: scan_code + * modifiers: mods + * type: is_down? "keydown" : "keyup" + */ - if (action == GLFW_PRESS) { - lrmods_after |= sided_mod; - } else if (action == GLFW_RELEASE) { - lrmods_after &= ~sided_mod; + return platch_send( + KEY_EVENT_CHANNEL, + &(struct platch_obj) { + .codec = kJSONMessageCodec, + .json_value = { + .type = kJsonObject, + .size = 7, + .keys = (char*[7]) { + "keymap", + "toolkit", + "unicodeScalarValues", + "keyCode", + "scanCode", + "modifiers", + "type" + }, + .values = (struct json_value[7]) { + /* keymap */ {.type = kJsonString, .string_value = "linux"}, + /* toolkit */ {.type = kJsonString, .string_value = "gtk"}, + /* unicodeScalarValues */ {.type = kJsonNumber, .number_value = unicode_scalar_values}, + /* keyCode */ {.type = kJsonNumber, .number_value = key_code}, + /* scanCode */ {.type = kJsonNumber, .number_value = scan_code}, + /* modifiers */ {.type = kJsonNumber, .number_value = modifiers}, + /* type */ {.type = kJsonString, .string_value = is_down? "keydown" : "keyup"} } - break; - case GLFW_MOD_CAPS_LOCK: - case GLFW_MOD_NUM_LOCK: - if (action == GLFW_PRESS) - lrmods_after ^= mod; - break; - default: - break; - } - - mods_after = lrmods_after | (lrmods_after >> 8); - if (mods_after != raw_keyboard.mods) - send = true; - } - - switch (key) { - case GLFW_KEY_RIGHT_SHIFT: - key = GLFW_KEY_LEFT_SHIFT; - break; - case GLFW_KEY_RIGHT_CONTROL: - key = GLFW_KEY_LEFT_CONTROL; - break; - case GLFW_KEY_RIGHT_ALT: - key = GLFW_KEY_LEFT_ALT; - break; - case GLFW_KEY_RIGHT_SUPER: - key = GLFW_KEY_LEFT_SUPER; - break; - default: break; - } - - if (send) { - send_glfw_keyevent(0, key, scan_code, raw_keyboard.mods, action != GLFW_RELEASE); - } - - raw_keyboard.leftright_mods = lrmods_after; - raw_keyboard.mods = mods_after; - - return 0; + } + }, + kJSONMessageCodec, + NULL, + NULL + ); } int rawkb_init(void) { - raw_keyboard.leftright_mods = 0; - raw_keyboard.mods = 0; - raw_keyboard.initialized = true; - return 0; } int rawkb_deinit(void) { - raw_keyboard.initialized = false; - return 0; } \ No newline at end of file diff --git a/src/plugins/text_input.c b/src/plugins/text_input.c index 755f610c..0a855f34 100644 --- a/src/plugins/text_input.c +++ b/src/plugins/text_input.c @@ -6,11 +6,16 @@ #include #include +#include #include struct { - int32_t transaction_id; + int64_t connection_id; enum text_input_type input_type; + bool allow_signs; + bool has_allow_signs; + bool allow_decimal; + bool has_allow_decimal; bool autocorrect; enum text_input_action input_action; char text[TEXT_INPUT_MAX_CHARS]; @@ -20,351 +25,581 @@ struct { int composing_base, composing_extent; bool warned_about_autocorrect; } text_input = { - .transaction_id = -1 + .connection_id = -1 }; -int textin_on_receive(char *channel, struct platch_obj *object, FlutterPlatformMessageResponseHandle *responsehandle) { - struct json_value jsvalue, *temp, *temp2, *state, *config; - int ok; +/** + * UTF8 utility functions + */ +static inline uint8_t utf8_symbol_length(uint8_t c) { + if (!(c & 0b10000000)) { + return 1; + } else if (!(c & 0b01000000)) { + // we are in a follow byte + return 0; + } else if (c & 0b00100000) { + return 2; + } else if (c & 0b00010000) { + return 3; + } else if (c & 0b00001000) { + return 4; + } - if STREQ("TextInput.setClient", object->method) { - /* - * TextInput.setClient(List) - * Establishes a new transaction. The argument is - * a [List] whose first value is an integer representing a previously - * unused transaction identifier, and the second is a [String] with a - * JSON-encoded object with five keys, as obtained from - * [TextInputConfiguration.toJSON]. This method must be invoked before any - * others (except `TextInput.hide`). See [TextInput.attach]. - */ - - if ((object->json_arg.type != kJsonArray) || (object->json_arg.size != 2)) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected `arg` to be an array with length 2." - ); - } + return 0; +} - if (object->json_arg.array[0].type != kJsonNumber) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected transaction id to be a number." - ); - } +static inline uint8_t *symbol_at(unsigned int symbol_index) { + uint8_t *cursor = text_input.text; - if (object->json_arg.array[1].type != kJsonObject) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected text input configuration to be a String" - ); - } + for (; symbol_index && *cursor; symbol_index--) + cursor += utf8_symbol_length(*cursor); - struct json_value *config = &object->json_arg.array[1]; + return symbol_index? NULL : cursor; +} - if (config->type != kJsonObject) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected decoded text input configuration to be an Object" - ); - } +static inline int to_byte_index(unsigned int symbol_index) { + char *cursor = text_input.text; - enum text_input_type input_type; - bool autocorrect; - enum text_input_action input_action; + while ((*cursor) && (symbol_index--)) + cursor += utf8_symbol_length(*cursor); - // AUTOCORRECT - temp = jsobject_get(config, "autocorrect"); - if (!(temp && ((temp->type == kJsonTrue) || (temp->type == kJsonFalse)))) - goto invalid_config; + if (*cursor) + return cursor - text_input.text; - autocorrect = temp->type == kJsonTrue; - - // INPUT ACTION - temp = jsobject_get(config, "inputAction"); - if (!(temp && (temp->type == kJsonString))) - goto invalid_config; - - if STREQ("TextInputAction.none", temp->string_value) - input_action = kTextInputActionNone; - else if STREQ("TextInputAction.unspecified", temp->string_value) - input_action = kTextInputActionUnspecified; - else if STREQ("TextInputAction.done", temp->string_value) - input_action = kTextInputActionDone; - else if STREQ("TextInputAction.go", temp->string_value) - input_action = kTextInputActionGo; - else if STREQ("TextInputAction.search", temp->string_value) - input_action = kTextInputActionSearch; - else if STREQ("TextInputAction.send", temp->string_value) - input_action = kTextInputActionSend; - else if STREQ("TextInputAction.next", temp->string_value) - input_action = kTextInputActionNext; - else if STREQ("TextInputAction.previous", temp->string_value) - input_action = kTextInputActionPrevious; - else if STREQ("TextInputAction.continueAction", temp->string_value) - input_action = kTextInputActionContinueAction; - else if STREQ("TextInputAction.join", temp->string_value) - input_action = kTextInputActionJoin; - else if STREQ("TextInputAction.route", temp->string_value) - input_action = kTextInputActionRoute; - else if STREQ("TextInputAction.emergencyCall", temp->string_value) - input_action = kTextInputActionEmergencyCall; - else if STREQ("TextInputAction.newline", temp->string_value) - input_action = kTextInputActionNewline; - else - goto invalid_config; - - - // INPUT TYPE - temp = jsobject_get(config, "inputType"); - - if (!temp || temp->type != kJsonObject) - goto invalid_config; - - - temp2 = jsobject_get(temp, "name"); - - if (!temp2 || temp2->type != kJsonString) - goto invalid_config; - - if STREQ("TextInputType.text", temp2->string_value) { - input_type = kInputTypeText; - } else if STREQ("TextINputType.multiline", temp2->string_value) { - input_type = kInputTypeMultiline; - } else if STREQ("TextInputType.number", temp2->string_value) { - input_type = kInputTypeNumber; - } else if STREQ("TextInputType.phone", temp2->string_value) { - input_type = kInputTypePhone; - } else if STREQ("TextInputType.datetime", temp2->string_value) { - input_type = kInputTypeDatetime; - } else if STREQ("TextInputType.emailAddress", temp2->string_value) { - input_type = kInputTypeEmailAddress; - } else if STREQ("TextInputType.url", temp2->string_value) { - input_type = kInputTypeUrl; - } else if STREQ("TextInputType.visiblePassword", temp2->string_value) { - input_type = kInputTypeVisiblePassword; - } else { - goto invalid_config; - } + return -1; +} + +static inline int to_symbol_index(unsigned int byte_index) { + char *cursor = text_input.text; + char *target_cursor = cursor + byte_index; + int symbol_index = 0; - // TRANSACTION ID - int32_t new_id = (int32_t) object->json_arg.array[0].number_value; + while ((*cursor) && (cursor < target_cursor)) { + cursor += utf8_symbol_length(*cursor); + symbol_index++; + } - // everything okay, apply the new text editing config - text_input.transaction_id = new_id; - text_input.autocorrect = autocorrect; - text_input.input_action = input_action; - text_input.input_type = input_type; + return cursor < target_cursor? -1 : symbol_index; +} - if (autocorrect && (!text_input.warned_about_autocorrect)) { - printf("[text_input] warning: flutter requested native autocorrect, which" - "is not supported by flutter-pi.\n"); - text_input.warned_about_autocorrect = true; - } +/** + * Platform message callbacks + */ +static int on_set_client( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + enum text_input_action input_action; + enum text_input_type input_type; + struct json_value jsvalue, *temp, *temp2, *state, *config; + int64_t transaction_id; + bool autocorrect, allow_signs, allow_decimal, has_allow_signs, has_allow_decimal; + int ok; - return platch_respond( + /* + * TextInput.setClient(List) + * Establishes a new transaction. The argument is + * a [List] whose first value is an integer representing a previously + * unused transaction identifier, and the second is a [String] with a + * JSON-encoded object with five keys, as obtained from + * [TextInputConfiguration.toJSON]. This method must be invoked before any + * others (except `TextInput.hide`). See [TextInput.attach]. + */ + + if ((object->json_arg.type != kJsonArray) || (object->json_arg.size != 2)) { + return platch_respond_illegal_arg_json( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + "Expected `arg` to be an array with length 2." ); + } - // invalid config given to setClient - invalid_config: - return platch_respond_illegal_arg_json( - responsehandle, - "Expected decoded text input configuration to at least contain values for \"autocorrect\"" - " and \"inputAction\"" - ); + if (object->json_arg.array[0].type != kJsonNumber) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[0]` to be a number" + ); + } - } else if STREQ("TextInput.show", object->method) { - /* - * TextInput.show() - * Show the keyboard. See [TextInputConnection.show]. - * - */ - - // do nothing since we use a physical keyboard. - return platch_respond( + if (object->json_arg.array[1].type != kJsonObject) { + return platch_respond_illegal_arg_json( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + "Expected `arg[1]` to be an map." + ); + } + + config = &object->json_arg.array[1]; + + // AUTOCORRECT + temp = jsobject_get(config, "autocorrect"); + if (temp == NULL || (temp->type != kJsonTrue && temp->type != kJsonFalse)) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['autocorrect']` to be a boolean." + ); + } else { + autocorrect = temp->type == kJsonTrue; + } + + // INPUT ACTION + temp = jsobject_get(config, "inputAction"); + if (temp == NULL || temp->type != kJsonString) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputAction']` to be a string-ification of `TextInputAction`." + ); + } + + if STREQ("TextInputAction.none", temp->string_value) + input_action = kTextInputActionNone; + else if STREQ("TextInputAction.unspecified", temp->string_value) + input_action = kTextInputActionUnspecified; + else if STREQ("TextInputAction.done", temp->string_value) + input_action = kTextInputActionDone; + else if STREQ("TextInputAction.go", temp->string_value) + input_action = kTextInputActionGo; + else if STREQ("TextInputAction.search", temp->string_value) + input_action = kTextInputActionSearch; + else if STREQ("TextInputAction.send", temp->string_value) + input_action = kTextInputActionSend; + else if STREQ("TextInputAction.next", temp->string_value) + input_action = kTextInputActionNext; + else if STREQ("TextInputAction.previous", temp->string_value) + input_action = kTextInputActionPrevious; + else if STREQ("TextInputAction.continueAction", temp->string_value) + input_action = kTextInputActionContinueAction; + else if STREQ("TextInputAction.join", temp->string_value) + input_action = kTextInputActionJoin; + else if STREQ("TextInputAction.route", temp->string_value) + input_action = kTextInputActionRoute; + else if STREQ("TextInputAction.emergencyCall", temp->string_value) + input_action = kTextInputActionEmergencyCall; + else if STREQ("TextInputAction.newline", temp->string_value) + input_action = kTextInputActionNewline; + else + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputAction']` to be a string-ification of `TextInputAction`." ); - } else if STREQ("TextInput.setEditingState", object->method) { - /* - * TextInput.setEditingState(Map textEditingValue) - * Update the value in the text editing control. The argument is a - * [String] with a JSON-encoded object with seven keys, as - * obtained from [TextEditingValue.toJSON]. - * See [TextInputConnection.setEditingState]. - * - */ - - state = &object->json_arg; - - if (state->type != kJsonObject) { - return platch_respond_illegal_arg_json( - responsehandle, - "Expected decoded text editing value to be an Object" - ); - } - char *text; - int selection_base, selection_extent, composing_base, composing_extent; - bool selection_affinity_is_downstream, selection_is_directional; + // INPUT TYPE + temp = jsobject_get(config, "inputType"); + if (temp == NULL || temp->type != kJsonObject) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputType']` to be a map." + ); + } - temp = jsobject_get(state, "text"); - if (temp && (temp->type == kJsonString)) text = temp->string_value; - else goto invalid_editing_value; + temp2 = jsobject_get(temp, "signed"); + if (temp2 == NULL || temp2->type == kJsonNull) { + has_allow_signs = false; + } else if (temp2->type == kJsonTrue || temp2->type == kJsonFalse) { + has_allow_signs = true; + allow_signs = temp2->type == kJsonTrue; + } else { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputType']['signed']` to be a boolean or null." + ); + } - temp = jsobject_get(state, "selectionBase"); - if (temp && (temp->type == kJsonNumber)) selection_base = (int) temp->number_value; - else goto invalid_editing_value; + temp2 = jsobject_get(temp, "decimal"); + if (temp2 == NULL || temp2->type == kJsonNull) { + has_allow_decimal = false; + } else if (temp2->type == kJsonTrue || temp2->type == kJsonFalse) { + has_allow_decimal = true; + allow_signs = temp2->type == kJsonTrue; + } else { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputType']['decimal']` to be a boolean or null." + ); + } - temp = jsobject_get(state, "selectionExtent"); - if (temp && (temp->type == kJsonNumber)) selection_extent = (int) temp->number_value; - else goto invalid_editing_value; + temp2 = jsobject_get(temp, "name"); + if (temp2 == NULL || temp2->type != kJsonString) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputType']['name']` to be a string-ification of `TextInputType`." + ); + } - temp = jsobject_get(state, "selectionAffinity"); - if (temp && (temp->type == kJsonString)) { - if STREQ("TextAffinity.downstream", temp->string_value) { - selection_affinity_is_downstream = true; - } else if STREQ("TextAffinity.upstream", temp->string_value) { - selection_affinity_is_downstream = false; - } else { - goto invalid_editing_value; - } - } else { - goto invalid_editing_value; + if STREQ("TextInputType.text", temp2->string_value) { + input_type = kInputTypeText; + } else if STREQ("TextInputType.multiline", temp2->string_value) { + input_type = kInputTypeMultiline; + } else if STREQ("TextInputType.number", temp2->string_value) { + input_type = kInputTypeNumber; + } else if STREQ("TextInputType.phone", temp2->string_value) { + input_type = kInputTypePhone; + } else if STREQ("TextInputType.datetime", temp2->string_value) { + input_type = kInputTypeDatetime; + } else if STREQ("TextInputType.emailAddress", temp2->string_value) { + input_type = kInputTypeEmailAddress; + } else if STREQ("TextInputType.url", temp2->string_value) { + input_type = kInputTypeUrl; + } else if STREQ("TextInputType.visiblePassword", temp2->string_value) { + input_type = kInputTypeVisiblePassword; + } else if STREQ("TextInputType.name", temp2->string_value) { + input_type = kInputTypeName; + } else if STREQ("TextInputType.address", temp2->string_value) { + input_type = kInputTypeAddress; + } else { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg[1]['inputType']['name']` to be a string-ification of `TextInputType`." + ); + } + + // TRANSACTION ID + int32_t new_id = (int32_t) object->json_arg.array[0].number_value; + + // everything okay, apply the new text editing config + text_input.connection_id = new_id; + text_input.autocorrect = autocorrect; + text_input.input_action = input_action; + text_input.input_type = input_type; + + if (autocorrect && !text_input.warned_about_autocorrect) { + printf("[text_input] warning: flutter requested native autocorrect, which" + "is not supported by flutter-pi.\n"); + text_input.warned_about_autocorrect = true; + } + + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} } + ); +} - temp = jsobject_get(state, "selectionIsDirectional"); - if (temp && (temp->type == kJsonTrue || temp->type == kJsonFalse)) { - selection_is_directional = temp->type == kJsonTrue; - } else { - goto invalid_editing_value; +static int on_hide( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + /* + * TextInput.hide() + * Hide the keyboard. Unlike the other methods, this can be called + * at any time. See [TextInputConnection.close]. + * + */ + + // do nothing since we use a physical keyboard. + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} } + ); +} - temp = jsobject_get(state, "composingBase"); - if (temp && (temp->type == kJsonNumber)) composing_base = (int) temp->number_value; - else goto invalid_editing_value; +static int on_clear_client( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + /* + * TextInput.clearClient() + * End the current transaction. The next method called must be + * `TextInput.setClient` (or `TextInput.hide`). + * See [TextInputConnection.close]. + * + */ + + text_input.connection_id = -1; + + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); +} - temp = jsobject_get(state, "composingExtent"); - if (temp && (temp->type == kJsonNumber)) composing_extent = (int) temp->number_value; - else goto invalid_editing_value; +static int on_set_editing_state( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + struct json_value jsvalue, *temp, *temp2, *state, *config; + char *text; + bool selection_affinity_is_downstream, selection_is_directional; + int selection_base, selection_extent, composing_base, composing_extent; + int ok; + /* + * TextInput.setEditingState(Map textEditingValue) + * Update the value in the text editing control. The argument is a + * [String] with a JSON-encoded object with seven keys, as + * obtained from [TextEditingValue.toJSON]. + * See [TextInputConnection.setEditingState]. + * + */ - snprintf(text_input.text, sizeof(text_input.text), "%s", text); - text_input.selection_base = selection_base; - text_input.selection_extent = selection_extent; - text_input.selection_affinity_is_downstream = selection_affinity_is_downstream; - text_input.selection_is_directional = selection_is_directional; - text_input.composing_base = composing_base; - text_input.composing_extent = composing_extent; + state = &object->json_arg; - return platch_respond( + if (state->type != kJsonObject) { + return platch_respond_illegal_arg_json( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + "Expected `arg` to be a map." ); + } + + temp = jsobject_get(state, "text"); + if (temp == NULL || temp->type != kJsonString) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg['text']` to be a string." + ); + } else { + text = temp->string_value; + } - invalid_editing_value: + temp = jsobject_get(state, "selectionBase"); + if (temp == NULL || temp->type != kJsonNumber) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg['selectionBase']` to be a number." + ); + } else { + selection_base = (int) temp->number_value; + } + + temp = jsobject_get(state, "selectionExtent"); + if (temp == NULL || temp->type != kJsonNumber) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg['selectionExtent']` to be a number." + ); + } else { + selection_extent = (int) temp->number_value; + } + + temp = jsobject_get(state, "selectionAffinity"); + if (temp == NULL || temp->type != kJsonString) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg['selectionAffinity']` to be a string-ification of `TextAffinity`." + ); + } else { + if STREQ("TextAffinity.downstream", temp->string_value) { + selection_affinity_is_downstream = true; + } else if STREQ("TextAffinity.upstream", temp->string_value) { + selection_affinity_is_downstream = false; + } else { return platch_respond_illegal_arg_json( responsehandle, - "Expected decoded text editing value to be a valid" - " JSON representation of a text editing value" + "Expected `arg['selectionAffinity']` to be a string-ification of `TextAffinity`." ); + } + } - } else if STREQ("TextInput.clearClient", object->method) { - /* - * TextInput.clearClient() - * End the current transaction. The next method called must be - * `TextInput.setClient` (or `TextInput.hide`). - * See [TextInputConnection.close]. - * - */ - - text_input.transaction_id = -1; + temp = jsobject_get(state, "selectionIsDirectional"); + if (temp == NULL || (temp->type != kJsonTrue && temp->type != kJsonFalse)) { + return platch_respond_illegal_arg_json( + responsehandle, + "Expected `arg['selectionIsDirectional']` to be a bool." + ); + } else { + selection_is_directional = temp->type == kJsonTrue; + } - return platch_respond( + temp = jsobject_get(state, "composingBase"); + if (temp == NULL || temp->type != kJsonNumber) { + return platch_respond_illegal_arg_json( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + "Expected `arg['composingBase']` to be a number." ); - } else if STREQ("TextInput.hide", object->method) { - /* - * TextInput.hide() - * Hide the keyboard. Unlike the other methods, this can be called - * at any time. See [TextInputConnection.close]. - * - */ - - // do nothing since we use a physical keyboard. - return platch_respond( + } else { + composing_base = (int) temp->number_value; + } + + temp = jsobject_get(state, "composingExtent"); + if (temp == NULL || temp->type != kJsonNumber) { + return platch_respond_illegal_arg_json( responsehandle, - &(struct platch_obj) { - .codec = kJSONMethodCallResponse, - .success = true, - .json_result = {.type = kJsonNull} - } + "Expected `arg['composingExtent']` to be a number." ); + } else { + composing_extent = (int) temp->number_value; } - return platch_respond_not_implemented(responsehandle); + strncpy(text_input.text, text, TEXT_INPUT_MAX_CHARS); + text_input.selection_base = selection_base; + text_input.selection_extent = selection_extent; + text_input.selection_affinity_is_downstream = selection_affinity_is_downstream; + text_input.selection_is_directional = selection_is_directional; + text_input.composing_base = composing_base; + text_input.composing_extent = composing_extent; + + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); } +static int on_show( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + /* + * TextInput.show() + * Show the keyboard. See [TextInputConnection.show]. + * + */ + + // do nothing since we use a physical keyboard. + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); +} -int textin_sync_editing_state() { - return platch_send( - TEXT_INPUT_CHANNEL, +static int on_request_autofill( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); +} + +static int on_set_editable_size_and_transform( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); +} + +static int on_set_style( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + return platch_respond( + responsehandle, + &(struct platch_obj) { + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); +} + +static int on_finish_autofill_context( + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + return platch_respond( + responsehandle, &(struct platch_obj) { - .codec = kJSONMethodCall, - .method = "TextInputClient.updateEditingState", - .json_arg = { - .type = kJsonArray, - .size = 2, - .array = (struct json_value[2]) { - {.type = kJsonNumber, .number_value = text_input.transaction_id}, - {.type = kJsonObject, .size = 7, - .keys = (char*[7]) { - "text", "selectionBase", "selectionExtent", "selectionAffinity", - "selectionIsDirectional", "composingBase", "composingExtent" + .codec = kJSONMethodCallResponse, + .success = true, + .json_result = {.type = kJsonNull} + } + ); +} + +static int on_receive( + char *channel, + struct platch_obj *object, + FlutterPlatformMessageResponseHandle *responsehandle +) { + if STREQ("TextInput.setClient", object->method) { + return on_set_client(object, responsehandle); + } else if STREQ("TextInput.hide", object->method) { + return on_hide(object, responsehandle); + } else if STREQ("TextInput.clearClient", object->method) { + return on_clear_client(object, responsehandle); + } else if STREQ("TextInput.setEditingState", object->method) { + return on_set_editing_state(object, responsehandle); + } else if STREQ("TextInput.show", object->method) { + return on_show(object, responsehandle); + } else if STREQ("TextInput.requestAutofill", object->method) { + return on_request_autofill(object, responsehandle); + } else if STREQ("TextInput.setEditableSizeAndTransform", object->method) { + return on_set_style(object, responsehandle); + } else if STREQ("TextInput.setStyle", object->method) { + return on_set_style(object, responsehandle); + } else if STREQ("TextInput.finishAutofillContext", object->method) { + return on_finish_autofill_context(object, responsehandle); + } + + return platch_respond_not_implemented(responsehandle); +} + +static int client_update_editing_state( + double connection_id, + char *text, + double selection_base, + double selection_extent, + bool selection_affinity_is_downstream, + bool selection_is_directional, + double composing_base, + double composing_extent +) { + return platch_call_json( + TEXT_INPUT_CHANNEL, + "TextInputClient.updateEditingState", + &(struct json_value) { + .type = kJsonArray, + .size = 2, + .array = (struct json_value[2]) { + {.type = kJsonNumber, .number_value = connection_id}, + {.type = kJsonObject, .size = 7, + .keys = (char*[7]) { + "text", "selectionBase", "selectionExtent", "selectionAffinity", + "selectionIsDirectional", "composingBase", "composingExtent" + }, + .values = (struct json_value[7]) { + {.type = kJsonString, .string_value = text}, + {.type = kJsonNumber, .number_value = selection_base}, + {.type = kJsonNumber, .number_value = selection_extent}, + { + .type = kJsonString, + .string_value = selection_affinity_is_downstream ? + "TextAffinity.downstream" : "TextAffinity.upstream" }, - .values = (struct json_value[7]) { - {.type = kJsonString, .string_value = text_input.text}, - {.type = kJsonNumber, .number_value = text_input.selection_base}, - {.type = kJsonNumber, .number_value = text_input.selection_extent}, - { - .type = kJsonString, - .string_value = text_input.selection_affinity_is_downstream ? - "TextAffinity.downstream" : "TextAffinity.upstream" - }, - {.type = text_input.selection_is_directional? kJsonTrue : kJsonFalse}, - {.type = kJsonNumber, .number_value = text_input.composing_base}, - {.type = kJsonNumber, .number_value = text_input.composing_extent} - } + {.type = selection_is_directional? kJsonTrue : kJsonFalse}, + {.type = kJsonNumber, .number_value = composing_base}, + {.type = kJsonNumber, .number_value = composing_extent} } } } }, - kJSONMethodCallResponse, NULL, NULL ); } -int textin_perform_action(enum text_input_action action) { - +int client_perform_action( + double connection_id, + enum text_input_action action +) { char *action_str = (action == kTextInputActionNone) ? "TextInputAction.none" : (action == kTextInputActionUnspecified) ? "TextInputAction.unspecified" : @@ -380,85 +615,195 @@ int textin_perform_action(enum text_input_action action) { (action == kTextInputActionEmergencyCall) ? "TextInputAction.emergencyCall" : "TextInputAction.newline"; - return platch_send( + return platch_call_json( TEXT_INPUT_CHANNEL, - &(struct platch_obj) { - .codec = kJSONMethodCall, - .method = "TextInputClient.performAction", - .json_arg = { - .type = kJsonArray, - .size = 2, - .array = (struct json_value[2]) { - {.type = kJsonNumber, .number_value = text_input.transaction_id}, - {.type = kJsonString, .string_value = action_str} + "TextInputClient.performAction", + &(struct json_value) { + .type = kJsonArray, + .size = 2, + .array = (struct json_value[2]) { + {.type = kJsonNumber, .number_value = connection_id}, + {.type = kJsonString, .string_value = action_str} + } + }, + NULL, + NULL + ); +} + +int client_perform_private_command( + double connection_id, + char *action, + struct json_value *data +) { + if (data != NULL && data->type != kJsonNull && data->type != kJsonObject) { + return EINVAL; + } + + return platch_call_json( + TEXT_INPUT_CHANNEL, + "TextInputClient.performPrivateCommand", + &(struct json_value) { + .type = kJsonArray, + .size = 2, + .array = (struct json_value[2]) { + {.type = kJsonNumber, .number_value = connection_id}, + { + .type = kJsonObject, + .size = 2, + .keys = (char*[2]) { + "action", + "data" + }, + .values = (struct json_value[2]) { + {.type = kJsonString, .string_value = action}, + *data + } } } }, - 0, NULL, NULL + NULL, + NULL ); } -int textin_on_connection_closed(void) { - text_input.transaction_id = -1; +int client_update_floating_cursor( + double connection_id, + enum floating_cursor_drag_state text_cursor_action, + double x, + double y +) { + return platch_call_json( + TEXT_INPUT_CHANNEL, + "TextInputClient.updateFloatingCursor", + &(struct json_value) { + .type = kJsonArray, + .size = 3, + .array = (struct json_value[3]) { + {.type = kJsonNumber, .number_value = connection_id}, + { + .type = kJsonString, + .string_value = text_cursor_action == kFloatingCursorDragStateStart ? "FloatingCursorDragState.start" : + text_cursor_action == kFloatingCursorDragStateUpdate ? "FloatingCursorDragState.update" : + "FloatingCursorDragState.end" + }, + { + .type = kJsonObject, + .size = 2, + .keys = (char*[2]) { + "X", + "Y" + }, + .values = (struct json_value[2]) { + {.type = kJsonNumber, .number_value = x}, + {.type = kJsonNumber, .number_value = y} + } + } + } + }, + NULL, + NULL + ); +} - return platch_send( +int client_on_connection_closed(double connection_id) { + return platch_call_json( TEXT_INPUT_CHANNEL, - &(struct platch_obj) { - .codec = kJSONMethodCall, - .method = "TextInputClient.onConnectionClosed", - .json_arg = {.type = kJsonNull} + "TextInputClient.onConnectionClosed", + &(struct json_value) { + .type = kJsonArray, + .size = 1, + .array = (struct json_value[1]) { + {.type = kJsonNumber, .number_value = connection_id} + } }, - kBinaryCodec, NULL, NULL + NULL, + NULL ); } -inline int to_byte_index(unsigned int symbol_index) { - char *cursor = text_input.text; +int client_show_autocorrection_prompt_rect( + double connection_id, + double start, + double end +) { + return platch_call_json( + TEXT_INPUT_CHANNEL, + "TextInputClient.showAutocorrectionPromptRect", + &(struct json_value) { + .type = kJsonArray, + .size = 3, + .array = (struct json_value[3]) { + {.type = kJsonNumber, .number_value = connection_id}, + {.type = kJsonNumber, .number_value = start}, + {.type = kJsonNumber, .number_value = end} + } + }, + NULL, + NULL + ); +} - while ((*cursor) && (symbol_index--)) - cursor += utf8_symbol_length(cursor); +/** + * Text Input Model functions. + */ - if (*cursor) - return cursor - text_input.text; +static inline int min(int a, int b) { + return a < b? a : b; +} - return -1; +static inline int max(int a, int b) { + return a > b? a : b; +} + +static inline int selection_start(void) { + return min(text_input.selection_base, text_input.selection_extent); +} + +static inline int selection_end(void) { + return max(text_input.selection_base, text_input.selection_extent); } -// start and end index are both inclusive. -int textin_erase(unsigned int start, unsigned int end) { +/** + * Erases the characters between `start` and `end` (both inclusive) and returns + * `start`. + */ +static int model_erase(unsigned int start, unsigned int end) { // 0 <= start <= end < len - char *start_str = utf8_symbol_at(text_input.text, start); - char *after_end_str = utf8_symbol_at(text_input.text, end+1); + char *start_str = symbol_at(start); + char *after_end_str = symbol_at(end+1); if (start_str && after_end_str) memmove(start_str, after_end_str, strlen(after_end_str) + 1 /* null byte */); return start; } -bool textin_delete_selected(void) { + +static bool model_delete_selected(void) { // erase selected text - text_input.selection_base = textin_erase(text_input.selection_base, text_input.selection_extent-1); + text_input.selection_base = model_erase(selection_start(), selection_end()-1); text_input.selection_extent = text_input.selection_base; return true; } -bool textin_add_utf8_char(char *c) { + +static bool model_add_utf8_char(uint8_t *c) { size_t symbol_length; - char *to_move; + uint8_t *to_move; if (text_input.selection_base != text_input.selection_extent) - textin_delete_selected(); + model_delete_selected(); // find out where in our string we need to insert the utf8 symbol - symbol_length = utf8_symbol_length(c); - to_move = utf8_symbol_at(text_input.text, text_input.selection_base); + symbol_length = utf8_symbol_length(*c); + to_move = symbol_at(text_input.selection_base); if (!to_move || !symbol_length) return false; // move the string behind the insertion position to - // make place for the utf8 character + // make place for the utf8 charactercursor memmove(to_move + symbol_length, to_move, strlen(to_move) + 1 /* null byte */); @@ -473,32 +818,35 @@ bool textin_add_utf8_char(char *c) { return true; } -bool textin_backspace(void) { + +static bool model_backspace(void) { if (text_input.selection_base != text_input.selection_extent) - return textin_delete_selected(); + return model_delete_selected(); if (text_input.selection_base != 0) { int base = text_input.selection_base - 1; - text_input.selection_base = textin_erase(base, base); + text_input.selection_base = model_erase(base, base); text_input.selection_extent = text_input.selection_base; return true; } return false; } -bool textin_delete(void) { + +static bool model_delete(void) { if (text_input.selection_base != text_input.selection_extent) - return textin_delete_selected(); + return model_delete_selected(); - if (text_input.selection_base < strlen(text_input.text)) { - text_input.selection_base = textin_erase(text_input.selection_base, text_input.selection_base); + if (selection_start() < strlen(text_input.text)) { + text_input.selection_base = model_erase(selection_start(), selection_end()); text_input.selection_extent = text_input.selection_base; return true; } return false; } -bool textin_move_cursor_to_beginning(void) { + +static bool model_move_cursor_to_beginning(void) { if ((text_input.selection_base != 0) || (text_input.selection_extent != 0)) { text_input.selection_base = 0; text_input.selection_extent = 0; @@ -507,8 +855,9 @@ bool textin_move_cursor_to_beginning(void) { return false; } -bool textin_move_cursor_to_end(void) { - int end = strlen(text_input.text); + +static bool model_move_cursor_to_end(void) { + int end = to_symbol_index(strlen(text_input.text)); if (text_input.selection_base != end) { text_input.selection_base = end; @@ -518,13 +867,14 @@ bool textin_move_cursor_to_end(void) { return false; } -bool textin_move_cursor_forward(void) { + +static bool model_move_cursor_forward(void) { if (text_input.selection_base != text_input.selection_extent) { text_input.selection_base = text_input.selection_extent; return true; } - if (text_input.selection_extent < strlen(text_input.text)) { + if (text_input.selection_extent < to_symbol_index(strlen(text_input.text))) { text_input.selection_extent++; text_input.selection_base++; return true; @@ -532,7 +882,8 @@ bool textin_move_cursor_forward(void) { return false; } -bool textin_move_cursor_back(void) { + +static bool model_move_cursor_back(void) { if (text_input.selection_base != text_input.selection_extent) { text_input.selection_extent = text_input.selection_base; return true; @@ -548,64 +899,104 @@ bool textin_move_cursor_back(void) { } -// these two functions automatically sync the editing state with flutter if -// a change ocurred, so you don't explicitly need to call textin_sync_editing_state(). -// `c` doesn't need to be NULL-terminated, the length of the char will be calculated -// using the start byte. -int textin_on_utf8_char(char *c) { - if (text_input.transaction_id == -1) + +static int sync_editing_state(void) { + return client_update_editing_state( + text_input.connection_id, + text_input.text, + text_input.selection_base, + text_input.selection_extent, + text_input.selection_affinity_is_downstream, + text_input.selection_is_directional, + text_input.composing_base, + text_input.composing_extent + ); +} + +/** + * `c` doesn't need to be NULL-terminated, the length of the char will be calculated + * using the start byte. + */ +int textin_on_utf8_char(uint8_t *c) { + if (text_input.connection_id == -1) return 0; + + switch (text_input.input_type) { + case kInputTypeNumber: + if (isdigit(*c)) { + break; + } else { + return 0; + } + case kInputTypePhone: + if (isdigit(*c) || *c == '*' || *c == '#' || *c == '+') { + break; + } else { + return 0; + } + default: + break; + } - if (textin_add_utf8_char(c)) - return textin_sync_editing_state(); + if (model_add_utf8_char(c)) + return sync_editing_state(); return 0; } -int textin_on_key(glfw_key key) { +int textin_on_xkb_keysym(xkb_keysym_t keysym) { bool needs_sync = false; bool perform_action = false; int ok; - if (text_input.transaction_id == -1) + if (text_input.connection_id == -1) return 0; - switch (key) { - case GLFW_KEY_LEFT: - needs_sync = textin_move_cursor_back(); - break; - case GLFW_KEY_RIGHT: - needs_sync = textin_move_cursor_forward(); - break; - case GLFW_KEY_END: - needs_sync = textin_move_cursor_to_end(); - break; - case GLFW_KEY_HOME: - needs_sync = textin_move_cursor_to_beginning(); + switch (keysym) { + case XKB_KEY_BackSpace: + needs_sync = model_backspace(); break; - case GLFW_KEY_BACKSPACE: - needs_sync = textin_backspace(); + case XKB_KEY_Delete: + case XKB_KEY_KP_Delete: + needs_sync = model_delete(); break; - case GLFW_KEY_DELETE: - needs_sync = textin_delete(); + case XKB_KEY_End: + case XKB_KEY_KP_End: + needs_sync = model_move_cursor_to_end(); break; - case GLFW_KEY_ENTER: + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + case XKB_KEY_ISO_Enter: if (text_input.input_type == kInputTypeMultiline) - needs_sync = textin_add_utf8_char("\n"); + needs_sync = model_add_utf8_char("\n"); perform_action = true; break; + case XKB_KEY_Home: + case XKB_KEY_KP_Home: + needs_sync = model_move_cursor_to_beginning(); + break; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + // handled inside of flutter + // needs_sync = model_move_cursor_back(); + break; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + // handled inside of flutter + // needs_sync = model_move_cursor_forward(); + break; default: break; } if (needs_sync) { - ok = textin_sync_editing_state(); + ok = sync_editing_state(); if (ok != 0) return ok; } if (perform_action) { - ok = textin_perform_action(text_input.input_action); + ok = client_perform_action(text_input.connection_id, text_input.input_action); if (ok != 0) return ok; } @@ -619,7 +1010,7 @@ int textin_init(void) { text_input.text[0] = '\0'; text_input.warned_about_autocorrect = false; - ok = plugin_registry_set_receiver(TEXT_INPUT_CHANNEL, kJSONMethodCall, textin_on_receive); + ok = plugin_registry_set_receiver(TEXT_INPUT_CHANNEL, kJSONMethodCall, on_receive); if (ok != 0) return ok; return 0; From 60137c83bd3c2aa2a30f36b0da5e489b689f82cb Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 11 Oct 2020 22:31:08 +0200 Subject: [PATCH 08/15] reimplement legacy modesetting --- include/compositor.h | 17 ++ include/modesetting.h | 60 ++++++ src/compositor.c | 442 ++++++++++++++++++++++++++++++++++-------- src/flutter-pi.c | 1 + src/modesetting.c | 230 ++++++++++++++++++++++ 5 files changed, 674 insertions(+), 76 deletions(-) diff --git a/include/compositor.h b/include/compositor.h index 8555e053..84aa40c8 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -126,6 +126,12 @@ struct compositor { * like usual. */ bool do_blocking_atomic_commits; + + /** + * @brief Whether atomic modesetting should be used. + * If `false`, legacy modesetting is used. + */ + bool use_atomic_modesetting; }; /* @@ -221,6 +227,17 @@ struct rendertarget { int height, int zpos ); + int (*present_legacy)( + struct rendertarget *target, + struct drmdev *drmdev, + uint32_t drm_plane_id, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + bool set_mode + ); }; struct flutterpi_backing_store { diff --git a/include/modesetting.h b/include/modesetting.h index bffbd3ab..272e0e9a 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -88,6 +88,11 @@ int drmdev_configure( const drmModeModeInfo *mode ); +int drmdev_plane_get_type( + struct drmdev *drmdev, + uint32_t plane_id +); + int drmdev_plane_supports_setting_rotation_value( struct drmdev *drmdev, uint32_t plane_id, @@ -166,6 +171,61 @@ int drmdev_atomic_req_commit( void *userdata ); +int drmdev_legacy_set_mode_and_fb( + struct drmdev *drmdev, + uint32_t fb_id +); + +/** + * @brief Do a nonblocking, vblank-synced framebuffer swap. + */ +int drmdev_legacy_primary_plane_pageflip( + struct drmdev *drmdev, + uint32_t fb_id, + void *userdata +); + +/** + * @brief Do a blocking, vblank-synced framebuffer swap. + * Using this in combination with @ref drmdev_legacy_primary_plane_pageflip + * is not a good idea, since it will block until the primary plane pageflip is complete, + * and then block even longer till the overlay plane pageflip completes the vblank after. + */ +int drmdev_legacy_overlay_plane_pageflip( + struct drmdev *drmdev, + uint32_t plane_id, + uint32_t fb_id, + int32_t crtc_x, + int32_t crtc_y, + int32_t crtc_w, + int32_t crtc_h, + uint32_t src_x, + uint32_t src_y, + uint32_t src_w, + uint32_t src_h +); + +int drmdev_legacy_set_connector_property( + struct drmdev *drmdev, + const char *name, + uint64_t value +); + +int drmdev_legacy_set_crtc_property( + struct drmdev *drmdev, + const char *name, + uint64_t value +); + +int drmdev_legacy_set_plane_property( + struct drmdev *drmdev, + uint32_t plane_id, + const char *name, + uint64_t value +); + + + inline static struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { bool found = connector == NULL; for (int i = 0; i < drmdev->n_connectors; i++) { diff --git a/src/compositor.c b/src/compositor.c index d8c17517..69228f5a 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -50,7 +50,8 @@ struct compositor compositor = { .has_applied_modeset = false, .should_create_window_surface_backing_store = true, .stale_rendertargets = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), - .do_blocking_atomic_commits = false + .do_blocking_atomic_commits = false, + .use_atomic_modesetting = false }; static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { @@ -417,6 +418,105 @@ static int rendertarget_gbm_present( return 0; } +static int rendertarget_gbm_present_legacy( + struct rendertarget *target, + struct drmdev *drmdev, + uint32_t drm_plane_id, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + bool set_mode +) { + struct rendertarget_gbm *gbm_target; + struct gbm_bo *next_front_bo; + uint32_t next_front_fb_id; + bool supported, is_primary; + int ok; + + gbm_target = &target->gbm; + + is_primary = drmdev_plane_get_type(drmdev, drm_plane_id) == DRM_PLANE_TYPE_PRIMARY; + + next_front_bo = gbm_surface_lock_front_buffer(gbm_target->gbm_surface); + next_front_fb_id = gbm_bo_get_drm_fb_id(next_front_bo); + + if (is_primary) { + if (set_mode) { + drmdev_legacy_set_mode_and_fb( + drmdev, + next_front_fb_id + ); + } else { + drmdev_legacy_primary_plane_pageflip( + drmdev, + next_front_fb_id, + NULL + ); + } + } else { + drmdev_legacy_overlay_plane_pageflip( + drmdev, + drm_plane_id, + next_front_fb_id, + 0, + 0, + flutterpi.display.width, + flutterpi.display.height, + 0, + 0, + ((uint16_t) flutterpi.display.width) << 16, + ((uint16_t) flutterpi.display.height) << 16 + ); + } + + ok = drmdev_plane_supports_setting_rotation_value(drmdev, drm_plane_id, DRM_MODE_ROTATE_0, &supported); + if (ok != 0) return ok; + + if (supported) { + drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "rotation", DRM_MODE_ROTATE_0); + } else { + static bool printed = false; + + if (!printed) { + fprintf(stderr, + "[compositor] GPU does not support reflecting the screen in Y-direction.\n" + " This is required for rendering into hardware overlay planes though.\n" + " Any UI that is drawn in overlay planes will look upside down.\n" + ); + printed = true; + } + } + + ok = drmdev_plane_supports_setting_zpos_value(drmdev, drm_plane_id, zpos, &supported); + if (ok != 0) return ok; + + if (supported) { + drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "zpos", zpos); + } else { + static bool printed = false; + + if (!printed) { + fprintf(stderr, + "[compositor] GPU does not supported the desired HW plane order.\n" + " Some UI layers may be invisible.\n" + ); + printed = true; + } + } + + // TODO: move this to the page flip handler. + // We can only be sure the buffer can be released when the buffer swap + // ocurred. + if (gbm_target->current_front_bo != NULL) { + gbm_surface_release_buffer(gbm_target->gbm_surface, gbm_target->current_front_bo); + } + gbm_target->current_front_bo = (struct gbm_bo *) next_front_bo; + + return 0; +} + /** * @brief Create a type of rendertarget that is backed by a GBM Surface, used for rendering into the DRM primary plane. * @@ -447,7 +547,8 @@ static int rendertarget_gbm_new( }, .gl_fbo_id = 0, .destroy = rendertarget_gbm_destroy, - .present = rendertarget_gbm_present + .present = rendertarget_gbm_present, + .present_legacy = rendertarget_gbm_present_legacy }; *out = target; @@ -531,6 +632,99 @@ static int rendertarget_nogbm_present( return 0; } +static int rendertarget_nogbm_present_legacy( + struct rendertarget *target, + struct drmdev *drmdev, + uint32_t drm_plane_id, + int offset_x, + int offset_y, + int width, + int height, + int zpos, + bool set_mode +) { + struct rendertarget_nogbm *nogbm_target; + uint32_t fb_id; + bool supported, is_primary; + int ok; + + nogbm_target = &target->nogbm; + + is_primary = drmdev_plane_get_type(drmdev, drm_plane_id) == DRM_PLANE_TYPE_PRIMARY; + + nogbm_target->current_front_rbo ^= 1; + ok = attach_drm_rbo_to_fbo(nogbm_target->gl_fbo_id, nogbm_target->rbos + nogbm_target->current_front_rbo); + if (ok != 0) return ok; + + fb_id = nogbm_target->rbos[nogbm_target->current_front_rbo ^ 1].drm_fb_id; + + if (is_primary) { + if (set_mode) { + drmdev_legacy_set_mode_and_fb( + drmdev, + fb_id + ); + } else { + drmdev_legacy_primary_plane_pageflip( + drmdev, + fb_id, + NULL + ); + } + } else { + drmdev_legacy_overlay_plane_pageflip( + drmdev, + drm_plane_id, + fb_id, + 0, + 0, + flutterpi.display.width, + flutterpi.display.height, + 0, + 0, + ((uint16_t) flutterpi.display.width) << 16, + ((uint16_t) flutterpi.display.height) << 16 + ); + } + + ok = drmdev_plane_supports_setting_rotation_value(drmdev, drm_plane_id, DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y, &supported); + if (ok != 0) return ok; + + if (supported) { + drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "rotation", DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y); + } else { + static bool printed = false; + + if (!printed) { + fprintf(stderr, + "[compositor] GPU does not support reflecting the screen in Y-direction.\n" + " This is required for rendering into hardware overlay planes though.\n" + " Any UI that is drawn in overlay planes will look upside down.\n" + ); + printed = true; + } + } + + ok = drmdev_plane_supports_setting_zpos_value(drmdev, drm_plane_id, zpos, &supported); + if (ok != 0) return ok; + + if (supported) { + drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "zpos", zpos); + } else { + static bool printed = false; + + if (!printed) { + fprintf(stderr, + "[compositor] GPU does not supported the desired HW plane order.\n" + " Some UI layers may be invisible.\n" + ); + printed = true; + } + } + + return 0; +} + /** * @brief Create a type of rendertarget that is not backed by a GBM-Surface, used for rendering into DRM overlay planes. * @@ -557,6 +751,7 @@ static int rendertarget_nogbm_new( target->compositor = compositor; target->destroy = rendertarget_nogbm_destroy; target->present = rendertarget_nogbm_present; + target->present_legacy = rendertarget_nogbm_present_legacy; eglGetError(); glGetError(); @@ -784,14 +979,30 @@ static bool on_present_layers( ) { struct drmdev_atomic_req *req; struct view_cb_data *cb_data; + struct pointer_set planes; struct compositor *compositor; struct drm_plane *plane; + struct drmdev *drmdev; uint32_t req_flags; + void *planes_storage[32] = {0}; + bool legacy_rendertarget_set_mode = false; + bool schedule_fake_page_flip_event; int ok; compositor = userdata; + drmdev = compositor->drmdev; + schedule_fake_page_flip_event = compositor->do_blocking_atomic_commits; - drmdev_new_atomic_req(compositor->drmdev, &req); + if (compositor->use_atomic_modesetting) { + drmdev_new_atomic_req(compositor->drmdev, &req); + } else { + planes = PSET_INITIALIZER_STATIC(planes_storage, 32); + for_each_plane_in_drmdev(drmdev, plane) { + if (plane->plane->possible_crtcs & drmdev->selected_crtc->bitmask) { + pset_put(&planes, plane); + } + } + } cpset_lock(&compositor->cbs); @@ -800,34 +1011,68 @@ static bool on_present_layers( req_flags = 0 /* DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK*/; if (compositor->has_applied_modeset == false) { - ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); - if (ok != 0) return false; + if (compositor->use_atomic_modesetting) { + ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); + if (ok != 0) return false; + } else { + legacy_rendertarget_set_mode = true; + schedule_fake_page_flip_event = true; + } int64_t max_zpos = 0; - for_each_unreserved_plane_in_atomic_req(req, plane) { - if (plane->type == DRM_PLANE_TYPE_CURSOR) { - // make sure the cursor is in front of everything - int64_t max_zpos; - bool supported; + if (compositor->use_atomic_modesetting) { + for_each_unreserved_plane_in_atomic_req(req, plane) { + if (plane->type == DRM_PLANE_TYPE_CURSOR) { + // make sure the cursor is in front of everything + int64_t max_zpos; + bool supported; + + ok = drmdev_plane_get_max_zpos_value(req->drmdev, plane->plane->plane_id, &max_zpos); + if (ok != 0) { + printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); + continue; + } + + ok = drmdev_plane_supports_setting_zpos_value(req->drmdev, plane->plane->plane_id, max_zpos, &supported); + if (ok != 0) { + printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); + continue; + } - ok = drmdev_plane_get_max_zpos_value(req->drmdev, plane->plane->plane_id, &max_zpos); - if (ok != 0) { - printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); - continue; - } - - ok = drmdev_plane_supports_setting_zpos_value(req->drmdev, plane->plane->plane_id, max_zpos, &supported); - if (ok != 0) { - printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; + if (supported) { + drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "zpos", max_zpos); + } else { + printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); + continue; + } } + } + } else { + for_each_pointer_in_pset(&planes, plane) { + if (plane->type == DRM_PLANE_TYPE_CURSOR) { + // make sure the cursor is in front of everything + int64_t max_zpos; + bool supported; + + ok = drmdev_plane_get_max_zpos_value(drmdev, plane->plane->plane_id, &max_zpos); + if (ok != 0) { + printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_get_max_zpos_value: %s\n", strerror(ok)); + continue; + } + + ok = drmdev_plane_supports_setting_zpos_value(drmdev, plane->plane->plane_id, max_zpos, &supported); + if (ok != 0) { + printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); + continue; + } - if (supported) { - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "zpos", max_zpos); - } else { - printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); - continue; + if (supported) { + drmdev_legacy_set_plane_property(drmdev, plane->plane->plane_id, "zpos", max_zpos); + } else { + printf("[compositor] Could not move cursor to front. Mouse cursor may be invisible. drmdev_plane_supports_setting_zpos_value: %s\n", strerror(ok)); + continue; + } } } } @@ -983,31 +1228,56 @@ static bool on_present_layers( } int64_t min_zpos; - for_each_unreserved_plane_in_atomic_req(req, plane) { - if (plane->type == DRM_PLANE_TYPE_PRIMARY) { - ok = drmdev_plane_get_min_zpos_value(req->drmdev, plane->plane->plane_id, &min_zpos); - if (ok != 0) { - min_zpos = 0; + if (compositor->use_atomic_modesetting) { + for_each_unreserved_plane_in_atomic_req(req, plane) { + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + ok = drmdev_plane_get_min_zpos_value(req->drmdev, plane->plane->plane_id, &min_zpos); + if (ok != 0) { + min_zpos = 0; + } + break; + } + } + } else { + for_each_pointer_in_pset(&planes, plane) { + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + ok = drmdev_plane_get_min_zpos_value(drmdev, plane->plane->plane_id, &min_zpos); + if (ok != 0) { + min_zpos = 0; + } + break; } - break; } } for (int i = 0; i < layers_count; i++) { if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - for_each_unreserved_plane_in_atomic_req(req, plane) { - // choose a plane which has an "intrinsic" zpos that matches - // the zpos we want the plane to have. - // (Since planes are buggy and we can't rely on the zpos we explicitly - // configure the plane to have to be actually applied to the hardware. - // In short, assigning a different value to the zpos property won't always - // take effect.) - if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { - drmdev_atomic_req_reserve_plane(req, plane); - break; - } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - drmdev_atomic_req_reserve_plane(req, plane); - break; + if (compositor->use_atomic_modesetting) { + for_each_unreserved_plane_in_atomic_req(req, plane) { + // choose a plane which has an "intrinsic" zpos that matches + // the zpos we want the plane to have. + // (Since planes are buggy and we can't rely on the zpos we explicitly + // configure the plane to have to be actually applied to the hardware. + // In short, assigning a different value to the zpos property won't always + // take effect.) + if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { + ok = drmdev_atomic_req_reserve_plane(req, plane); + break; + } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { + ok = drmdev_atomic_req_reserve_plane(req, plane); + break; + } + } + } else { + for_each_pointer_in_pset(&planes, plane) { + if ((i == 0) && (plane->type == DRM_PLANE_TYPE_PRIMARY)) { + break; + } else if ((i != 0) && (plane->type == DRM_PLANE_TYPE_OVERLAY)) { + break; + } + } + if (plane != NULL) { + pset_remove(&planes, plane); } } if (plane == NULL) { @@ -1018,18 +1288,32 @@ static bool on_present_layers( struct flutterpi_backing_store *store = layers[i]->backing_store->user_data; struct rendertarget *target = store->target; - ok = target->present( - target, - req, - plane->plane->plane_id, - 0, - 0, - compositor->drmdev->selected_mode->hdisplay, - compositor->drmdev->selected_mode->vdisplay, - i + min_zpos - ); - if (ok != 0) { - fprintf(stderr, "[compositor] Could not present backing store. rendertarget->present: %s\n", strerror(ok)); + if (compositor->use_atomic_modesetting) { + ok = target->present( + target, + req, + plane->plane->plane_id, + 0, + 0, + compositor->drmdev->selected_mode->hdisplay, + compositor->drmdev->selected_mode->vdisplay, + i + min_zpos + ); + if (ok != 0) { + fprintf(stderr, "[compositor] Could not present backing store. rendertarget->present: %s\n", strerror(ok)); + } + } else { + ok = target->present_legacy( + target, + drmdev, + plane->plane->plane_id, + 0, + 0, + compositor->drmdev->selected_mode->hdisplay, + compositor->drmdev->selected_mode->vdisplay, + i + min_zpos, + legacy_rendertarget_set_mode && (plane->type == DRM_PLANE_TYPE_PRIMARY) + ); } } else if (layers[i]->type == kFlutterLayerContentTypePlatformView) { cb_data = get_cbs_for_view_id_locked(layers[i]->platform_view->identifier); @@ -1054,33 +1338,40 @@ static bool on_present_layers( } } - for_each_unreserved_plane_in_atomic_req(req, plane) { - if ((plane->type == DRM_PLANE_TYPE_PRIMARY) || (plane->type == DRM_PLANE_TYPE_OVERLAY)) { - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "FB_ID", 0); - drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "CRTC_ID", 0); + if (compositor->use_atomic_modesetting) { + for_each_unreserved_plane_in_atomic_req(req, plane) { + if ((plane->type == DRM_PLANE_TYPE_PRIMARY) || (plane->type == DRM_PLANE_TYPE_OVERLAY)) { + drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "FB_ID", 0); + drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "CRTC_ID", 0); + } } } eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - do_commit: - if (compositor->do_blocking_atomic_commits) { - req_flags &= ~(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); - } else { - req_flags |= DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; - } - - ok = drmdev_atomic_req_commit(req, req_flags, NULL); - if ((compositor->do_blocking_atomic_commits == false) && (ok < 0) && (errno == EBUSY)) { - printf("[compositor] Non-blocking drmModeAtomicCommit failed with EBUSY.\n" - " Future drmModeAtomicCommits will be executed blockingly.\n" - " This may have have an impact on performance.\n"); + if (compositor->use_atomic_modesetting) { + do_commit: + if (compositor->do_blocking_atomic_commits) { + req_flags &= ~(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); + } else { + req_flags |= DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT; + } + + ok = drmdev_atomic_req_commit(req, req_flags, NULL); + if ((compositor->do_blocking_atomic_commits == false) && (ok < 0) && (errno == EBUSY)) { + printf("[compositor] Non-blocking drmModeAtomicCommit failed with EBUSY.\n" + " Future drmModeAtomicCommits will be executed blockingly.\n" + " This may have have an impact on performance.\n"); + + compositor->do_blocking_atomic_commits = true; + schedule_fake_page_flip_event = true; + goto do_commit; + } - compositor->do_blocking_atomic_commits = true; - goto do_commit; + drmdev_destroy_atomic_req(req); } - if (compositor->do_blocking_atomic_commits) { + if (schedule_fake_page_flip_event) { uint64_t time = flutterpi.flutter.libflutter_engine.FlutterEngineGetCurrentTime(); struct simulated_page_flip_event_data *data = malloc(sizeof(struct simulated_page_flip_event_data)); @@ -1094,7 +1385,6 @@ static bool on_present_layers( flutterpi_post_platform_task(execute_simulate_page_flip_event, data); } - drmdev_destroy_atomic_req(req); cpset_unlock(&compositor->cbs); return true; diff --git a/src/flutter-pi.c b/src/flutter-pi.c index dfb3e969..17eb8c95 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1035,6 +1035,7 @@ void on_pageflip_event( flutterpi.flutter.libflutter_engine.FlutterEngineTraceEventInstant("pageflip"); + printf("on page flip event\n"); cqueue_lock(&flutterpi.frame_queue); diff --git a/src/modesetting.c b/src/modesetting.c index 397b9e46..6e8481e8 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -598,6 +598,18 @@ static int get_plane_property_index_by_name( return prop_index; } +int drmdev_plane_get_type( + struct drmdev *drmdev, + uint32_t plane_id +) { + struct drm_plane *plane = get_plane_by_id(drmdev, plane_id); + if (plane == NULL) { + return -1; + } + + return plane->type; +} + int drmdev_plane_supports_setting_rotation_value( struct drmdev *drmdev, uint32_t plane_id, @@ -1025,4 +1037,222 @@ int drmdev_atomic_req_commit( drmdev_unlock(req->drmdev); return 0; +} + +int drmdev_legacy_set_mode_and_fb( + struct drmdev *drmdev, + uint32_t fb_id +) { + int ok; + + drmdev_lock(drmdev); + + ok = drmModeSetCrtc( + drmdev->fd, + drmdev->selected_crtc->crtc->crtc_id, + fb_id, + 0, + 0, + &drmdev->selected_connector->connector->connector_id, + 1, + (drmModeModeInfoPtr) drmdev->selected_mode + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not set CRTC mode and framebuffer. drmModeSetCrtc"); + drmdev_unlock(drmdev); + return ok; + } + + drmdev_unlock(drmdev); + + return 0; +} + +int drmdev_legacy_primary_plane_pageflip( + struct drmdev *drmdev, + uint32_t fb_id, + void *userdata +) { + int ok; + + drmdev_lock(drmdev); + + ok = drmModePageFlip( + drmdev->fd, + drmdev->selected_crtc->crtc->crtc_id, + fb_id, + DRM_MODE_PAGE_FLIP_EVENT, + userdata + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not schedule pageflip on primary plane. drmModePageFlip"); + drmdev_unlock(drmdev); + return ok; + } + + drmdev_unlock(drmdev); + + return 0; +} + +int drmdev_legacy_overlay_plane_pageflip( + struct drmdev *drmdev, + uint32_t plane_id, + uint32_t fb_id, + int32_t crtc_x, + int32_t crtc_y, + int32_t crtc_w, + int32_t crtc_h, + uint32_t src_x, + uint32_t src_y, + uint32_t src_w, + uint32_t src_h +) { + int ok; + + drmdev_lock(drmdev); + + ok = drmModeSetPlane( + drmdev->fd, + plane_id, + drmdev->selected_crtc->crtc->crtc_id, + fb_id, + 0, + crtc_x, crtc_y, crtc_w, crtc_h, + src_x, src_y, src_w, src_h + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not do blocking pageflip on overlay plane. drmModeSetPlane"); + drmdev_unlock(drmdev); + return ok; + } + + drmdev_unlock(drmdev); + + return 0; +} + +int drmdev_legacy_set_connector_property( + struct drmdev *drmdev, + const char *name, + uint64_t value +) { + int ok; + + drmdev_lock(drmdev); + + for (int i = 0; i < drmdev->selected_connector->props->count_props; i++) { + drmModePropertyRes *prop = drmdev->selected_connector->props_info[i]; + if (strcmp(prop->name, name) == 0) { + ok = drmModeConnectorSetProperty( + drmdev->fd, + drmdev->selected_connector->connector->connector_id, + prop->prop_id, + value + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not set connector property. drmModeConnectorSetProperty"); + drmdev_unlock(drmdev); + return ok; + } + + drmdev_unlock(drmdev); + return 0; + } + } + + drmdev_unlock(drmdev); + return EINVAL; +} + +int drmdev_legacy_set_crtc_property( + struct drmdev *drmdev, + const char *name, + uint64_t value +) { + int ok; + + drmdev_lock(drmdev); + + for (int i = 0; i < drmdev->selected_crtc->props->count_props; i++) { + drmModePropertyRes *prop = drmdev->selected_crtc->props_info[i]; + if (strcmp(prop->name, name) == 0) { + ok = drmModeObjectSetProperty( + drmdev->fd, + drmdev->selected_crtc->crtc->crtc_id, + DRM_MODE_OBJECT_CRTC, + prop->prop_id, + value + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not set CRTC property. drmModeObjectSetProperty"); + drmdev_unlock(drmdev); + return ok; + } + + drmdev_unlock(drmdev); + return 0; + } + } + + drmdev_unlock(drmdev); + return EINVAL; +} + +int drmdev_legacy_set_plane_property( + struct drmdev *drmdev, + uint32_t plane_id, + const char *name, + uint64_t value +) { + struct drm_plane *plane; + int ok; + + drmdev_lock(drmdev); + + plane = NULL; + for (int i = 0; i < drmdev->n_planes; i++) { + if (drmdev->planes[i].plane->plane_id == plane_id) { + plane = drmdev->planes + i; + break; + } + } + + if (plane == NULL) { + drmdev_unlock(drmdev); + return EINVAL; + } + + for (int i = 0; i < plane->props->count_props; i++) { + drmModePropertyRes *prop; + + prop = plane->props_info[i]; + + if (strcmp(prop->name, name) == 0) { + ok = drmModeObjectSetProperty( + drmdev->fd, + plane_id, + DRM_MODE_OBJECT_PLANE, + prop->prop_id, + value + ); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not set plane property. drmModeObjectSetProperty"); + drmdev_unlock(drmdev); + return ok; + } + + drmdev_unlock(drmdev); + return 0; + } + } + + drmdev_unlock(drmdev); + return EINVAL; } \ No newline at end of file From c3efe4f9d642e7075cff8e902f58f819482e61e2 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 11 Oct 2020 22:41:26 +0200 Subject: [PATCH 09/15] update cmakelists --- CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5ea7082..1a84af76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,8 @@ pkg_check_modules(GBM REQUIRED gbm) pkg_check_modules(EGL REQUIRED egl) pkg_check_modules(GLESV2 REQUIRED glesv2) pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) -pkg_check_modules(LIBINPUT libinput) +pkg_check_modules(LIBINPUT REQUIRED libinput) +pkg_check_modules(LIBXKBCOMMON REQUIRED xkbcommon) pkg_check_modules(LIBUDEV libudev) pkg_check_modules(GPIOD libgpiod) @@ -99,12 +100,12 @@ set(FLUTTER_PI_SRC src/flutter-pi.c src/platformchannel.c src/pluginregistry.c - src/console_keyboard.c src/texture_registry.c src/compositor.c src/modesetting.c src/collection.c - src/cursor.c + src/cursor.c + src/keyboard.c src/plugins/services.c src/plugins/testplugin.c src/plugins/text_input.c @@ -136,6 +137,7 @@ target_link_libraries(flutter-pi ${LIBINPUT_LDFLAGS} ${LIBUDEV_LDFLAGS} ${GPIOD_LDFLAGS} + ${LIBXKBCOMMON_LDFLAGS} pthread dl rt m ) @@ -151,6 +153,7 @@ target_include_directories(flutter-pi PRIVATE ${LIBINPUT_INCLUDE_DIRS} ${LIBUDEV_INCLUDE_DIRS} ${GPIOD_INCLUDE_DIRS} + ${LIBXKBCOMMON_INCLUDE_DIRS} ) target_compile_options(flutter-pi PRIVATE @@ -162,6 +165,7 @@ target_compile_options(flutter-pi PRIVATE ${LIBINPUT_CFLAGS} ${LIBUDEV_CFLAGS} ${GPIOD_CFLAGS} + ${LIBXKBCOMMON_CFLAGS} -ggdb -DBUILD_TEXT_INPUT_PLUGIN -DBUILD_SPIDEV_PLUGIN From f67c4e7d16a6118cab926cad9088484391e0ab9a Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 11 Oct 2020 22:43:43 +0200 Subject: [PATCH 10/15] update readme --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 08e42dfe..e5af4488 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ ## 📰 NEWS -- the physical dimensions of the screen can now be specified via cmdline, using the `--dimensions` option. -- the layout of the engine-binaries branch has changed again. The symbolic link from `libflutter_engine.so` to the fitting `libflutter_engine.so.release` or `libflutter_engine.so.debug` is no longer needed, flutter-pi will now dynamically load the engine fitting the the runtime mode that was specified via cmdline. (if `--release` is given, flutter-pi will load `libflutter_engine.so.release`, else `libflutter_engine.so.debug`) -- flutter-pi now requires `libsystemd-dev`, `libinput-dev` and `libudev-dev` at compile-time. (`libudev-dev` is actually optional. To build without udev support, use cmake.) -- flutter-pi and the engine binaries updated for flutter 1.20. -- it's possible to run flutter-pi in AOT mode now. Instructions for that are WIP. -- `--aot` was renamed to `--release` +- flutter-pi now requires `libxkbcommon`. Install using `sudo apt install libxkbcommon-dev` # flutter-pi A light-weight Flutter Engine Embedder for Raspberry Pi. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. @@ -236,7 +231,7 @@ sudo fc-cache ``` ### libgpiod (for the included GPIO plugin), libsystemd, libinput, libudev ```bash -sudo apt-get install gpiod libgpiod-dev libsystemd-dev libinput-dev libudev-dev +sudo apt-get install gpiod libgpiod-dev libsystemd-dev libinput-dev libudev-dev libxkbcommon-dev ``` ## Compiling flutter-pi (on the Raspberry Pi) From 1bf90bfe3cb0cb33ad6c0260417959ff9168d161 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 12 Oct 2020 01:27:11 +0200 Subject: [PATCH 11/15] improve speed of legacy modesetting fix drmdev_new_from_fd failing for legacy modeset devices --- include/compositor.h | 6 ----- include/modesetting.h | 1 + src/compositor.c | 56 +++++++++---------------------------------- src/flutter-pi.c | 2 -- src/modesetting.c | 10 +++++++- 5 files changed, 21 insertions(+), 54 deletions(-) diff --git a/include/compositor.h b/include/compositor.h index 84aa40c8..37cdbd6b 100644 --- a/include/compositor.h +++ b/include/compositor.h @@ -126,12 +126,6 @@ struct compositor { * like usual. */ bool do_blocking_atomic_commits; - - /** - * @brief Whether atomic modesetting should be used. - * If `false`, legacy modesetting is used. - */ - bool use_atomic_modesetting; }; /* diff --git a/include/modesetting.h b/include/modesetting.h index 272e0e9a..d97576c2 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -38,6 +38,7 @@ struct drmdev { int fd; pthread_mutex_t mutex; + bool supports_atomic_modesetting; size_t n_connectors; struct drm_connector *connectors; diff --git a/src/compositor.c b/src/compositor.c index 69228f5a..9a22b585 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -50,8 +50,7 @@ struct compositor compositor = { .has_applied_modeset = false, .should_create_window_surface_backing_store = true, .stale_rendertargets = CPSET_INITIALIZER(CPSET_DEFAULT_MAX_SIZE), - .do_blocking_atomic_commits = false, - .use_atomic_modesetting = false + .do_blocking_atomic_commits = false }; static struct view_cb_data *get_cbs_for_view_id_locked(int64_t view_id) { @@ -471,41 +470,6 @@ static int rendertarget_gbm_present_legacy( ); } - ok = drmdev_plane_supports_setting_rotation_value(drmdev, drm_plane_id, DRM_MODE_ROTATE_0, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "rotation", DRM_MODE_ROTATE_0); - } else { - static bool printed = false; - - if (!printed) { - fprintf(stderr, - "[compositor] GPU does not support reflecting the screen in Y-direction.\n" - " This is required for rendering into hardware overlay planes though.\n" - " Any UI that is drawn in overlay planes will look upside down.\n" - ); - printed = true; - } - } - - ok = drmdev_plane_supports_setting_zpos_value(drmdev, drm_plane_id, zpos, &supported); - if (ok != 0) return ok; - - if (supported) { - drmdev_legacy_set_plane_property(drmdev, drm_plane_id, "zpos", zpos); - } else { - static bool printed = false; - - if (!printed) { - fprintf(stderr, - "[compositor] GPU does not supported the desired HW plane order.\n" - " Some UI layers may be invisible.\n" - ); - printed = true; - } - } - // TODO: move this to the page flip handler. // We can only be sure the buffer can be released when the buffer swap // ocurred. @@ -987,13 +951,15 @@ static bool on_present_layers( void *planes_storage[32] = {0}; bool legacy_rendertarget_set_mode = false; bool schedule_fake_page_flip_event; + bool use_atomic_modesetting; int ok; compositor = userdata; drmdev = compositor->drmdev; schedule_fake_page_flip_event = compositor->do_blocking_atomic_commits; + use_atomic_modesetting = drmdev->supports_atomic_modesetting; - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { drmdev_new_atomic_req(compositor->drmdev, &req); } else { planes = PSET_INITIALIZER_STATIC(planes_storage, 32); @@ -1011,7 +977,7 @@ static bool on_present_layers( req_flags = 0 /* DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK*/; if (compositor->has_applied_modeset == false) { - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { ok = drmdev_atomic_req_put_modeset_props(req, &req_flags); if (ok != 0) return false; } else { @@ -1021,7 +987,7 @@ static bool on_present_layers( int64_t max_zpos = 0; - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { for_each_unreserved_plane_in_atomic_req(req, plane) { if (plane->type == DRM_PLANE_TYPE_CURSOR) { // make sure the cursor is in front of everything @@ -1228,7 +1194,7 @@ static bool on_present_layers( } int64_t min_zpos; - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { for_each_unreserved_plane_in_atomic_req(req, plane) { if (plane->type == DRM_PLANE_TYPE_PRIMARY) { ok = drmdev_plane_get_min_zpos_value(req->drmdev, plane->plane->plane_id, &min_zpos); @@ -1252,7 +1218,7 @@ static bool on_present_layers( for (int i = 0; i < layers_count; i++) { if (layers[i]->type == kFlutterLayerContentTypeBackingStore) { - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { for_each_unreserved_plane_in_atomic_req(req, plane) { // choose a plane which has an "intrinsic" zpos that matches // the zpos we want the plane to have. @@ -1288,7 +1254,7 @@ static bool on_present_layers( struct flutterpi_backing_store *store = layers[i]->backing_store->user_data; struct rendertarget *target = store->target; - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { ok = target->present( target, req, @@ -1338,7 +1304,7 @@ static bool on_present_layers( } } - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { for_each_unreserved_plane_in_atomic_req(req, plane) { if ((plane->type == DRM_PLANE_TYPE_PRIMARY) || (plane->type == DRM_PLANE_TYPE_OVERLAY)) { drmdev_atomic_req_put_plane_property(req, plane->plane->plane_id, "FB_ID", 0); @@ -1349,7 +1315,7 @@ static bool on_present_layers( eglMakeCurrent(flutterpi.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (compositor->use_atomic_modesetting) { + if (use_atomic_modesetting) { do_commit: if (compositor->do_blocking_atomic_commits) { req_flags &= ~(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 17eb8c95..751e02e3 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1035,8 +1035,6 @@ void on_pageflip_event( flutterpi.flutter.libflutter_engine.FlutterEngineTraceEventInstant("pageflip"); - printf("on page flip event\n"); - cqueue_lock(&flutterpi.frame_queue); ok = cqueue_try_dequeue_locked(&flutterpi.frame_queue, &presented_frame); diff --git a/src/modesetting.c b/src/modesetting.c index 6e8481e8..785c314b 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -396,10 +396,14 @@ int drmdev_new_from_fd( } ok = drmSetClientCap(drmdev->fd, DRM_CLIENT_CAP_ATOMIC, 1); - if (ok < 0) { + if ((ok < 0) && (errno == EOPNOTSUPP)) { + drmdev->supports_atomic_modesetting = false; + } else if (ok < 0) { ok = errno; perror("[modesetting] Could not set DRM client atomic capable. drmSetClientCap"); goto fail_free_drmdev; + } else { + drmdev->supports_atomic_modesetting = true; } drmdev->res = drmModeGetResources(drmdev->fd); @@ -819,6 +823,10 @@ int drmdev_new_atomic_req( struct drm_plane *plane; int ok; + if (drmdev->supports_atomic_modesetting == false) { + return EOPNOTSUPP; + } + req = calloc(1, sizeof *req); if (req == NULL) { return ENOMEM; From 0aca30d6be8bf3a4fbdfc973467c5aa8d72ddfec Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 12 Oct 2020 13:53:39 +0200 Subject: [PATCH 12/15] add some logging to initialization --- include/modesetting.h | 2 +- src/flutter-pi.c | 80 ++++++++++++++++++++++++++++++++++++++++++- src/modesetting.c | 4 +++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/include/modesetting.h b/include/modesetting.h index d97576c2..f744bd30 100644 --- a/include/modesetting.h +++ b/include/modesetting.h @@ -225,7 +225,7 @@ int drmdev_legacy_set_plane_property( uint64_t value ); - +float mode_get_vrefresh(const drmModeModeInfo *mode); inline static struct drm_connector *__next_connector(const struct drmdev *drmdev, const struct drm_connector *connector) { bool found = connector == NULL; diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 751e02e3..d6a3e042 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1278,11 +1278,89 @@ static int init_display(void) { fprintf(stderr, "[flutter-pi] Could not find a connected connector!\n"); return EINVAL; } - + + printf(" modes: \n" + " name refresh (calculated) hdisp hss hse htot vdisp vss vse vtot clock\n"); // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, // prefer higher refresh rates. After that, prefer progressive scanout modes. for_each_mode_in_connector(connector, mode_iter) { + printf(" %s %d (%.2f) %d %d %d %d %d %d %d %d", + mode_iter->name, + mode_iter->vrefresh, + mode_get_vrefresh(mode_iter), + mode_iter->hdisplay, + mode_iter->hsync_start, + mode_iter->hsync_end, + mode_iter->htotal, + mode_iter->vdisplay, + mode_iter->vsync_start, + mode_iter->vsync_end, + mode_iter->vtotal + ); + printf(" %d flags: ", mode_iter->clock); + + { + const char *sep = ""; + for (uint32_t i = 1; i & 0x3FFF; i<<=1) { + if (mode_iter->flags & i) { + const char *name; + switch (i) { + case DRM_MODE_FLAG_PHSYNC: name = "phsync"; break; + case DRM_MODE_FLAG_NHSYNC: name = "nhsync"; break; + case DRM_MODE_FLAG_PVSYNC: name = "pvsync"; break; + case DRM_MODE_FLAG_NVSYNC: name = "nvsync"; break; + case DRM_MODE_FLAG_INTERLACE: name = "interlace"; break; + case DRM_MODE_FLAG_DBLSCAN: name = "dblscan"; break; + case DRM_MODE_FLAG_CSYNC: name = "csync"; break; + case DRM_MODE_FLAG_PCSYNC: name = "pcsync"; break; + case DRM_MODE_FLAG_NCSYNC: name = "ncsync"; break; + case DRM_MODE_FLAG_HSKEW: name = "hskew"; break; + case DRM_MODE_FLAG_BCAST: name = "bcast"; break; + case DRM_MODE_FLAG_PIXMUX: name = "pixmux"; break; + case DRM_MODE_FLAG_DBLCLK: name = "dblclk"; break; + case DRM_MODE_FLAG_CLKDIV2: name = "clkdiv2"; break; + default: name = "?"; break; + } + printf( + "%s%s", + sep, + name + ); + sep = ", "; + } + } + } + + printf("; type: "); + + { + const char *sep = ""; + for (uint32_t i = 1; i & 0x7F; i<<=1) { + if (mode_iter->type & i) { + const char *name; + switch (i) { + case DRM_MODE_TYPE_BUILTIN: name = "builtin"; break; + case DRM_MODE_TYPE_CLOCK_C: name = "clock_c"; break; + case DRM_MODE_TYPE_CRTC_C: name = "crtc_c"; break; + case DRM_MODE_TYPE_PREFERRED: name = "preferred"; break; + case DRM_MODE_TYPE_DEFAULT: name = "default"; break; + case DRM_MODE_TYPE_USERDEF: name = "userdef"; break; + case DRM_MODE_TYPE_DRIVER: name = "driver"; break; + default: name = "?"; break; + } + printf( + "%s%s", + sep, + name + ); + sep = ", "; + } + } + } + + printf("\n"); + if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { mode = mode_iter; break; diff --git a/src/modesetting.c b/src/modesetting.c index 785c314b..1b9a3a7c 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -374,6 +374,10 @@ static int free_planes(struct drm_plane *planes, size_t n_planes) { } +float mode_get_vrefresh(const drmModeModeInfo *mode) { + return mode->clock * 1000.0 / (mode->htotal * mode->vtotal); +} + int drmdev_new_from_fd( struct drmdev **drmdev_out, int fd From a6dfe38d83a3944f0224f61f77b8a82848a68bb8 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 12 Oct 2020 23:00:25 +0200 Subject: [PATCH 13/15] fix mode being uninitialized dont create display mode property blob if not using atomic modesetting --- src/flutter-pi.c | 1 + src/modesetting.c | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index d6a3e042..43c0940a 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1284,6 +1284,7 @@ static int init_display(void) { // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, // prefer higher refresh rates. After that, prefer progressive scanout modes. + mode = NULL; for_each_mode_in_connector(connector, mode_iter) { printf(" %s %d (%.2f) %d %d %d %d %d %d %d %d", mode_iter->name, diff --git a/src/modesetting.c b/src/modesetting.c index 1b9a3a7c..aa28ae8a 100644 --- a/src/modesetting.c +++ b/src/modesetting.c @@ -539,21 +539,24 @@ int drmdev_configure( return EINVAL; } - ok = drmModeCreatePropertyBlob(drmdev->fd, mode, sizeof(*mode), &mode_id); - if (ok < 0) { - perror("[modesetting] Could not create property blob for DRM mode. drmModeCreatePropertyBlob"); - drmdev_unlock(drmdev); - return errno; - } - - if (drmdev->selected_mode != NULL) { - ok = drmModeDestroyPropertyBlob(drmdev->fd, drmdev->selected_mode_blob_id); + mode_id = 0; + if (drmdev->supports_atomic_modesetting) { + ok = drmModeCreatePropertyBlob(drmdev->fd, mode, sizeof(*mode), &mode_id); if (ok < 0) { - ok = errno; - perror("[modesetting] Could not destroy old DRM mode property blob. drmModeDestroyPropertyBlob"); - drmModeDestroyPropertyBlob(drmdev->fd, mode_id); + perror("[modesetting] Could not create property blob for DRM mode. drmModeCreatePropertyBlob"); drmdev_unlock(drmdev); - return ok; + return errno; + } + + if (drmdev->selected_mode != NULL) { + ok = drmModeDestroyPropertyBlob(drmdev->fd, drmdev->selected_mode_blob_id); + if (ok < 0) { + ok = errno; + perror("[modesetting] Could not destroy old DRM mode property blob. drmModeDestroyPropertyBlob"); + drmModeDestroyPropertyBlob(drmdev->fd, mode_id); + drmdev_unlock(drmdev); + return ok; + } } } From 20d60e72eb4bea8d77dbe96d9c12be4743b281e5 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 13 Oct 2020 14:24:09 +0200 Subject: [PATCH 14/15] remove logging --- src/flutter-pi.c | 78 ------------------------------------------------ 1 file changed, 78 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 43c0940a..bbe99a51 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -1279,89 +1279,11 @@ static int init_display(void) { return EINVAL; } - printf(" modes: \n" - " name refresh (calculated) hdisp hss hse htot vdisp vss vse vtot clock\n"); // Find the preferred mode (GPU drivers _should_ always supply a preferred mode, but of course, they don't) // Alternatively, find the mode with the highest width*height. If there are multiple modes with the same w*h, // prefer higher refresh rates. After that, prefer progressive scanout modes. mode = NULL; for_each_mode_in_connector(connector, mode_iter) { - printf(" %s %d (%.2f) %d %d %d %d %d %d %d %d", - mode_iter->name, - mode_iter->vrefresh, - mode_get_vrefresh(mode_iter), - mode_iter->hdisplay, - mode_iter->hsync_start, - mode_iter->hsync_end, - mode_iter->htotal, - mode_iter->vdisplay, - mode_iter->vsync_start, - mode_iter->vsync_end, - mode_iter->vtotal - ); - printf(" %d flags: ", mode_iter->clock); - - { - const char *sep = ""; - for (uint32_t i = 1; i & 0x3FFF; i<<=1) { - if (mode_iter->flags & i) { - const char *name; - switch (i) { - case DRM_MODE_FLAG_PHSYNC: name = "phsync"; break; - case DRM_MODE_FLAG_NHSYNC: name = "nhsync"; break; - case DRM_MODE_FLAG_PVSYNC: name = "pvsync"; break; - case DRM_MODE_FLAG_NVSYNC: name = "nvsync"; break; - case DRM_MODE_FLAG_INTERLACE: name = "interlace"; break; - case DRM_MODE_FLAG_DBLSCAN: name = "dblscan"; break; - case DRM_MODE_FLAG_CSYNC: name = "csync"; break; - case DRM_MODE_FLAG_PCSYNC: name = "pcsync"; break; - case DRM_MODE_FLAG_NCSYNC: name = "ncsync"; break; - case DRM_MODE_FLAG_HSKEW: name = "hskew"; break; - case DRM_MODE_FLAG_BCAST: name = "bcast"; break; - case DRM_MODE_FLAG_PIXMUX: name = "pixmux"; break; - case DRM_MODE_FLAG_DBLCLK: name = "dblclk"; break; - case DRM_MODE_FLAG_CLKDIV2: name = "clkdiv2"; break; - default: name = "?"; break; - } - printf( - "%s%s", - sep, - name - ); - sep = ", "; - } - } - } - - printf("; type: "); - - { - const char *sep = ""; - for (uint32_t i = 1; i & 0x7F; i<<=1) { - if (mode_iter->type & i) { - const char *name; - switch (i) { - case DRM_MODE_TYPE_BUILTIN: name = "builtin"; break; - case DRM_MODE_TYPE_CLOCK_C: name = "clock_c"; break; - case DRM_MODE_TYPE_CRTC_C: name = "crtc_c"; break; - case DRM_MODE_TYPE_PREFERRED: name = "preferred"; break; - case DRM_MODE_TYPE_DEFAULT: name = "default"; break; - case DRM_MODE_TYPE_USERDEF: name = "userdef"; break; - case DRM_MODE_TYPE_DRIVER: name = "driver"; break; - default: name = "?"; break; - } - printf( - "%s%s", - sep, - name - ); - sep = ", "; - } - } - } - - printf("\n"); - if (mode_iter->type & DRM_MODE_TYPE_PREFERRED) { mode = mode_iter; break; From 87874bc233aede886b9bddde8e34873499431d9b Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Tue, 13 Oct 2020 14:26:46 +0200 Subject: [PATCH 15/15] Update README.md --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index e5af4488..65767476 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ## 📰 NEWS - flutter-pi now requires `libxkbcommon`. Install using `sudo apt install libxkbcommon-dev` +- keyboard input works better now. You can now use any keyboard connected to the Raspberry Pi for text and raw keyboard input. # flutter-pi A light-weight Flutter Engine Embedder for Raspberry Pi. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. @@ -245,13 +246,6 @@ The _flutter-pi_ executable will then be located at this path: `/path/to/the/clo ## Performance Performance is actually better than I expected. With most of the apps inside the `flutter SDK -> examples -> catalog` directory I get smooth 50-60fps. -## Keyboard Input -Keyboard input is supported. **There is one important limitation though**. Text input (i.e. writing any kind of text/symbols to flutter input fields) only works when typing on the keyboard, which is attached to the terminal flutter-pi is running on. So, if you ssh into your Raspberry Pi to run flutter-pi, you have to enter text into your ssh terminal. - -Raw Keyboard input (i.e. using tab to iterate through focus nodes) works with any keyboard attached to your Raspberry Pi. - -converting raw key-codes to text symbols is not that easy (because of all the different keyboard layouts), so for text input flutter-pi basically uses `stdin`. - ## Touchscreen Latency Due to the way the touchscreen driver works in raspbian, there's some delta between an actual touch of the touchscreen and a touch event arriving at userspace. The touchscreen driver in the raspbian kernel actually just repeatedly polls some buffer shared with the firmware running on the VideoCore, and the videocore repeatedly polls the touchscreen. (both at 60Hz) So on average, there's a delay of 17ms (minimum 0ms, maximum 34ms). If I have enough time in the future, I'll try to build a better touchscreen driver to lower the delay.