From ddcbb5b1c89ccc0dfe43ba27ee665510764f5cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Sep 2025 17:55:07 +0200 Subject: [PATCH 1/8] treewide: Use gm_str_is_null_or_empty() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's drop our custom macro Signed-off-by: Guido Günther Part-of: --- src/contrib/util.h | 2 -- src/pos-completer-manager.c | 4 ++-- src/pos-completer.c | 8 +++++--- src/pos-input-surface.c | 2 +- src/pos-osk-widget.c | 6 ++++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/contrib/util.h b/src/contrib/util.h index e0a39d8..09b5dea 100644 --- a/src/contrib/util.h +++ b/src/contrib/util.h @@ -10,8 +10,6 @@ #include #include -#define STR_IS_NULL_OR_EMPTY(x) ((x) == NULL || (x)[0] == '\0') - #define phosh_async_error_warn(err, ...) \ phosh_error_warnv (G_LOG_DOMAIN, err, G_IO_ERROR, G_IO_ERROR_CANCELLED, __VA_ARGS__) diff --git a/src/pos-completer-manager.c b/src/pos-completer-manager.c index f38724c..1a5dc31 100644 --- a/src/pos-completer-manager.c +++ b/src/pos-completer-manager.c @@ -23,7 +23,7 @@ # include "completers/pos-completer-varnam.h" #endif -#include "contrib/util.h" +#include "gmobile.h" #include @@ -144,7 +144,7 @@ on_default_completer_changed (PosCompleterManager *self) g_assert (POS_IS_COMPLETER_MANAGER (self)); default_name = g_settings_get_string (self->settings, "default"); - if (!STR_IS_NULL_OR_EMPTY (default_name)) { + if (!gm_str_is_null_or_empty (default_name)) { default_ = init_completer (self, default_name, &err); if (default_ == NULL) { g_critical ("Failed to init default completer '%s': %s", default_name, diff --git a/src/pos-completer.c b/src/pos-completer.c index eee45c5..fbc25d0 100644 --- a/src/pos-completer.c +++ b/src/pos-completer.c @@ -15,6 +15,8 @@ #include "pos-completer-priv.h" #include "util.h" +#include "gmobile.h" + #include /** @@ -460,7 +462,7 @@ pos_completer_grab_last_word (const char *text, char **new_text, char **word) g_return_val_if_fail (word && *word == NULL, FALSE); /* Nothing to parse */ - if (STR_IS_NULL_OR_EMPTY (text)) + if (gm_str_is_null_or_empty (text)) return FALSE; /* text ends with whitespace */ @@ -509,7 +511,7 @@ pos_completer_find_prev_word_break (const char *text) g_autofree char *symbol = NULL; /* Nothing to parse */ - if (STR_IS_NULL_OR_EMPTY (text)) + if (gm_str_is_null_or_empty (text)) return -1; len = g_utf8_strlen (text, -1); @@ -563,7 +565,7 @@ pos_completer_capitalize_by_template (const char *template, const GStrv completi g_autoptr (GStrvBuilder) builder = NULL; g_autofree gunichar *utemplate = NULL; - if (completions == NULL || STR_IS_NULL_OR_EMPTY (template)) + if (completions == NULL || gm_str_is_null_or_empty (template)) return g_strdupv (completions); utemplate = g_utf8_to_ucs4_fast (template, -1, &templ_len); diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 35d24ca..9092b62 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -1893,7 +1893,7 @@ build_layout_name (const char *engine, const char *layout, const char *variant) { char *name; - if (STR_IS_NULL_OR_EMPTY (variant)) + if (gm_str_is_null_or_empty (variant)) name = g_strdup_printf ("%s:%s", engine, layout); else name = g_strdup_printf ("%s:%s+%s", engine, layout, variant); diff --git a/src/pos-osk-widget.c b/src/pos-osk-widget.c index 7721df2..229d9ae 100644 --- a/src/pos-osk-widget.c +++ b/src/pos-osk-widget.c @@ -20,6 +20,8 @@ #include "pos-osk-key.h" #include "pos-osk-widget.h" +#include "gmobile.h" + #include #include @@ -1896,7 +1898,7 @@ parse_lang (PosOskWidget *self, const char *layout, const char *variant) /* Keyboard layout has language (`en`), region is from layout (`us`) */ self->lang = g_strdup (self->layout.locale); - if (STR_IS_NULL_OR_EMPTY (variant)) { + if (gm_str_is_null_or_empty (variant)) { self->region = g_strdup (layout); return; } @@ -1950,7 +1952,7 @@ pos_osk_widget_set_layout (PosOskWidget *self, g_free (self->layout_id); self->layout_id = g_strdup (layout_id); - if (!STR_IS_NULL_OR_EMPTY (variant)) + if (!gm_str_is_null_or_empty (variant)) path = g_strdup_printf ("/mobi/phosh/stevia/layouts/%s+%s.json", layout, variant); else path = g_strdup_printf ("/mobi/phosh/stevia/layouts/%s.json", layout); -- GitLab From 7907a65d045b1b4aef3c015445fc5641a4c4d914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sun, 21 Sep 2025 20:17:11 +0200 Subject: [PATCH 2/8] completers/presage: Remove unused future MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- src/completers/pos-completer-presage.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/completers/pos-completer-presage.c b/src/completers/pos-completer-presage.c index b733359..515a7a5 100644 --- a/src/completers/pos-completer-presage.c +++ b/src/completers/pos-completer-presage.c @@ -68,7 +68,6 @@ struct _PosCompleterPresage { presage_t presage; char *presage_past; - char *presage_future; char *dict_dir; char *lang; @@ -323,7 +322,6 @@ pos_completer_presage_finalize (GObject *object) g_clear_pointer (&self->completions, g_strfreev); g_string_free (self->preedit, TRUE); g_clear_pointer (&self->presage_past, g_free); - g_clear_pointer (&self->presage_future, g_free); g_clear_pointer (&self->lang, g_free); g_clear_pointer (&self->dict_dir, g_free); presage_free (self->presage); -- GitLab From 51eeec9ba14bc9a24317e3b4d15abda7cc57862e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sun, 21 Sep 2025 20:24:31 +0200 Subject: [PATCH 3/8] completers/presage: Remove unused headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- src/completers/pos-completer-presage.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/completers/pos-completer-presage.c b/src/completers/pos-completer-presage.c index 515a7a5..f6e4e35 100644 --- a/src/completers/pos-completer-presage.c +++ b/src/completers/pos-completer-presage.c @@ -15,9 +15,6 @@ #include "pos-completer-base.h" #include "pos-completer-priv.h" #include "pos-completer-presage.h" -#include "pos-emoji-db.h" - -#include "util.h" #include -- GitLab From 1da0a412b44dbbf1f11aa46b7eb6f97b6d2bf75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sun, 21 Sep 2025 22:47:28 +0200 Subject: [PATCH 4/8] completers/presage: Use self for non public function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need to use the interface here Signed-off-by: Guido Günther Part-of: --- src/completers/pos-completer-presage.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/completers/pos-completer-presage.c b/src/completers/pos-completer-presage.c index f6e4e35..59af547 100644 --- a/src/completers/pos-completer-presage.c +++ b/src/completers/pos-completer-presage.c @@ -85,11 +85,10 @@ G_DEFINE_TYPE_WITH_CODE (PosCompleterPresage, pos_completer_presage, POS_TYPE_CO static void -pos_completer_presage_set_completions (PosCompleter *iface, - GStrv completions, - gboolean additional_sources) +pos_completer_presage_set_completions (PosCompleterPresage *self, + GStrv completions, + gboolean additional_sources) { - PosCompleterPresage *self = POS_COMPLETER_PRESAGE (iface); g_auto (GStrv) additional_results = NULL; g_auto (GStrv) caps_completions = NULL; g_autoptr (GStrvBuilder) builder = g_strv_builder_new (); @@ -120,10 +119,10 @@ pos_completer_presage_predict (PosCompleterPresage *self) result = presage_predict (self->presage, &completions); if (result == PRESAGE_OK) { - pos_completer_presage_set_completions (POS_COMPLETER (self), completions, TRUE); + pos_completer_presage_set_completions (self, completions, TRUE); } else { g_warning ("Failed to complete %s", self->preedit->str); - pos_completer_presage_set_completions (POS_COMPLETER (self), NULL, FALSE); + pos_completer_presage_set_completions (self, NULL, FALSE); } } @@ -150,7 +149,7 @@ pos_completer_presage_set_preedit (PosCompleter *iface, const char *preedit) g_string_append (self->preedit, preedit); else { /* No string: reset completions */ - pos_completer_presage_set_completions (POS_COMPLETER (self), NULL, FALSE); + pos_completer_presage_set_completions (self, NULL, FALSE); } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PREEDIT]); -- GitLab From 1119172c85bc21587ce83f9827d5121e5fcd2bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Sep 2025 17:37:06 +0200 Subject: [PATCH 5/8] completer: Add interface to lookup completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What should be displayed as completion to the user is not necessarily what needs to be appended as text. E.g. we want to show the full word while we only append the remaining bit. Add an interface so completer users can lookup the "better" result. Signed-off-by: Guido Günther Part-of: --- src/pos-completer.c | 26 ++++++++++++++++++++++++++ src/pos-completer.h | 2 ++ 2 files changed, 28 insertions(+) diff --git a/src/pos-completer.c b/src/pos-completer.c index fbc25d0..17cdc04 100644 --- a/src/pos-completer.c +++ b/src/pos-completer.c @@ -206,6 +206,32 @@ pos_completer_get_completions (PosCompleter *self) return completions; } +/** + * pos_completer_lookup_completion: + * @self: the completer + * @completion: The completion to lookup + * + * Given a given completion string lookup what should actually + * be completed. This allows completers to e.g. provide the full + * word as completion "handle" and fill in just the last chars as + * actual completions. + * + * Returns: the actual completion string + */ +const char * +pos_completer_lookup_completion (PosCompleter *self, const char *completion) +{ + PosCompleterInterface *iface; + + g_return_val_if_fail (POS_IS_COMPLETER (self), NULL); + + iface = POS_COMPLETER_GET_IFACE (self); + if (!iface->lookup_completion) + return NULL; + + return (iface->lookup_completion)(self, completion); +} + /** * pos_completer_get_preedit: * @self: the completer diff --git a/src/pos-completer.h b/src/pos-completer.h index d8e9a0a..25a63a9 100644 --- a/src/pos-completer.h +++ b/src/pos-completer.h @@ -56,12 +56,14 @@ struct _PosCompleterInterface GError **error); char * (*get_display_name) (PosCompleter *self); void (*learn_accepted) (PosCompleter *self, const char *word); + const char * (*lookup_completion) (PosCompleter *self, const char *completion); }; /* Used by completion users */ const char *pos_completer_get_name (PosCompleter *self); gboolean pos_completer_feed_symbol (PosCompleter *self, const char *symbol); GStrv pos_completer_get_completions (PosCompleter *self); +const char *pos_completer_lookup_completion (PosCompleter *self, const char *completion); const char *pos_completer_get_preedit (PosCompleter *self); void pos_completer_set_preedit (PosCompleter *self, const char *preedit); void pos_completer_set_surrounding_text (PosCompleter *self, -- GitLab From 3af1cd2fb5c135b481eec2cfec29b2fd0e7ab7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Sep 2025 17:38:40 +0200 Subject: [PATCH 6/8] input-surface: Lookup better completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- src/pos-input-surface.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pos-input-surface.c b/src/pos-input-surface.c index 9092b62..fd6a4cd 100644 --- a/src/pos-input-surface.c +++ b/src/pos-input-surface.c @@ -409,12 +409,18 @@ static void on_completion_selected (PosInputSurface *self, const char *completion) { g_autofree char *send = NULL; + const char *lookup; g_return_if_fail (POS_IS_INPUT_SURFACE (self)); g_return_if_fail (completion != NULL); - g_debug ("completion: %s", completion); - send = g_strdup_printf ("%s ", completion); + lookup = pos_completer_lookup_completion (self->completer, completion); + g_debug ("completion: %s -> lookup: %s", completion, lookup); + + if (lookup) + send = g_strdup (lookup); + else + send = g_strdup_printf ("%s ", completion); pos_input_method_send_string (self->input_method, send, TRUE); -- GitLab From 26de828642c149c5a0d49eb3215b63b5e0008318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Sep 2025 16:17:20 +0200 Subject: [PATCH 7/8] completers/presage: Lookup better completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Presage knows the difference between the prediction and what should actually be completed. So if the user e.g. types "time" then the predictions are "time" and "timetable". The actual completions are " " and "table ". Make use of that feature so e.g. deleting backwards into a word works better. Signed-off-by: Guido Günther Part-of: --- src/completers/pos-completer-presage.c | 38 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/completers/pos-completer-presage.c b/src/completers/pos-completer-presage.c index 59af547..3ef977b 100644 --- a/src/completers/pos-completer-presage.c +++ b/src/completers/pos-completer-presage.c @@ -61,6 +61,7 @@ struct _PosCompleterPresage { char *name; GString *preedit; GStrv completions; + GHashTable *compmap; /* key: displayed completion, value: actual completion to use */ guint max_completions; presage_t presage; @@ -114,12 +115,29 @@ static void pos_completer_presage_predict (PosCompleterPresage *self) { presage_error_code_t result; - g_auto (GStrv) completions = NULL; + g_auto (GStrv) predictions = NULL; - result = presage_predict (self->presage, &completions); + result = presage_predict (self->presage, &predictions); if (result == PRESAGE_OK) { - pos_completer_presage_set_completions (self, completions, TRUE); + g_autoptr (GStrvBuilder) builder = g_strv_builder_new (); + g_auto (GStrv) completions = NULL; + + g_hash_table_remove_all (self->compmap); + for (int i = 0; predictions[i]; i++) { + g_autofree char *str = NULL; + g_autofree char *compl = NULL; + + result = presage_completion (self->presage, predictions[i], &str); + if (result != PRESAGE_OK) + continue; + + /* preedit is part of `presage_past` so we need to add it to the completion */ + compl = g_strdup_printf ("%s%s", self->preedit->str, str); + /* FIXME: this breaks when we capitalize later on */ + g_hash_table_insert (self->compmap, g_strdup (predictions[i]), g_steal_pointer (&compl)); + } + pos_completer_presage_set_completions (self, predictions, TRUE); } else { g_warning ("Failed to complete %s", self->preedit->str); pos_completer_presage_set_completions (self, NULL, FALSE); @@ -263,6 +281,17 @@ pos_completer_presage_set_language (PosCompleter *completer, } +static const char * +pos_completer_presage_lookup_completion (PosCompleter *completer, const char *completion) +{ + PosCompleterPresage *self = POS_COMPLETER_PRESAGE (completer); + const char *lookup; + + lookup = g_hash_table_lookup (self->compmap, completion); + return lookup ? lookup : completion; +} + + static void pos_completer_presage_set_property (GObject *object, guint property_id, @@ -316,6 +345,7 @@ pos_completer_presage_finalize (GObject *object) PosCompleterPresage *self = POS_COMPLETER_PRESAGE (object); g_clear_pointer (&self->completions, g_strfreev); + g_clear_pointer (&self->compmap, g_hash_table_unref); g_string_free (self->preedit, TRUE); g_clear_pointer (&self->presage_past, g_free); g_clear_pointer (&self->lang, g_free); @@ -487,6 +517,7 @@ pos_completer_presage_interface_init (PosCompleterInterface *iface) iface->set_preedit = pos_completer_presage_set_preedit; iface->set_surrounding_text = pos_completer_presage_set_surrounding_text; iface->set_language = pos_completer_presage_set_language; + iface->lookup_completion = pos_completer_presage_lookup_completion; } @@ -496,6 +527,7 @@ pos_completer_presage_init (PosCompleterPresage *self) self->max_completions = MAX_COMPLETIONS; self->preedit = g_string_new (NULL); self->name = "presage"; + self->compmap = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } /** -- GitLab From 94ff67c6934f8424faa5bd391a0aa7e56f21e137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Sep 2025 18:24:54 +0200 Subject: [PATCH 8/8] completers/presage: Handle capitalized completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to transform the compmap in this case. We do that past captialization. Signed-off-by: Guido Günther Part-of: --- src/completers/pos-completer-presage.c | 49 +++++++++++++++++++++----- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/completers/pos-completer-presage.c b/src/completers/pos-completer-presage.c index 3ef977b..2ac03a7 100644 --- a/src/completers/pos-completer-presage.c +++ b/src/completers/pos-completer-presage.c @@ -85,10 +85,40 @@ G_DEFINE_TYPE_WITH_CODE (PosCompleterPresage, pos_completer_presage, POS_TYPE_CO ) +static void +pos_completer_presage_update_compmap (PosCompleterPresage *self, + GStrv completions, + GStrv caps_completions, + GHashTable *compmap) +{ + g_hash_table_remove_all (self->compmap); + + if (g_strv_length (completions) != g_strv_length (caps_completions)) { + g_warning ("Capitalized completions don't match: %d != %d", + g_strv_length (completions), + g_strv_length (caps_completions)); + return; + } + + if (!compmap) + return; + + /* Transform the keys from lowercase to their capitalized versions */ + for (int i = 0; completions[i]; i++) { + gpointer value; + + /* No need to free key as passed in `compmap` doesn't have key ownership */ + if (g_hash_table_steal_extended (compmap, completions[i], NULL, &value)) + g_hash_table_insert (self->compmap, g_strdup (caps_completions[i]), value); + } +} + + static void pos_completer_presage_set_completions (PosCompleterPresage *self, GStrv completions, - gboolean additional_sources) + gboolean additional_sources, + GHashTable *compmap) { g_auto (GStrv) additional_results = NULL; g_auto (GStrv) caps_completions = NULL; @@ -100,8 +130,11 @@ pos_completer_presage_set_completions (PosCompleterPresage *self, MAX_ADDITIONAL_RESULTS); caps_completions = pos_completer_capitalize_by_template (self->preedit->str, completions); - if (caps_completions) + + if (caps_completions) { g_strv_builder_addv (builder, (const char **)caps_completions); + pos_completer_presage_update_compmap (self, completions, caps_completions, compmap); + } if (additional_results) g_strv_builder_addv (builder, (const char **)additional_results); g_strfreev (self->completions); @@ -122,8 +155,9 @@ pos_completer_presage_predict (PosCompleterPresage *self) if (result == PRESAGE_OK) { g_autoptr (GStrvBuilder) builder = g_strv_builder_new (); g_auto (GStrv) completions = NULL; + g_autoptr (GHashTable) compmap = NULL; - g_hash_table_remove_all (self->compmap); + compmap = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); for (int i = 0; predictions[i]; i++) { g_autofree char *str = NULL; g_autofree char *compl = NULL; @@ -134,13 +168,12 @@ pos_completer_presage_predict (PosCompleterPresage *self) /* preedit is part of `presage_past` so we need to add it to the completion */ compl = g_strdup_printf ("%s%s", self->preedit->str, str); - /* FIXME: this breaks when we capitalize later on */ - g_hash_table_insert (self->compmap, g_strdup (predictions[i]), g_steal_pointer (&compl)); + g_hash_table_insert (compmap, predictions[i], g_steal_pointer (&compl)); } - pos_completer_presage_set_completions (self, predictions, TRUE); + pos_completer_presage_set_completions (self, predictions, TRUE, compmap); } else { g_warning ("Failed to complete %s", self->preedit->str); - pos_completer_presage_set_completions (self, NULL, FALSE); + pos_completer_presage_set_completions (self, NULL, FALSE, NULL); } } @@ -167,7 +200,7 @@ pos_completer_presage_set_preedit (PosCompleter *iface, const char *preedit) g_string_append (self->preedit, preedit); else { /* No string: reset completions */ - pos_completer_presage_set_completions (self, NULL, FALSE); + pos_completer_presage_set_completions (self, NULL, FALSE, NULL); } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PREEDIT]); -- GitLab