From 335067cc589348d12d8268d058e84904ed040018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 15:16:40 +0200 Subject: [PATCH 01/13] run: Ease running the OSK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We usually want the OSK enabled to handle that and turn it off when we hit CTRL-C. Signed-off-by: Guido Günther --- run.in | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/run.in b/run.in index 856d33c..341ed86 100755 --- a/run.in +++ b/run.in @@ -1,15 +1,36 @@ -#!/bin/sh +#!/bin/bash set -e +handle_done() { + echo "Caught SIGINT" + kill -TERM $child +} + +trap handle_done SIGTERM + ABS_BUILDDIR='@ABS_BUILDDIR@' -ABS_SRCDIR='@ABS_SRCDIR@' export GSETTINGS_SCHEMA_DIR="${ABS_BUILDDIR}/data" +STATE=$(gsettings get org.gnome.desktop.a11y.applications screen-keyboard-enabled) +gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true + if [ "${POS_GDB}" = 1 ]; then echo "Running phosh-osk-stevia under gdb" WRAPPER="gdb --args" fi +set +e set -x -exec ${WRAPPER} "${ABS_BUILDDIR}/src/phosh-osk-stevia" "$@" +${WRAPPER} "${ABS_BUILDDIR}/src/phosh-osk-stevia" "$@" +child=$! +set +x +ret=$? +set -e + +if [ "${STATE}" == false ]; then + echo "Disabling OSK" + gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled "${STATE}" +fi + +exit "$ret" -- GitLab From 2af7b41d7ddcb5d267746eff449d8723260af7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:12 +0200 Subject: [PATCH 02/13] build: Rename options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `meson.options` is the recommended name nowadays Signed-off-by: Guido Günther --- meson_options.txt => meson.options | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename meson_options.txt => meson.options (100%) diff --git a/meson_options.txt b/meson.options similarity index 100% rename from meson_options.txt rename to meson.options -- GitLab From adf808ad8990de0fa291bb7e96cb91c63545de3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:13 +0200 Subject: [PATCH 03/13] completer: Fix comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without `and` it is hard to parse Gbp-Dch: Ignore Signed-off-by: Guido Günther --- src/pos-completer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pos-completer.c b/src/pos-completer.c index e1824c6..975a97b 100644 --- a/src/pos-completer.c +++ b/src/pos-completer.c @@ -506,9 +506,9 @@ pos_completer_symbol_is_word_separator (const char *symbol, gboolean *is_ws) * @new_text:(out): The new text with the last word removed * @word: The last word of text * - * Scans `text` from the end returns the last word. If `text` ends with - * whitespace the last word is considered empty and `new_text` and `word` - * remain unchanged. + * Scans `text` from the end and returns the last word. If `text` ends + * with whitespace the last word is considered empty and `new_text` + * and `word` remain unchanged. * * Returns: %TRUE `new_text` and `word` were filled. */ -- GitLab From f6fa2af88cfac510bb57a6f5e6fd67c973ca4ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 13:08:45 +0200 Subject: [PATCH 04/13] completer: Fix indent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gbp-Dch: Ignore Signed-off-by: Guido Günther --- src/pos-completer.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pos-completer.c b/src/pos-completer.c index 975a97b..fe225a8 100644 --- a/src/pos-completer.c +++ b/src/pos-completer.c @@ -408,9 +408,8 @@ pos_completer_add_preedit (PosCompleter *self, GString *preedit, const char *sym } /* Return/Enter is special, see above. */ - if (g_strcmp0 (symbol, "KEY_ENTER") == 0) { + if (g_strcmp0 (symbol, "KEY_ENTER") == 0) return TRUE; - } /* Ignore all other special keys */ if (g_str_has_prefix (symbol, "KEY_")) @@ -527,18 +526,18 @@ pos_completer_grab_last_word (const char *text, char **new_text, char **word) /* text ends with whitespace */ len = g_utf8_strlen (text, -1); - symbol = g_utf8_substring (text, len-1, len); + symbol = g_utf8_substring (text, len - 1, len); if (pos_completer_symbol_is_word_separator (symbol, NULL)) return FALSE; /* Get last word in text */ for (glong start = len - 1; start >= 0; start--) { g_free (symbol); - symbol = g_utf8_substring (text, start, start+1); + symbol = g_utf8_substring (text, start, start + 1); if (pos_completer_symbol_is_word_separator (symbol, NULL)) { - *word = g_strdup (g_utf8_offset_to_pointer (text, start+1)); - *new_text = g_utf8_substring (text, 0, start+1); + *word = g_strdup (g_utf8_offset_to_pointer (text, start + 1)); + *new_text = g_utf8_substring (text, 0, start + 1); return TRUE; } } @@ -562,7 +561,7 @@ pos_completer_grab_last_word (const char *text, char **new_text, char **word) * Returns: (transfer full): copy of completions with changed capitalization */ GStrv -pos_completer_capitalize_by_template (const char *template, const GStrv completions) +pos_completer_capitalize_by_template (const char *template, const GStrv completions) { gboolean has_caps; glong templ_len; -- GitLab From d13faf3fc0b93a207be5422e487552b85f38e8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:13 +0200 Subject: [PATCH 05/13] completer: Rename test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can test more features of the completer there Signed-off-by: Guido Günther --- tests/meson.build | 2 +- tests/{test-grab-word.c => test-completer.c} | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) rename tests/{test-grab-word.c => test-completer.c} (94%) diff --git a/tests/meson.build b/tests/meson.build index 2b8729e..4576147 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -20,7 +20,7 @@ test_env.set('ASAN_OPTIONS', 'fast_unwind_on_malloc=0') #test_env.set('ASAN_OPTIONS', 'fast_unwind_on_malloc=0:disable_coredump=0:unmap_shadow_on_exit=1:abort_on_error=1') tests = [ - 'grab-word', + 'completer', 'completer-hunspell', 'load-layouts', 'osk-widget', diff --git a/tests/test-grab-word.c b/tests/test-completer.c similarity index 94% rename from tests/test-grab-word.c rename to tests/test-completer.c index 9b40c91..bc2a68b 100644 --- a/tests/test-grab-word.c +++ b/tests/test-completer.c @@ -1,8 +1,10 @@ /* * Copyright © 2020 Lugsole - * Copyright (C) 2021 Purism SPC + * 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author: Guido Günther */ #include "pos-completer-priv.h" -- GitLab From 3cc1e4b1d5cf10ca4392b5dbe549f656219d857f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:14 +0200 Subject: [PATCH 06/13] completer: Add helper to get last word offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To delete surrounding text we need the offset of the last word. We'll use that in the next commit but it will also be useful in more clever replacements later on. Signed-off-by: Guido Günther --- src/pos-completer.c | 59 +++++++++++++++++++++++++++++++++++++++++- src/pos-completer.h | 2 ++ tests/test-completer.c | 21 ++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/pos-completer.c b/src/pos-completer.c index fe225a8..cad47ae 100644 --- a/src/pos-completer.c +++ b/src/pos-completer.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2023 The Phosh Developers + * Copyright (C) 2023-2024 The Phosh Developers + * 2025 Phosh.mobi e.V. * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -549,6 +550,62 @@ pos_completer_grab_last_word (const char *text, char **new_text, char **word) return TRUE; } +/** + * pos_completer_find_prev_word_break: + * @text: the text to find the word break in + * + * Scans `text` from the end and returns the last word break in bytes + * counted from the end of the string. The word break is the position + * of the last two words separated by `completion_end_symbols`. + * + * If the string has trailing whitespace the amount of whitespace at the + * end is returned instead. + * + * Returns: The position of the last word break or `-1` if none was found. + */ +long +pos_completer_find_prev_word_break (const char *text) +{ + char *addr; + long len, pos, byte_len; + g_autofree char *symbol = NULL; + + /* Nothing to parse */ + if (STR_IS_NULL_OR_EMPTY (text)) + return -1; + + len = g_utf8_strlen (text, -1); + if (!len) + return -1; + + /* Check trailing whitespace */ + for (pos = len - 1; pos >= 0; pos--) { + g_free (symbol); + symbol = g_utf8_substring (text, pos, pos + 1); + + if (!pos_completer_symbol_is_word_separator (symbol, NULL)) + break; + } + + if (pos != len - 1) + goto out; + + for (; pos >= 0; pos--) { + g_free (symbol); + symbol = g_utf8_substring (text, pos, pos + 1); + + g_debug ("%s", symbol); + + if (pos_completer_symbol_is_word_separator (symbol, NULL)) + break; + } + + out: + byte_len = strlen (text); + addr = g_utf8_offset_to_pointer (text, pos); + return &(text[byte_len]) - addr - 1; +} + /** * pos_completer_capitalize_by_template: * @template: String with some characters in upper case diff --git a/src/pos-completer.h b/src/pos-completer.h index cc95697..5312df3 100644 --- a/src/pos-completer.h +++ b/src/pos-completer.h @@ -81,4 +81,6 @@ void pos_completer_learn_accepted (PosCompleter *self, const char *wor GStrv pos_completer_capitalize_by_template (const char *template, const GStrv completions); +glong pos_completer_find_prev_word_break (const char *text); + G_END_DECLS diff --git a/tests/test-completer.c b/tests/test-completer.c index bc2a68b..300b2b7 100644 --- a/tests/test-completer.c +++ b/tests/test-completer.c @@ -12,7 +12,7 @@ #include static void -test_grab_last_word(void) +test_grab_last_word (void) { char *new_before = NULL; char *word = NULL; @@ -42,12 +42,31 @@ test_grab_last_word(void) } +static void +test_find_prev_word_break (void) +{ + g_assert_cmpint (pos_completer_find_prev_word_break ("ends with word"), ==, 4); + g_assert_cmpint (pos_completer_find_prev_word_break ("ends with w😀rd"), ==, 7); + g_assert_cmpint (pos_completer_find_prev_word_break ("e😀😀s with w😀rd"), ==, 7); + g_assert_cmpint (pos_completer_find_prev_word_break ("justoneword"), ==, 11); + + g_assert_cmpint (pos_completer_find_prev_word_break (NULL), ==, -1); + g_assert_cmpint (pos_completer_find_prev_word_break (""), ==, -1); + + g_assert_cmpint (pos_completer_find_prev_word_break (" "), ==, 1); + g_assert_cmpint (pos_completer_find_prev_word_break (" \n\t"), ==, 3); + + g_assert_cmpint (pos_completer_find_prev_word_break ("a \t "), ==, 3); +} + + int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/pos/completer/grab_last_word", test_grab_last_word); + g_test_add_func ("/pos/completer/find_prev_word_break", test_find_prev_word_break); return g_test_run (); } -- GitLab From 43a77514372e39bf744493a57adde0590184e8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:14 +0200 Subject: [PATCH 07/13] osk-widget: Emit up after symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise it's not possible to do anything after the symbol was emitted. We're not using the up signal anywhere at the moment so this is no functional change. Signed-off-by: Guido Günther --- src/pos-osk-widget.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pos-osk-widget.c b/src/pos-osk-widget.c index 90e088d..c21f76a 100644 --- a/src/pos-osk-widget.c +++ b/src/pos-osk-widget.c @@ -851,8 +851,8 @@ on_key_repeat (gpointer data) g_return_val_if_fail (self->current, G_SOURCE_REMOVE); g_signal_emit (self, signals[OSK_KEY_DOWN], 0, pos_osk_key_get_symbol (self->current)); - g_signal_emit (self, signals[OSK_KEY_UP], 0, pos_osk_key_get_symbol (self->current)); g_signal_emit (self, signals[OSK_KEY_SYMBOL], 0, pos_osk_key_get_symbol (self->current)); + g_signal_emit (self, signals[OSK_KEY_UP], 0, pos_osk_key_get_symbol (self->current)); return G_SOURCE_CONTINUE; } @@ -986,8 +986,8 @@ pos_osk_widget_key_release_action (PosOskWidget *self, PosOskKey *key) G_GNUC_FALLTHROUGH; case POS_OSK_KEY_USE_DELETE: pos_osk_widget_set_key_pressed (self, self->current, FALSE); - g_signal_emit (self, signals[OSK_KEY_UP], 0, pos_osk_key_get_symbol (key)); g_signal_emit (self, signals[OSK_KEY_SYMBOL], 0, pos_osk_key_get_symbol (key)); + g_signal_emit (self, signals[OSK_KEY_UP], 0, pos_osk_key_get_symbol (key)); switch_layer (self, key); break; @@ -1142,6 +1142,7 @@ on_symbol_selected (PosOskWidget *self, const char *symbol) g_signal_emit (self, signals[OSK_KEY_DOWN], 0, symbol); g_signal_emit (self, signals[OSK_KEY_SYMBOL], 0, symbol); + g_signal_emit (self, signals[OSK_KEY_UP], 0, symbol); g_clear_pointer (&self->char_popup, phosh_cp_widget_destroy); } -- GitLab From 5b8013cc23dedcdec711d2e89b93c037c3fcbc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:15 +0200 Subject: [PATCH 08/13] input-surface: Remove unused header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther --- src/pos-input-surface.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 65da1e0..ad1f2db 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -27,7 +27,6 @@ #include "pos-shortcuts-bar.h" #include "pos-style-manager.h" #include "pos-vk-driver.h" -#include "pos-virtual-keyboard.h" #include "pos-vk-driver.h" #include "util.h" -- GitLab From ae02f917c925df5f67dcda39dbf0f1cfc154f3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:15 +0200 Subject: [PATCH 09/13] input-surface: Move define upwards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They go before the structs and enums Signed-off-by: Guido Günther --- src/pos-input-surface.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index ad1f2db..6f8bd36 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2021 Purism SPC * 2022-2024 The Phosh Developers + * 2025 Phosh.mobi e.V. * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -40,6 +41,8 @@ #include +#define MIN_Y_VELOCITY 1000 + /** * POS_INPUT_SURFACE_IS_LANG_LAYOUT: * @layout: The layout to check @@ -160,7 +163,6 @@ G_DEFINE_TYPE_WITH_CODE (PosInputSurface, pos_input_surface, PHOSH_TYPE_LAYER_SU G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_MAP, pos_input_surface_action_map_iface_init) ) -#define MIN_Y_VELOCITY 1000 static void on_swipe (GtkGestureSwipe *swipe, double velocity_x, double velocity_y, gpointer data) -- GitLab From 9db5dd3e0eb5a88078333b7dddf6d8d5b2246706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:16 +0200 Subject: [PATCH 10/13] input-surface: Move feedback triggering upwards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will avoid a forward declaration. While at that we unify it into a single function. Signed-off-by: Guido Günther --- src/pos-input-surface.c | 49 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 6f8bd36..140230e 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -41,6 +41,9 @@ #include +#define KEY_PRESS_EVENT "key-pressed" +#define BUTTON_PRESS_EVENT "button-pressed" + #define MIN_Y_VELOCITY 1000 /** @@ -164,6 +167,19 @@ G_DEFINE_TYPE_WITH_CODE (PosInputSurface, pos_input_surface, PHOSH_TYPE_LAYER_SU ) +static void +pos_input_surface_trigger_feedback (PosInputSurface *self, const char *event_name) +{ + g_autoptr (LfbEvent) event = NULL; + + g_assert (POS_IS_INPUT_SURFACE (self)); + + event = lfb_event_new (event_name); + lfb_event_set_important (event, TRUE); + lfb_event_trigger_feedback_async (event, NULL, NULL, NULL); +} + + static void on_swipe (GtkGestureSwipe *swipe, double velocity_x, double velocity_y, gpointer data) { @@ -237,31 +253,6 @@ on_num_shortcuts_changed (PosInputSurface *self) } -static void -pos_input_surface_notify_key_press (PosInputSurface *self) -{ - g_autoptr (LfbEvent) event = NULL; - - g_assert (POS_IS_INPUT_SURFACE (self)); - - event = lfb_event_new ("key-pressed"); - lfb_event_set_important (event, TRUE); - lfb_event_trigger_feedback_async (event, NULL, NULL, NULL); -} - - -static void -pos_input_surface_notify_button_press (PosInputSurface *self) -{ - g_autoptr (LfbEvent) event = NULL; - - g_assert (POS_IS_INPUT_SURFACE (self)); - - event = lfb_event_new ("button-pressed"); - lfb_event_trigger_feedback_async (event, NULL, NULL, NULL); -} - - static gboolean on_click_hook (GSignalInvocationHint *ihint, guint n_param_values, @@ -270,7 +261,7 @@ on_click_hook (GSignalInvocationHint *ihint, { PosInputSurface *self = POS_INPUT_SURFACE (user_data); - pos_input_surface_notify_button_press (self); + pos_input_surface_trigger_feedback (self, BUTTON_PRESS_EVENT); return TRUE; } @@ -389,7 +380,7 @@ on_osk_key_down (PosInputSurface *self, const char *symbol, GtkWidget *osk_widge g_return_if_fail (POS_IS_INPUT_SURFACE (self)); g_return_if_fail (POS_IS_OSK_WIDGET (osk_widget)); - pos_input_surface_notify_key_press (self); + pos_input_surface_trigger_feedback (self, KEY_PRESS_EVENT); } @@ -591,7 +582,7 @@ on_emoji_picked (PosInputSurface *self, const char *emoji, PosEmojiPicker *emoji send_emoji_via_vk (self, emoji); } - pos_input_surface_notify_key_press (self); + pos_input_surface_trigger_feedback (self, KEY_PRESS_EVENT); } @@ -625,7 +616,7 @@ on_keypad_symbol_pressed (PosInputSurface *self, const char *symbol, PosKeypad * pos_vk_driver_key_up (self->keyboard_driver, symbol); } - pos_input_surface_notify_key_press (self); + pos_input_surface_trigger_feedback (self, KEY_PRESS_EVENT); } -- GitLab From 9505789c81790ada33c7fe3b47a66f207f9fc77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:16 +0200 Subject: [PATCH 11/13] input-surface: Ease submitting a symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a forward declaration so we can use it more easily and avoid using the a `on_` handler but rather use a proper function name. This cleans up the emoji delete and will be further used in the next commit. Signed-off-by: Guido Günther --- src/pos-input-surface.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 140230e..7e5030b 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -158,6 +158,8 @@ struct _PosInputSurface { }; +static void pos_input_surface_submit_symbol (PosInputSurface *self, const char *symbol); + static void pos_input_surface_action_group_iface_init (GActionGroupInterface *iface); static void pos_input_surface_action_map_iface_init (GActionMapInterface *iface); @@ -387,9 +389,16 @@ on_osk_key_down (PosInputSurface *self, const char *symbol, GtkWidget *osk_widge static void on_osk_key_symbol (PosInputSurface *self, const char *symbol) { - gboolean handled; + g_assert (POS_IS_INPUT_SURFACE (self)); - g_return_if_fail (POS_IS_INPUT_SURFACE (self)); + pos_input_surface_submit_symbol (self, symbol); +} + + +static void +pos_input_surface_submit_symbol (PosInputSurface *self, const char *symbol) +{ + gboolean handled; g_debug ("Key: '%s' symbol", symbol); @@ -596,8 +605,8 @@ on_emoji_picker_done (PosInputSurface *self) static void on_emoji_picker_delete_last (PosInputSurface *self) { - g_debug ("%s", __func__); - on_osk_key_symbol (self, "KEY_BACKSPACE"); + g_debug ("Deleting last emoji"); + pos_input_surface_submit_symbol (self, "KEY_BACKSPACE"); } /* Keypads */ -- GitLab From 4318ebf2e2299a6455da875b2013961f2ba45803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:17 +0200 Subject: [PATCH 12/13] input-surface: Move key repeat handling out of osk-widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We only use it to track backspace so name it accordingly Signed-off-by: Guido Günther --- src/pos-input-surface.c | 94 ++++++++++++++++++++++++++++++++++++++++- src/pos-osk-widget.c | 38 +---------------- src/ui/input-surface.ui | 2 + 3 files changed, 96 insertions(+), 38 deletions(-) diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 7e5030b..ae6f622 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -46,6 +46,9 @@ #define MIN_Y_VELOCITY 1000 +#define BS_KEY_REPEAT_DELAY 700 +#define BS_KEY_REPEAT_INTERVAL_CHAR 50 + /** * POS_INPUT_SURFACE_IS_LANG_LAYOUT: * @layout: The layout to check @@ -155,6 +158,9 @@ struct _PosInputSurface { /* emission hook for clicks */ gulong clicked_id; + + /* Backspace handling */ + guint bs_repeat_id; }; @@ -182,6 +188,61 @@ pos_input_surface_trigger_feedback (PosInputSurface *self, const char *event_nam } +static gboolean +on_bs_key_repeat (gpointer data) +{ + PosInputSurface *self = data; + + pos_input_surface_trigger_feedback (self, KEY_PRESS_EVENT); + pos_input_surface_submit_symbol (self, "KEY_BACKSPACE"); + + return G_SOURCE_CONTINUE; +} + + +static void +on_bs_long_press_timeout (gpointer data) +{ + PosInputSurface *self = data; + guint interval; + + interval = BS_KEY_REPEAT_INTERVAL_CHAR; + + self->bs_repeat_id = g_timeout_add (interval, on_bs_key_repeat, self); + g_source_set_name_by_id (self->bs_repeat_id, "[pos-bs-key-repeat]"); + } + + +static gboolean +pos_input_surface_set_backspace_pressed (PosInputSurface *self, const char *symbol) +{ + gboolean pressed; + + pressed = symbol && g_str_equal (symbol, "KEY_BACKSPACE"); + if (!pressed) { + g_clear_handle_id (&self->bs_repeat_id, g_source_remove); + return FALSE; + } + + if (!self->bs_repeat_id) { + self->bs_repeat_id = g_timeout_add_once (BS_KEY_REPEAT_DELAY, + on_bs_long_press_timeout, + self); + g_source_set_name_by_id (self->bs_repeat_id, "[pos-bs-long-press-timeout]"); + } + + return TRUE; +} + + +static void +pos_input_surface_handle_backsapce (PosInputSurface *self) +{ + pos_vk_driver_key_down (self->keyboard_driver, "KEY_BACKSPACE", POS_KEYCODE_MODIFIER_NONE); + pos_vk_driver_key_up (self->keyboard_driver, "KEY_BACKSPACE"); +} + + static void on_swipe (GtkGestureSwipe *swipe, double velocity_x, double velocity_y, gpointer data) { @@ -383,6 +444,26 @@ on_osk_key_down (PosInputSurface *self, const char *symbol, GtkWidget *osk_widge g_return_if_fail (POS_IS_OSK_WIDGET (osk_widget)); pos_input_surface_trigger_feedback (self, KEY_PRESS_EVENT); + + pos_input_surface_set_backspace_pressed (self, symbol); +} + + +static void +on_osk_key_up (PosInputSurface *self) +{ + g_assert (POS_IS_INPUT_SURFACE (self)); + + pos_input_surface_set_backspace_pressed (self, NULL); +} + + +static void +on_osk_key_cancelled (PosInputSurface *self) +{ + g_assert (POS_IS_INPUT_SURFACE (self)); + + pos_input_surface_set_backspace_pressed (self, NULL); } @@ -398,10 +479,12 @@ on_osk_key_symbol (PosInputSurface *self, const char *symbol) static void pos_input_surface_submit_symbol (PosInputSurface *self, const char *symbol) { - gboolean handled; + gboolean handled, is_bs; g_debug ("Key: '%s' symbol", symbol); + is_bs = pos_input_surface_set_backspace_pressed (self, symbol); + /* Latched modifiers, send as virtual-keyboard */ if (self->latched_modifiers) { PosKeycodeModifier modifier; @@ -425,7 +508,9 @@ pos_input_surface_submit_symbol (PosInputSurface *self, const char *symbol) return; } - if (g_str_has_prefix (symbol, "KEY_")) { + if (is_bs) { + pos_input_surface_handle_backsapce (self); + } else if (g_str_has_prefix (symbol, "KEY_")) { pos_vk_driver_key_down (self->keyboard_driver, symbol, POS_KEYCODE_MODIFIER_NONE); pos_vk_driver_key_up (self->keyboard_driver, symbol); } else { @@ -1400,6 +1485,7 @@ pos_input_surface_finalize (GObject *object) self->clicked_id = 0; g_clear_handle_id (&self->animation.id, g_source_remove); + g_clear_handle_id (&self->bs_repeat_id, g_source_remove); g_clear_object (&self->logind_session); g_clear_object (&self->keyboard_driver); @@ -1603,7 +1689,9 @@ pos_input_surface_class_init (PosInputSurfaceClass *klass) gtk_widget_class_bind_template_callback (widget_class, on_latched_modifiers_changed); gtk_widget_class_bind_template_callback (widget_class, on_num_shortcuts_changed); + gtk_widget_class_bind_template_callback (widget_class, on_osk_key_cancelled); gtk_widget_class_bind_template_callback (widget_class, on_osk_key_down); + gtk_widget_class_bind_template_callback (widget_class, on_osk_key_up); gtk_widget_class_bind_template_callback (widget_class, on_osk_key_symbol); gtk_widget_class_bind_template_callback (widget_class, on_osk_mode_changed); gtk_widget_class_bind_template_callback (widget_class, on_osk_popover_shown); @@ -1787,8 +1875,10 @@ insert_osk (PosInputSurface *self, g_debug ("Adding osk for layout '%s'", name); gtk_widget_set_visible (GTK_WIDGET (osk_widget), TRUE); g_object_connect (osk_widget, + "swapped-signal::key-cancelled", G_CALLBACK (on_osk_key_cancelled), self, "swapped-signal::key-down", G_CALLBACK (on_osk_key_down), self, "swapped-signal::key-symbol", G_CALLBACK (on_osk_key_symbol), self, + "swapped-signal::key-up", G_CALLBACK (on_osk_key_up), self, "swapped-signal::notify::mode", G_CALLBACK (on_osk_mode_changed), self, "swapped-signal::popover-shown", G_CALLBACK (on_osk_popover_shown), self, "swapped-signal::popover-hidden", G_CALLBACK (on_osk_popover_hidden), self, diff --git a/src/pos-osk-widget.c b/src/pos-osk-widget.c index c21f76a..597c53c 100644 --- a/src/pos-osk-widget.c +++ b/src/pos-osk-widget.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2022 The Phosh Developers + * Copyright (C) 2022-2024 The Phosh Developers + * 2025 Phosh.mobi e.V. * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -32,9 +33,6 @@ #define MINIMUM_WIDTH 360 -#define KEY_REPEAT_DELAY 700 -#define KEY_REPEAT_INTERVAL 50 - enum { OSK_KEY_DOWN, OSK_KEY_UP, @@ -843,33 +841,6 @@ pos_osk_widget_locate_key (PosOskWidget *self, double x, double y) } -static gboolean -on_key_repeat (gpointer data) -{ - PosOskWidget *self = POS_OSK_WIDGET (data); - - g_return_val_if_fail (self->current, G_SOURCE_REMOVE); - - g_signal_emit (self, signals[OSK_KEY_DOWN], 0, pos_osk_key_get_symbol (self->current)); - g_signal_emit (self, signals[OSK_KEY_SYMBOL], 0, pos_osk_key_get_symbol (self->current)); - g_signal_emit (self, signals[OSK_KEY_UP], 0, pos_osk_key_get_symbol (self->current)); - - return G_SOURCE_CONTINUE; -} - - -static gboolean -on_repeat_timeout (gpointer data) -{ - PosOskWidget *self = POS_OSK_WIDGET (data); - - self->repeat_id = g_timeout_add (KEY_REPEAT_INTERVAL, on_key_repeat, self); - g_source_set_name_by_id (self->repeat_id, "[pos-key-repeat]"); - - return G_SOURCE_REMOVE; -} - - static void key_repeat_cancel (PosOskWidget *self) { @@ -919,11 +890,6 @@ pos_osk_widget_key_press (PosOskWidget *self, double x, double y) } pos_osk_widget_key_press_action (self, key); - if (pos_osk_key_get_use (key) == POS_OSK_KEY_USE_DELETE) { - self->repeat_id = g_timeout_add (KEY_REPEAT_DELAY, on_repeat_timeout, self); - g_source_set_name_by_id (self->repeat_id, "[pos-key-repeat-timeout]"); - } - return GDK_EVENT_PROPAGATE; } diff --git a/src/ui/input-surface.ui b/src/ui/input-surface.ui index 88535a0..9680f38 100644 --- a/src/ui/input-surface.ui +++ b/src/ui/input-surface.ui @@ -39,8 +39,10 @@ 1 + + -- GitLab From 1de94920edd2a4d8670cf4e9943b05448bb2d37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Fri, 19 Sep 2025 12:36:18 +0200 Subject: [PATCH 13/13] input-surface: Delete last word on backspace long press MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of deleting individual chars fast delete whole words at a slightly lower speed instead. This gives better control and doesn't let haptic buzz like crazy. Signed-off-by: Guido Günther --- src/pos-enums.h | 12 +++++++ src/pos-input-surface.c | 69 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/pos-enums.h b/src/pos-enums.h index c754d5c..39ca280 100644 --- a/src/pos-enums.h +++ b/src/pos-enums.h @@ -108,4 +108,16 @@ typedef enum { POS_INPUT_METHOD_HINT_MULTILINE, } PosInputMethodHint; +/** + * PosBackspaceMode: + * @POS_BACKSPACE_MODE_CHAR: Delete on character + * @POS_BACKSPACE_MODE_WORD: Delete one wordx + * + * How much a press of backspace should remove + */ +typedef enum { + POS_BACKSPACE_MODE_CHAR = 1, + POS_BACKSPACE_MODE_WORD = 2, +} PosBackspaceMode; + G_END_DECLS diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index ae6f622..25c5124 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -48,6 +48,7 @@ #define BS_KEY_REPEAT_DELAY 700 #define BS_KEY_REPEAT_INTERVAL_CHAR 50 +#define BS_KEY_REPEAT_INTERVAL_WORD 150 /** * POS_INPUT_SURFACE_IS_LANG_LAYOUT: @@ -161,6 +162,8 @@ struct _PosInputSurface { /* Backspace handling */ guint bs_repeat_id; + PosBackspaceMode bs_mode; + char *surround_before; }; @@ -188,6 +191,42 @@ pos_input_surface_trigger_feedback (PosInputSurface *self, const char *event_nam } +static void +pos_input_surface_set_backspace_mode (PosInputSurface *self, PosBackspaceMode mode) +{ + if (self->bs_mode == mode) + return; + + self->bs_mode = mode; + g_debug ("Backspace mode: %d", self->bs_mode); +} + +/** + * pos_input_surface_delete_last_word: + * @self: The input surface + * + * Delete the last word from the current surrounding text. + * + * Returns: `TRUE` if anything was deleted, otherwise `FALSE` + */ +static gboolean +pos_input_surface_delete_last_word (PosInputSurface *self) +{ + long len = 0; + + if (self->surround_before) + len = pos_completer_find_prev_word_break (self->surround_before); + + /* We didn't find anything to delete or it's just a single char */ + if (len <= 1) + return FALSE; + + g_debug ("Deleting last word of length %ld", len); + pos_input_method_delete_surrounding_text (self->input_method, MAX (1, len), 0, TRUE); + return TRUE; +} + + static gboolean on_bs_key_repeat (gpointer data) { @@ -206,7 +245,14 @@ on_bs_long_press_timeout (gpointer data) PosInputSurface *self = data; guint interval; - interval = BS_KEY_REPEAT_INTERVAL_CHAR; + if (pos_input_method_get_active (self->input_method)) { + interval = BS_KEY_REPEAT_INTERVAL_WORD; + pos_input_surface_set_backspace_mode (self, POS_BACKSPACE_MODE_WORD); + } else { + /* When no input method is active we delete individual chars which should + * happen faster than deleting whole words */ + interval = BS_KEY_REPEAT_INTERVAL_CHAR; + } self->bs_repeat_id = g_timeout_add (interval, on_bs_key_repeat, self); g_source_set_name_by_id (self->bs_repeat_id, "[pos-bs-key-repeat]"); @@ -220,6 +266,7 @@ pos_input_surface_set_backspace_pressed (PosInputSurface *self, const char *symb pressed = symbol && g_str_equal (symbol, "KEY_BACKSPACE"); if (!pressed) { + pos_input_surface_set_backspace_mode (self, POS_BACKSPACE_MODE_CHAR); g_clear_handle_id (&self->bs_repeat_id, g_source_remove); return FALSE; } @@ -238,6 +285,16 @@ pos_input_surface_set_backspace_pressed (PosInputSurface *self, const char *symb static void pos_input_surface_handle_backsapce (PosInputSurface *self) { + gboolean handled = FALSE; + + g_assert (pos_input_method_get_active (self->input_method)); + + if (self->bs_mode == POS_BACKSPACE_MODE_WORD) + handled = pos_input_surface_delete_last_word (self); + + if (handled) + return; + pos_vk_driver_key_down (self->keyboard_driver, "KEY_BACKSPACE", POS_KEYCODE_MODIFIER_NONE); pos_vk_driver_key_up (self->keyboard_driver, "KEY_BACKSPACE"); } @@ -1367,7 +1424,6 @@ on_im_surrounding_text_changed (PosInputSurface *self, GParamSpec *pspec, PosInp { const char *text; guint anchor, cursor; - g_autofree char *before = NULL; g_autofree char *after = NULL; g_assert (POS_IS_INPUT_SURFACE (self)); @@ -1378,12 +1434,15 @@ on_im_surrounding_text_changed (PosInputSurface *self, GParamSpec *pspec, PosInp if (!pos_input_surface_is_completion_mode (self)) return; - before = g_strndup (text, cursor); + g_free (self->surround_before); + self->surround_before = g_strndup (text, cursor); if (text) after = g_strdup (&(text[cursor])); - pos_completer_set_surrounding_text (POS_COMPLETER (self->completer), before, after); + pos_completer_set_surrounding_text (POS_COMPLETER (self->completer), + self->surround_before, + after); } @@ -1500,6 +1559,7 @@ pos_input_surface_finalize (GObject *object) g_clear_object (&self->swipe_down); g_clear_object (&self->style_manager); g_clear_pointer (&self->osks, g_hash_table_destroy); + g_clear_pointer (&self->surround_before, g_free); G_OBJECT_CLASS (pos_input_surface_parent_class)->finalize (object); } @@ -2072,6 +2132,7 @@ pos_input_surface_init (PosInputSurface *self) gtk_widget_init_template (GTK_WIDGET (self)); + self->bs_mode = POS_BACKSPACE_MODE_CHAR; self->style_manager = pos_style_manager_new (); self->action_map = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (self->action_map), -- GitLab