From 2a7d1758ae65f4be470680516e093b321b196dcb Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 19 Sep 2023 19:59:18 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat(linux):=20Store=20user-added=20keyboar?= =?UTF-8?q?ds=20in=20GSettings=20=F0=9F=8F=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- linux/ibus-keyman/src/keymanutil.c | 16 ++ linux/ibus-keyman/src/keymanutil.h | 3 + linux/ibus-keyman/src/keymanutil_internal.h | 2 + linux/ibus-keyman/src/test/keymanutil_tests.c | 156 ++++++++++++++++++ .../resources/com.keyman.gschema.xml | 9 + 5 files changed, 186 insertions(+) diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 4f797aa07b9..f0baa3b0f04 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -460,6 +460,22 @@ keyman_put_options_todconf(gchar *package_id, // kvp got assigned to options[x] and so gets freed when options are freed } +gchar** +keyman_get_custom_keyboards() { + g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); + gchar **result = g_settings_get_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY); + if (result && result[0] == NULL) { + g_strfreev(result); + return NULL; + } + return result; +} + +void +keyman_set_custom_keyboards(gchar ** keyboards) { + g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); + g_settings_set_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY, (const gchar *const *)keyboards); +} #ifdef DEBUG #include diff --git a/linux/ibus-keyman/src/keymanutil.h b/linux/ibus-keyman/src/keymanutil.h index de1aef49fab..8a3f4506faa 100644 --- a/linux/ibus-keyman/src/keymanutil.h +++ b/linux/ibus-keyman/src/keymanutil.h @@ -68,6 +68,9 @@ #define KEYMAN_DCONF_OPTIONS_PATH "/desktop/ibus/keyman/options/" #define KEYMAN_DCONF_OPTIONS_KEY "options" +#define KEYMAN_DCONF_ENGINE_NAME "com.keyman.engine" +#define KEYMAN_DCONF_ENGINE_PATH "/com/keyman/engine/" +#define KEYMAN_DCONF_KEYBOARDS_KEY "additional-keyboards" G_BEGIN_DECLS diff --git a/linux/ibus-keyman/src/keymanutil_internal.h b/linux/ibus-keyman/src/keymanutil_internal.h index 614bf737121..62122c41ea4 100644 --- a/linux/ibus-keyman/src/keymanutil_internal.h +++ b/linux/ibus-keyman/src/keymanutil_internal.h @@ -33,6 +33,8 @@ IBusEngineDesc *get_engine_for_language( gchar *lang_id, gchar *lang_name); +gchar** keyman_get_custom_keyboards(); +void keyman_set_custom_keyboards(gchar ** keyboards); void keyman_add_keyboard(gpointer data, gpointer user_data); void keyman_add_keyboards_from_dir(gpointer data, gpointer user_data); diff --git a/linux/ibus-keyman/src/test/keymanutil_tests.c b/linux/ibus-keyman/src/test/keymanutil_tests.c index 2be4c38bf26..cff899fb8c9 100644 --- a/linux/ibus-keyman/src/test/keymanutil_tests.c +++ b/linux/ibus-keyman/src/test/keymanutil_tests.c @@ -30,6 +30,24 @@ _get_tst_options_key(gchar* testname) { return result; } +void +_delete_tst_kbds_key() { + g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); + g_settings_reset(settings, KEYMAN_DCONF_KEYBOARDS_KEY); +} + +void +_set_tst_kbds_key(gchar** keyboards) { + g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); + g_settings_set_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY, (const gchar* const*)keyboards); +} + +gchar** +_get_tst_kbds_key() { + g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); + return g_settings_get_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY); +} + kmp_keyboard* _get_tst_kmp_keyboard(gchar* version, gchar** languages) { kmp_keyboard* keyboard = g_new0(kmp_keyboard, 1); @@ -177,6 +195,133 @@ test_keyman_put_options_todconf__existing_key() { _delete_tst_options_key(testname); } +//---------------------------------------------------------------------------------------------- +void +test_keyman_set_custom_keyboards__new_key() { + // Initialize + delete_kbds_key(); + gchar* keyboards[] = {"fr:/tmp/test/test.kmx", NULL}; + + // Execute + keyman_set_custom_keyboards(keyboards); + + // Verify + g_auto(GStrv) result = get_kbds_key(); + g_assert_nonnull(result); + g_assert_cmpstrv(result, keyboards); + + // Cleanup + delete_kbds_key(); +} + +void +test_keyman_set_custom_keyboards__overwrite_key() { + // Initialize + gchar* initialKbds[] = {"fr:/tmp/test/test.kmx", NULL}; + set_kbds_key(initialKbds); + + gchar* keyboards[] = {"fr:/tmp/test/test.kmx", "en:/tmp/foo/foo.kmx", NULL}; + + // Execute + keyman_set_custom_keyboards(keyboards); + + // Verify + g_auto(GStrv) result = get_kbds_key(); + g_assert_nonnull(result); + g_assert_cmpstrv(result, keyboards); + + // Cleanup + delete_kbds_key(); +} + +void +test_keyman_set_custom_keyboards__delete_key_NULL() { + // Initialize + gchar* initialKbds[] = {"fr:/tmp/test/test.kmx", NULL}; + set_kbds_key(initialKbds); + + // Execute + keyman_set_custom_keyboards(NULL); + + // Verify + g_auto(GStrv) result = get_kbds_key(); + gchar** expected[] = {NULL}; + g_assert_nonnull(result); + g_assert_cmpstrv(result, expected); + + // Cleanup + delete_kbds_key(); +} + +void +test_keyman_set_custom_keyboards__delete_key_empty_array() { + // Initialize + gchar* initialKbds[] = {"fr:/tmp/test/test.kmx", NULL}; + set_kbds_key(initialKbds); + + gchar* keyboards[] = {NULL}; + + // Execute + keyman_set_custom_keyboards(keyboards); + + // Verify + g_auto(GStrv) result = get_kbds_key(); + g_assert_nonnull(result); + g_assert_cmpstrv(result, keyboards); + + // Cleanup + delete_kbds_key(); +} + +//---------------------------------------------------------------------------------------------- +void +test_keyman_get_custom_keyboards__value() { + // Initialize + gchar* keyboards[] = {"fr:/tmp/test/test.kmx", NULL}; + set_kbds_key(keyboards); + + // Execute + g_auto(GStrv) result = keyman_get_custom_keyboards(); + + // Verify + g_assert_nonnull(result); + g_assert_cmpstrv(result, keyboards); + + // Cleanup + delete_kbds_key(); +} + +void +test_keyman_get_custom_keyboards__no_key() { + // Initialize + delete_kbds_key(); + + // Execute + g_auto(GStrv) result = keyman_get_custom_keyboards(); + + // Verify + g_assert_null(result); + + // Cleanup + delete_kbds_key(); +} + +void +test_keyman_get_custom_keyboards__empty() { + // Initialize + gchar* keyboards[] = {NULL}; + set_kbds_key(keyboards); + + // Execute + g_auto(GStrv) result = keyman_get_custom_keyboards(); + + // Verify + g_assert_null(result); + + // Cleanup + delete_kbds_key(); +} + //---------------------------------------------------------------------------------------------- void test_ibus_keyman_engine_desc_new__all_set() { @@ -655,6 +800,17 @@ int main(int argc, char* argv[]) { g_test_add_func("/keymanutil/keyman_put_options_todconf/other_keys", test_keyman_put_options_todconf__other_keys); g_test_add_func("/keymanutil/keyman_put_options_todconf/existing_key", test_keyman_put_options_todconf__existing_key); + g_test_add_func("/keymanutil/keyman_set_custom_keyboards/new_key", test_keyman_set_custom_keyboards__new_key); + g_test_add_func("/keymanutil/keyman_set_custom_keyboards/overwrite_key", test_keyman_set_custom_keyboards__overwrite_key); + g_test_add_func("/keymanutil/keyman_set_custom_keyboards/delete_key_NULL", test_keyman_set_custom_keyboards__delete_key_NULL); + g_test_add_func( + "/keymanutil/keyman_set_custom_keyboards/delete_key_empty_array", + test_keyman_set_custom_keyboards__delete_key_empty_array); + + g_test_add_func("/keymanutil/keyman_get_custom_keyboards/value", test_keyman_get_custom_keyboards__value); + g_test_add_func("/keymanutil/keyman_get_custom_keyboards/no_key", test_keyman_get_custom_keyboards__no_key); + g_test_add_func("/keymanutil/keyman_get_custom_keyboards/empty", test_keyman_get_custom_keyboards__empty); + g_test_add_func("/keymanutil/ibus_keyman_engine_desc_new/all_set", test_ibus_keyman_engine_desc_new__all_set); g_test_add_func("/keymanutil/ibus_keyman_engine_desc_new/only_description", test_ibus_keyman_engine_desc_new__only_description); g_test_add_func("/keymanutil/ibus_keyman_engine_desc_new/only_copyright", test_ibus_keyman_engine_desc_new__only_copyright); diff --git a/linux/keyman-config/resources/com.keyman.gschema.xml b/linux/keyman-config/resources/com.keyman.gschema.xml index df50060d8c4..c286687f407 100644 --- a/linux/keyman-config/resources/com.keyman.gschema.xml +++ b/linux/keyman-config/resources/com.keyman.gschema.xml @@ -16,4 +16,13 @@ List of strings + + + + [] + User-added additional language/keyboards + This setting contains installed keyboards for bcp47 codes + that are not listed in the keyboard metadata. + + From 8e1ee9ea7b88d0777a4d5e404a2570176f4df3b6 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 10 Oct 2023 17:54:59 +0200 Subject: [PATCH 2/4] =?UTF-8?q?feat(linux):=20Allow=20loading=20of=20keybo?= =?UTF-8?q?ards=20with=20arbitrary=20language=20=F0=9F=8F=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #8598. --- linux/ibus-keyman/src/keymanutil.c | 158 +++++++-- linux/ibus-keyman/src/keymanutil_internal.h | 9 +- linux/ibus-keyman/src/kmpdetails.c | 2 + linux/ibus-keyman/src/test/keymanutil_tests.c | 332 +++++++++++++++--- 4 files changed, 417 insertions(+), 84 deletions(-) diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index f0baa3b0f04..1ce807ff7e9 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -65,6 +65,22 @@ #define N_(text) text +GHashTable *custom_keyboards = NULL; + +void free_cust_kbd(gpointer data) { + if (data == NULL) + return; + + cust_kbd *kbd_data = (cust_kbd *)data; + + g_free(kbd_data->kb_id_with_lang); + g_free(kbd_data->lang->id); + if (kbd_data->lang->name) + g_free(kbd_data->lang->name); + g_free(kbd_data->lang); + g_free(kbd_data); +} + // change to keyman_get_kmpdirs_fromdir // returns list of directories with kmp.json GList * keyman_get_kmpdirs_fromdir(GList *kmpdir_list, const gchar * path) @@ -151,18 +167,17 @@ get_engine_for_language( kmp_info *info, keyboard_details *kbd_details, gchar *kmp_dir, - gchar *lang_id, - gchar *lang_name) { + kmp_language *lang) { IBusEngineDesc* engine_desc = NULL; - if (!lang_id || !strlen(lang_id)) + if (!lang || !lang->id || !strlen(lang->id)) return engine_desc; int capacity = 255; g_autofree gchar *name_with_lang = NULL; g_autofree gchar *minimized_tag = g_new0(gchar, capacity); - int result = bcp47_minimize(lang_id, minimized_tag, capacity); + int result = bcp47_minimize(lang->id, minimized_tag, capacity); if (result < 0) { - g_strlcpy(minimized_tag, lang_id, capacity); + g_strlcpy(minimized_tag, lang->id, capacity); } g_autofree gchar *lang_code = g_new0(gchar, capacity); @@ -172,11 +187,11 @@ get_engine_for_language( // If ibus doesn't know about the language then append the // language name to the keyboard name - if (lang_name != NULL) { + if (lang->name != NULL) { g_autofree gchar *ibus_lang = ibus_get_untranslated_language_name(lang_code); g_debug("%s: untranslated ibus language for %s: %s", __FUNCTION__, minimized_tag, ibus_lang); if (g_strcmp0(ibus_lang, "Other") == 0) { - name_with_lang = g_strjoin(" - ", keyboard->name, lang_name, NULL); + name_with_lang = g_strjoin(" - ", keyboard->name, lang->name, NULL); } } @@ -198,13 +213,12 @@ get_engine_for_language( return engine_desc; } -// Add a keyboard (ibus engine) to the list of engines -void -keyman_add_keyboard(gpointer data, gpointer user_data) { - kmp_keyboard *keyboard = (kmp_keyboard *)data; - add_keyboard_data *kb_data = (add_keyboard_data *)user_data; - - for (GList *e = kb_data->engines_list; e != NULL; e = e->next) { +gboolean +keyman_list_contains_keyboard( + GList *engines_list, + kmp_keyboard *keyboard +) { + for (GList *e = engines_list; e != NULL; e = e->next) { IBusEngineDesc *engine_desc = (IBusEngineDesc *)e->data; const gchar *version = ibus_engine_desc_get_version(engine_desc); const gchar *engine_name = ibus_engine_desc_get_name(engine_desc); @@ -215,40 +229,90 @@ keyman_add_keyboard(gpointer data, gpointer user_data) { // TODO: fix version comparison (#9593) if (g_strcmp0(kmx_file, keyboard->kmx_file) == 0 && g_strcmp0(version, keyboard->version) >= 0) { g_debug("keyboard %s already exists at version %s which is newer or same as %s", kmx_file, version, keyboard->version); - return; + return TRUE; } } + return FALSE; +} - g_autofree gchar *json_file = g_strjoin(".", keyboard->id, "json", NULL); - g_autoptr(keyboard_details) kbd_details = g_new0(keyboard_details, 1); - get_keyboard_details(kb_data->kmp_dir, json_file, kbd_details); - g_autofree gchar *abs_kmx = g_strjoin("/", kb_data->kmp_dir, keyboard->kmx_file, NULL); +GList * +keyman_add_custom_keyboards( + kmp_keyboard *keyboard, + add_keyboard_data *kb_data, + keyboard_details * kbd_details, + gchar * kmx_path +) { + GList * engines_list = kb_data->engines_list; + GPtrArray *language_keyboards = g_hash_table_lookup(custom_keyboards, kmx_path); + if (language_keyboards == NULL) + return engines_list; + + for (int i = 0; i < language_keyboards->len; i++) { + cust_kbd *data = (cust_kbd *)g_ptr_array_index(language_keyboards, i); + IBusEngineDesc *engine_desc = get_engine_for_language(keyboard, kb_data->info, kbd_details, kb_data->kmp_dir, data->lang); + if (engine_desc) { + engines_list = g_list_append(engines_list, engine_desc); + } + } + return engines_list; +} +GList * +keyman_add_keyboards_for_language_if_given( + kmp_keyboard *keyboard, + add_keyboard_data *kb_data, + keyboard_details * kbd_details, + gchar * kmx_path +) { + GList * engines_list = kb_data->engines_list; if (keyboard->languages != NULL) { for (GList *l = keyboard->languages; l != NULL; l = l->next) { kmp_language *language = (kmp_language *)l->data; IBusEngineDesc *engine_desc = - get_engine_for_language(keyboard, kb_data->info, kbd_details, kb_data->kmp_dir, language->id, language->name); + get_engine_for_language(keyboard, kb_data->info, kbd_details, kb_data->kmp_dir, language); if (engine_desc) { - kb_data->engines_list = g_list_append(kb_data->engines_list, engine_desc); + engines_list = g_list_append(engines_list, engine_desc); } } } else { - g_message("adding engine %s", abs_kmx); - kb_data->engines_list = g_list_append( - kb_data->engines_list, + g_message("adding engine %s", kmx_path); + engines_list = g_list_append( + engines_list, ibus_keyman_engine_desc_new( - abs_kmx, // kmx full path + kmx_path, // kmx full path keyboard->name, // longname kbd_details->description, // description kb_data->info->copyright, // copyright if available NULL, // language, most are ignored by ibus except major languages kbd_details->license, // license kb_data->info->author_desc, // author name only, not email - keyman_get_icon_file(abs_kmx), // icon full path + keyman_get_icon_file(kmx_path), // icon full path "us", // layout defaulting to us (en-US) keyboard->version)); } + return engines_list; +} + +// Add a keyboard (ibus engine) to the list of engines +void +keyman_add_keyboard( + gpointer data, + gpointer user_data +) { + kmp_keyboard *keyboard = (kmp_keyboard *)data; + add_keyboard_data *kb_data = (add_keyboard_data *)user_data; + + if (keyman_list_contains_keyboard(kb_data->engines_list, keyboard)) { + return; + } + + g_autofree gchar *json_file = g_strjoin(".", keyboard->id, "json", NULL); + g_autoptr(keyboard_details) kbd_details = g_new0(keyboard_details, 1); + get_keyboard_details(kb_data->kmp_dir, json_file, kbd_details); + g_autofree gchar *abs_kmx = g_strjoin("/", kb_data->kmp_dir, keyboard->kmx_file, NULL); + + kb_data->engines_list = keyman_add_keyboards_for_language_if_given(keyboard, kb_data, kbd_details, abs_kmx); + kb_data->engines_list = keyman_add_custom_keyboards(keyboard, kb_data, kbd_details, abs_kmx); } // Add keyboards found in {kmp_dir}/kmp.json to engines_list @@ -258,6 +322,7 @@ keyman_add_keyboards_from_dir(gpointer data, gpointer user_data) { GList ** engines_list = (GList **)user_data; g_autoptr(kmp_details) details = g_new0(kmp_details, 1); + if (get_kmp_details(kmp_dir, details) == JSON_OK) { add_keyboard_data kb_data; kb_data.engines_list = *engines_list; @@ -270,13 +335,15 @@ keyman_add_keyboards_from_dir(gpointer data, gpointer user_data) { } GList * -ibus_keyman_list_engines (void) +ibus_keyman_list_engines() { GList *engines = NULL; GList *kmpdir_list; gchar *xdgenv; g_autofree gchar *local_keyboard_path; + custom_keyboards = keyman_get_custom_keyboard_dictionary(); + g_debug("adding from /usr/share/keyman"); kmpdir_list = keyman_get_kmpdirs_fromdir(NULL, "/usr/share/keyman"); g_debug("adding from /usr/local/share/keyman"); @@ -298,7 +365,10 @@ ibus_keyman_list_engines (void) } void -add_engine(gpointer data, gpointer user_data) { +add_engine( + gpointer data, + gpointer user_data +) { IBusEngineDesc *desc = IBUS_ENGINE_DESC(data); IBusComponent *component = IBUS_COMPONENT(user_data); ibus_component_add_engine(component, g_object_ref(desc)); @@ -324,7 +394,6 @@ ibus_keyman_get_component (void) return component; } - // Obtain Keyboard Options list from DConf // DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // @@ -477,6 +546,37 @@ keyman_set_custom_keyboards(gchar ** keyboards) { g_settings_set_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY, (const gchar *const *)keyboards); } +GHashTable * +keyman_get_custom_keyboard_dictionary() { + g_auto(GStrv) custom_keyboards = keyman_get_custom_keyboards(); + if (!custom_keyboards) + return NULL; + + GHashTable *hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + + for (int i = 0; custom_keyboards[i]; i++) { + g_auto(GStrv) keyboard_tokens = g_strsplit(custom_keyboards[i], ":", 2); + + if (keyboard_tokens != NULL && keyboard_tokens[0] != NULL && keyboard_tokens[1] != NULL) { + GPtrArray *language_keyboards = g_hash_table_lookup(hash_table, keyboard_tokens[1]); + if (!language_keyboards) { + language_keyboards = g_ptr_array_new_full(1, free_cust_kbd); + g_hash_table_insert(hash_table, g_strdup(keyboard_tokens[1]), language_keyboards); + } + + cust_kbd *data = g_new0(cust_kbd, 1); + data->kb_id_with_lang = g_strdup(custom_keyboards[i]); + data->lang = g_new0(kmp_language, 1); + data->lang->id = g_strdup(keyboard_tokens[0]); + g_ptr_array_add(language_keyboards, data); + } else { + g_debug("%s: Invalid keyboard: '%s'", __FUNCTION__, custom_keyboards[i]); + } + } + + return hash_table; +} + #ifdef DEBUG #include diff --git a/linux/ibus-keyman/src/keymanutil_internal.h b/linux/ibus-keyman/src/keymanutil_internal.h index 62122c41ea4..5b9495519d3 100644 --- a/linux/ibus-keyman/src/keymanutil_internal.h +++ b/linux/ibus-keyman/src/keymanutil_internal.h @@ -13,6 +13,11 @@ typedef struct { gchar *kmp_dir; } add_keyboard_data; +typedef struct { + kmp_language *lang; + gchar *kb_id_with_lang; +} cust_kbd; + IBusEngineDesc *ibus_keyman_engine_desc_new( gchar *file_name, gchar *name, @@ -30,11 +35,11 @@ IBusEngineDesc *get_engine_for_language( kmp_info *info, keyboard_details *kbd_details, gchar *kmp_dir, - gchar *lang_id, - gchar *lang_name); + kmp_language *lang); gchar** keyman_get_custom_keyboards(); void keyman_set_custom_keyboards(gchar ** keyboards); +GHashTable * keyman_get_custom_keyboard_dictionary(); void keyman_add_keyboard(gpointer data, gpointer user_data); void keyman_add_keyboards_from_dir(gpointer data, gpointer user_data); diff --git a/linux/ibus-keyman/src/kmpdetails.c b/linux/ibus-keyman/src/kmpdetails.c index 4748fc08bd4..ba6e26faea8 100644 --- a/linux/ibus-keyman/src/kmpdetails.c +++ b/linux/ibus-keyman/src/kmpdetails.c @@ -229,6 +229,7 @@ void kmp_files_foreach (JsonArray *array, } } +// Get kmp_details from `.json` kmp_json_status get_keyboard_details(const char *kmp_dir, const char *id, keyboard_details *keyboard) { g_autoptr(JsonParser) parser; @@ -267,6 +268,7 @@ kmp_json_status get_keyboard_details(const char *kmp_dir, const char *id, keyboa return JSON_OK; } +// Get kmp_details from `kmp.json` file kmp_json_status get_kmp_details(const char *kmp_dir, kmp_details *details) { g_autoptr(JsonParser) parser; diff --git a/linux/ibus-keyman/src/test/keymanutil_tests.c b/linux/ibus-keyman/src/test/keymanutil_tests.c index cff899fb8c9..bbecde06c3b 100644 --- a/linux/ibus-keyman/src/test/keymanutil_tests.c +++ b/linux/ibus-keyman/src/test/keymanutil_tests.c @@ -49,12 +49,13 @@ _get_tst_kbds_key() { } kmp_keyboard* -_get_tst_kmp_keyboard(gchar* version, gchar** languages) { +_get_tst_kmp_keyboard(gchar* version, gchar** languages, gchar* id) { + g_assert(id != NULL); kmp_keyboard* keyboard = g_new0(kmp_keyboard, 1); keyboard->name = g_strdup("Testing"); - keyboard->id = g_strdup("tst"); + keyboard->id = g_strdup(id); keyboard->version = g_strdup(version); - keyboard->kmx_file = g_strdup("tst.kmx"); + keyboard->kmx_file = g_strdup_printf("%s.kmx", id); keyboard->kvk_file = g_strdup(""); keyboard->languages = NULL; for (gchar* lang = *languages++; lang; lang = *languages++) { @@ -106,6 +107,22 @@ _free_tst_kb_data(add_keyboard_data* kb_data) { G_DEFINE_AUTOPTR_CLEANUP_FUNC(add_keyboard_data, _free_tst_kb_data) +// Newer glib versions have g_assert_cmpstrv which would allow to do +// g_assert_cmpstrv(result, keyboards); +// but unfortunately Ubuntu 20.04 Focal doesn't have that, so we roll +// our own +void +_kmn_assert_cmpstrv(gchar** result, gchar** expected) { + g_assert_nonnull(result); + g_assert_nonnull(expected); + int i = 0; + for (; result[i] && expected[i]; i++) { + g_assert_cmpstr(result[i], ==, expected[i]); + } + g_assert_null(result[i]); + g_assert_null(expected[i]); +} + //---------------------------------------------------------------------------------------------- void test_keyman_put_options_todconf__invalid() { @@ -195,30 +212,107 @@ test_keyman_put_options_todconf__existing_key() { _delete_tst_options_key(testname); } +//---------------------------------------------------------------------------------------------- +void +test_keyman_get_custom_keyboard_dictionary__values() { + // Initialize + gchar* keyboards[] = {"fr:/tmp/test/test.kmx", "en:/tmp/foo/foo.kmx", "fr:/tmp/foo/foo.kmx", NULL}; + _set_tst_kbds_key(keyboards); + + // Execute + g_autoptr(GHashTable) result = keyman_get_custom_keyboard_dictionary(); + + // Verify + GPtrArray* value = g_hash_table_lookup(result, "/tmp/test/test.kmx"); + g_assert_cmpint(value->len, ==, 1); + cust_kbd* pdata = value->pdata[0]; + g_assert_cmpstr(pdata->lang->id, ==, "fr"); + g_assert_cmpstr(pdata->kb_id_with_lang, ==, "fr:/tmp/test/test.kmx"); + + value = g_hash_table_lookup(result, "/tmp/foo/foo.kmx"); + g_assert_cmpint(value->len, ==, 2); + pdata = value->pdata[0]; + g_assert_cmpstr(pdata->lang->id, ==, "en"); + g_assert_cmpstr(pdata->kb_id_with_lang, ==, "en:/tmp/foo/foo.kmx"); + pdata = value->pdata[1]; + g_assert_cmpstr(pdata->lang->id, ==, "fr"); + g_assert_cmpstr(pdata->kb_id_with_lang, ==, "fr:/tmp/foo/foo.kmx"); + + // Cleanup + _delete_tst_kbds_key(); +} + +void +test_keyman_get_custom_keyboard_dictionary__invalid() { + // Initialize + gchar* keyboards[] = {"/tmp/test/test.kmx", NULL}; + _set_tst_kbds_key(keyboards); + + // Execute + g_autoptr(GHashTable) result = keyman_get_custom_keyboard_dictionary(); + + // Verify + g_assert_nonnull(result); + + // Cleanup + _delete_tst_kbds_key(); +} + +void +test_keyman_get_custom_keyboard_dictionary__empty() { + // Initialize + gchar* keyboards[] = {NULL}; + _set_tst_kbds_key(keyboards); + + // Execute + g_autoptr(GHashTable) result = keyman_get_custom_keyboard_dictionary(); + + // Verify + g_assert_null(result); + + // Cleanup + _delete_tst_kbds_key(); +} + +void +test_keyman_get_custom_keyboard_dictionary__null() { + // Initialize + _delete_tst_kbds_key(); + + // Execute + g_autoptr(GHashTable) result = keyman_get_custom_keyboard_dictionary(); + + // Verify + g_assert_null(result); + + // Cleanup + _delete_tst_kbds_key(); +} + //---------------------------------------------------------------------------------------------- void test_keyman_set_custom_keyboards__new_key() { // Initialize - delete_kbds_key(); + _delete_tst_kbds_key(); gchar* keyboards[] = {"fr:/tmp/test/test.kmx", NULL}; // Execute keyman_set_custom_keyboards(keyboards); // Verify - g_auto(GStrv) result = get_kbds_key(); + g_auto(GStrv) result = _get_tst_kbds_key(); g_assert_nonnull(result); - g_assert_cmpstrv(result, keyboards); + _kmn_assert_cmpstrv(result, keyboards); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } void test_keyman_set_custom_keyboards__overwrite_key() { // Initialize gchar* initialKbds[] = {"fr:/tmp/test/test.kmx", NULL}; - set_kbds_key(initialKbds); + _set_tst_kbds_key(initialKbds); gchar* keyboards[] = {"fr:/tmp/test/test.kmx", "en:/tmp/foo/foo.kmx", NULL}; @@ -226,38 +320,38 @@ test_keyman_set_custom_keyboards__overwrite_key() { keyman_set_custom_keyboards(keyboards); // Verify - g_auto(GStrv) result = get_kbds_key(); + g_auto(GStrv) result = _get_tst_kbds_key(); g_assert_nonnull(result); - g_assert_cmpstrv(result, keyboards); + _kmn_assert_cmpstrv(result, keyboards); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } void test_keyman_set_custom_keyboards__delete_key_NULL() { // Initialize gchar* initialKbds[] = {"fr:/tmp/test/test.kmx", NULL}; - set_kbds_key(initialKbds); + _set_tst_kbds_key(initialKbds); // Execute keyman_set_custom_keyboards(NULL); // Verify - g_auto(GStrv) result = get_kbds_key(); - gchar** expected[] = {NULL}; + g_auto(GStrv) result = _get_tst_kbds_key(); + gchar* expected[] = {NULL}; g_assert_nonnull(result); - g_assert_cmpstrv(result, expected); + _kmn_assert_cmpstrv(result, expected); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } void test_keyman_set_custom_keyboards__delete_key_empty_array() { // Initialize gchar* initialKbds[] = {"fr:/tmp/test/test.kmx", NULL}; - set_kbds_key(initialKbds); + _set_tst_kbds_key(initialKbds); gchar* keyboards[] = {NULL}; @@ -265,12 +359,12 @@ test_keyman_set_custom_keyboards__delete_key_empty_array() { keyman_set_custom_keyboards(keyboards); // Verify - g_auto(GStrv) result = get_kbds_key(); + g_auto(GStrv) result = _get_tst_kbds_key(); g_assert_nonnull(result); - g_assert_cmpstrv(result, keyboards); + _kmn_assert_cmpstrv(result, keyboards); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } //---------------------------------------------------------------------------------------------- @@ -278,23 +372,23 @@ void test_keyman_get_custom_keyboards__value() { // Initialize gchar* keyboards[] = {"fr:/tmp/test/test.kmx", NULL}; - set_kbds_key(keyboards); + _set_tst_kbds_key(keyboards); // Execute g_auto(GStrv) result = keyman_get_custom_keyboards(); // Verify g_assert_nonnull(result); - g_assert_cmpstrv(result, keyboards); + _kmn_assert_cmpstrv(result, keyboards); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } void test_keyman_get_custom_keyboards__no_key() { // Initialize - delete_kbds_key(); + _delete_tst_kbds_key(); // Execute g_auto(GStrv) result = keyman_get_custom_keyboards(); @@ -303,14 +397,14 @@ test_keyman_get_custom_keyboards__no_key() { g_assert_null(result); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } void test_keyman_get_custom_keyboards__empty() { // Initialize gchar* keyboards[] = {NULL}; - set_kbds_key(keyboards); + _set_tst_kbds_key(keyboards); // Execute g_auto(GStrv) result = keyman_get_custom_keyboards(); @@ -319,7 +413,7 @@ test_keyman_get_custom_keyboards__empty() { g_assert_null(result); // Cleanup - delete_kbds_key(); + _delete_tst_kbds_key(); } //---------------------------------------------------------------------------------------------- @@ -399,12 +493,13 @@ void test_get_engine_for_language__null_language() { // Initialize gchar* languages[] = {"en:English", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(kmp_info) info = _get_tst_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); g_autoptr(keyboard_details) details = _get_tst_keyboard_details("my description", "MIT"); + kmp_language lang = {NULL, NULL}; // Execute - g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", NULL, NULL); + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", &lang); // Verify g_assert_null(desc); @@ -414,12 +509,13 @@ void test_get_engine_for_language__empty_language() { // Initialize gchar* languages[] = {"en:English", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(kmp_info) info = _get_tst_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); g_autoptr(keyboard_details) details = _get_tst_keyboard_details("my description", "MIT"); + kmp_language lang = {"", ""}; // Execute - g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "", ""); + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", &lang); // Verify g_assert_null(desc); @@ -429,12 +525,13 @@ void test_get_engine_for_language__one_language() { // Initialize gchar* languages[] = {"en:English", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(kmp_info) info = _get_tst_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); g_autoptr(keyboard_details) details = _get_tst_keyboard_details("my description", "MIT"); + kmp_language lang = {"English", "en"}; // Execute - g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "en", "English"); + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", &lang); // Verify g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); @@ -446,12 +543,13 @@ void test_get_engine_for_language__one_unknown_language() { // Initialize gchar* languages[] = {"foo:Foo", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(kmp_info) info = _get_tst_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); g_autoptr(keyboard_details) details = _get_tst_keyboard_details("my description", "MIT"); + kmp_language lang = {"Foo", "foo"}; // Execute - g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "foo", "Foo"); + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", &lang); // Verify g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "foo:/tmp/tst.kmx"); @@ -463,12 +561,13 @@ void test_get_engine_for_language__different_languages() { // Initialize gchar* languages[] = {"en:English", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(kmp_info) info = _get_tst_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); g_autoptr(keyboard_details) details = _get_tst_keyboard_details("my description", "MIT"); + kmp_language lang = {"Foo", "foo"}; // Execute - g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "foo", "Foo"); + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", &lang); // Verify g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "foo:/tmp/tst.kmx"); @@ -480,12 +579,13 @@ void test_get_engine_for_language__no_kbd_language() { // Initialize gchar* languages[] = {NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(kmp_info) info = _get_tst_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); g_autoptr(keyboard_details) details = _get_tst_keyboard_details("my description", "MIT"); + kmp_language lang = {"English", "en"}; // Execute - g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "en", "English"); + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", &lang); // Verify g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); @@ -493,12 +593,15 @@ test_get_engine_for_language__no_kbd_language() { g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "en"); } +extern GHashTable* custom_keyboards; + //---------------------------------------------------------------------------------------------- void test_keyman_add_keyboard__no_language() { // Initialize - gchar* languages[] = {NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + gchar* languages[] = {NULL}; + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); // Execute @@ -514,8 +617,9 @@ test_keyman_add_keyboard__no_language() { void test_keyman_add_keyboard__one_language() { // Initialize - gchar* languages[] = {"en:English", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + gchar* languages[] = {"en:English", NULL}; + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); // Execute @@ -531,8 +635,9 @@ test_keyman_add_keyboard__one_language() { void test_keyman_add_keyboard__two_languages() { // Initialize - gchar* languages[] = {"en:English", "fr:French", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + gchar* languages[] = {"en:English", "fr:French", NULL}; + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); // Execute @@ -549,11 +654,79 @@ test_keyman_add_keyboard__two_languages() { g_assert_null(kb_data->engines_list->next->next); } +void +test_keyman_add_keyboard__one_language_plus_custom() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_hash_table_insert(custom_keyboards, g_strdup("/tmp/tst.kmx"), g_ptr_array_new_full(1, g_free)); + GPtrArray* language_keyboards = g_hash_table_lookup(custom_keyboards, "/tmp/tst.kmx"); + cust_kbd* data = g_new0(cust_kbd, 1); + data->kb_id_with_lang = g_strdup("ldb:/tmp/tst.kmx"); + data->lang = g_new0(kmp_language, 1); + data->lang->id = g_strdup("ldb"); + g_ptr_array_add(language_keyboards, data); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); + g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + g_assert_nonnull(kb_data->engines_list->data); + IBusEngineDesc* desc = (IBusEngineDesc*)kb_data->engines_list->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); + g_assert_nonnull(kb_data->engines_list->next); + g_assert_nonnull(kb_data->engines_list->next->data); + desc = (IBusEngineDesc*)kb_data->engines_list->next->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "ldb:/tmp/tst.kmx"); + g_assert_null(kb_data->engines_list->next->next); +} + +void +test_keyman_add_keyboard__one_language_plus_two_custom() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_hash_table_insert(custom_keyboards, g_strdup("/tmp/tst.kmx"), g_ptr_array_new_full(1, g_free)); + GPtrArray* language_keyboards = g_hash_table_lookup(custom_keyboards, "/tmp/tst.kmx"); + cust_kbd* data = g_new0(cust_kbd, 1); + data->kb_id_with_lang = g_strdup("ldb:/tmp/tst.kmx"); + data->lang = g_new0(kmp_language, 1); + data->lang->id = g_strdup("ldb"); + g_ptr_array_add(language_keyboards, data); + data = g_new0(cust_kbd, 1); + data->kb_id_with_lang = g_strdup("lda:/tmp/tst.kmx"); + data->lang = g_new0(kmp_language, 1); + data->lang->id = g_strdup("lda"); + g_ptr_array_add(language_keyboards, data); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); + g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + g_assert_nonnull(kb_data->engines_list->data); + IBusEngineDesc* desc = (IBusEngineDesc*)kb_data->engines_list->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); + g_assert_nonnull(kb_data->engines_list->next); + g_assert_nonnull(kb_data->engines_list->next->data); + desc = (IBusEngineDesc*)kb_data->engines_list->next->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "ldb:/tmp/tst.kmx"); + g_assert_nonnull(kb_data->engines_list->next); + g_assert_nonnull(kb_data->engines_list->next->data); + desc = (IBusEngineDesc*)kb_data->engines_list->next->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "lda:/tmp/tst.kmx"); + g_assert_null(kb_data->engines_list->next->next); +} + void test_keyman_add_keyboard__prev_engine_adding_same_version() { // Initialize - gchar* languages[] = {"en:English", NULL}; - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages); + gchar* languages[] = {"en:English", NULL}; + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.0", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); IBusEngineDesc* desc = ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.0"); kb_data->engines_list = g_list_append(kb_data->engines_list, desc); @@ -571,8 +744,9 @@ test_keyman_add_keyboard__prev_engine_adding_same_version() { void test_keyman_add_keyboard__prev_engine_adding_newer_version() { // Initialize - gchar* languages[] = {"en:English", "fr:French", NULL}; // New version adds French - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.1", languages); + gchar* languages[] = {"en:English", "fr:French", NULL}; // New version adds French + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.1", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); IBusEngineDesc* desc = ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.0"); @@ -602,8 +776,9 @@ test_keyman_add_keyboard__prev_engine_adding_newer_version_9593() { // the list. // Initialize - gchar* languages[] = {"en:English", "fr:French", NULL}; // New version adds French - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.10", languages); + gchar* languages[] = {"en:English", "fr:French", NULL}; // New version adds French + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("1.10", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); IBusEngineDesc* desc = ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.9"); @@ -630,8 +805,9 @@ test_keyman_add_keyboard__prev_engine_adding_newer_version_9593() { void test_keyman_add_keyboard__prev_engine_adding_older_version() { // Initialize - gchar* languages[] = {"en:English", "fr:French", NULL}; // Old version has additional French - g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("0.9", languages); + gchar* languages[] = {"en:English", "fr:French", NULL}; // Old version has additional French + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + g_autoptr(kmp_keyboard) keyboard = _get_tst_kmp_keyboard("0.9", languages, "tst"); g_autoptr(add_keyboard_data) kb_data = _get_tst_keyboard_data(); IBusEngineDesc* desc = ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.0"); @@ -766,6 +942,50 @@ test_keyman_add_keyboards_from_dir__prev_keyboards() { g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name); } +void +test_keyman_add_keyboards_from_dir__one_language_plus_two_different_custom() { + // Initialize + g_autoptr(GList) keyboards = NULL; + g_autoptr(gchar) kmp_dir = g_dir_make_tmp(NULL, NULL); + copy_test_data(kmp_dir, "kmp1.json"); + + custom_keyboards = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); + gchar* kmx_path = g_strdup_printf("%s/test1.kmx", kmp_dir); + g_hash_table_insert(custom_keyboards, g_strdup(kmx_path), g_ptr_array_new_full(1, g_free)); + GPtrArray* language_keyboards = g_hash_table_lookup(custom_keyboards, kmx_path); + cust_kbd* /* The above code is written in the C programming language. + However, it does not contain any specific instructions or + logic. It only includes the word "data" and three hash + symbols " */ + data = g_new0(cust_kbd, 1); + data->kb_id_with_lang = g_strdup_printf("ldb:%s", kmx_path); + data->lang = g_new0(kmp_language, 1); + data->lang->id = g_strdup("ldb"); + g_ptr_array_add(language_keyboards, data); + + // This custom keyboard will be ignored since we don't have this keyboard installed + g_hash_table_insert(custom_keyboards, g_strdup("/tmp/foo.kmx"), g_ptr_array_new_full(1, g_free)); + language_keyboards = g_hash_table_lookup(custom_keyboards, "/tmp/foo.kmx"); + data = g_new0(cust_kbd, 1); + data->kb_id_with_lang = g_strdup("lda:/tmp/foo.kmx"); + data->lang = g_new0(kmp_language, 1); + data->lang->id = g_strdup("foo"); + g_ptr_array_add(language_keyboards, data); + + // Execute + keyman_add_keyboards_from_dir(kmp_dir, &keyboards); + + // Verify + g_assert_cmpint(g_list_length(keyboards), ==, 2); + + IBusEngineDesc* desc = IBUS_ENGINE_DESC(keyboards->data); + g_autofree gchar* expected_name = g_strdup_printf("bza:%s/test1.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name); + desc = IBUS_ENGINE_DESC(keyboards->next->next->data); + expected_name = g_strdup_printf("ldb:%s/test1.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name); +} + //---------------------------------------------------------------------------------------------- void print_usage() { @@ -800,6 +1020,11 @@ int main(int argc, char* argv[]) { g_test_add_func("/keymanutil/keyman_put_options_todconf/other_keys", test_keyman_put_options_todconf__other_keys); g_test_add_func("/keymanutil/keyman_put_options_todconf/existing_key", test_keyman_put_options_todconf__existing_key); + g_test_add_func("/keymanutil/keyman_get_custom_keyboard_dictionary/values", test_keyman_get_custom_keyboard_dictionary__values); + g_test_add_func("/keymanutil/keyman_get_custom_keyboard_dictionary/invalid", test_keyman_get_custom_keyboard_dictionary__invalid); + g_test_add_func("/keymanutil/keyman_get_custom_keyboard_dictionary/empty", test_keyman_get_custom_keyboard_dictionary__empty); + g_test_add_func("/keymanutil/keyman_get_custom_keyboard_dictionary/null", test_keyman_get_custom_keyboard_dictionary__null); + g_test_add_func("/keymanutil/keyman_set_custom_keyboards/new_key", test_keyman_set_custom_keyboards__new_key); g_test_add_func("/keymanutil/keyman_set_custom_keyboards/overwrite_key", test_keyman_set_custom_keyboards__overwrite_key); g_test_add_func("/keymanutil/keyman_set_custom_keyboards/delete_key_NULL", test_keyman_set_custom_keyboards__delete_key_NULL); @@ -828,6 +1053,7 @@ int main(int argc, char* argv[]) { g_test_add_func("/keymanutil/keyman_add_keyboard/no_language", test_keyman_add_keyboard__no_language); g_test_add_func("/keymanutil/keyman_add_keyboard/one_language", test_keyman_add_keyboard__one_language); g_test_add_func("/keymanutil/keyman_add_keyboard/two_languages", test_keyman_add_keyboard__two_languages); + g_test_add_func("/keymanutil/keyman_add_keyboard/one_language_plus_custom", test_keyman_add_keyboard__one_language_plus_custom); g_test_add_func( "/keymanutil/keyman_add_keyboard/prev_engine_adding_same_version", test_keyman_add_keyboard__prev_engine_adding_same_version); From b0dd74ca596b2804dfa31dee843f708acd38e957 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 11 Oct 2023 19:52:13 +0200 Subject: [PATCH 3/4] =?UTF-8?q?feat(linux):=20Ignore=20invalid=20values=20?= =?UTF-8?q?in=20custom=20keyboards=20list=20=F0=9F=8F=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- linux/ibus-keyman/src/keymanutil.c | 76 +++++++++++++++++-- linux/ibus-keyman/src/test/keymanutil_tests.c | 39 ++++++++++ 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 1ce807ff7e9..716b53ca6e7 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -529,21 +529,87 @@ keyman_put_options_todconf(gchar *package_id, // kvp got assigned to options[x] and so gets freed when options are freed } -gchar** +static GPtrArray * +_ptr_array_new_from_array( + gpointer *data, + gsize len, + gboolean null_terminated) { + GPtrArray *array; + + g_assert(data != NULL || len == 0); + g_assert(len <= G_MAXUINT); + + array = g_ptr_array_new_full(len, NULL); + + for (gsize i = 0; i < len; i++) + array->pdata[i] = g_strdup(data[i]); + + if (null_terminated && array->pdata != NULL) + array->pdata[len++] = NULL; + + array->len = len; + + return array; +} + +// `g_ptr_array_new_from_null_terminated_array` is only available in GLib 2.76, but we're still +// stuck to 2.64 (Ubuntu 20.04 Focal) and 2.72 (Ubuntu 22.04 Jammy). Therefore we +// copy the implementation here (slightly simplified). Once we're past 2.76 we can use the GLib method +// directly. +GPtrArray * +_g_ptr_array_new_from_null_terminated_array( + gpointer *data, + GCopyFunc copy_func, + gpointer copy_func_user_data, + GDestroyNotify element_free_func) { + gsize len = 0; + + if (data != NULL) { + for (gsize i = 0; data[i] != NULL; ++i) + len += 1; + } + + g_assert(data != NULL || len == 0); + g_return_val_if_fail(len <= G_MAXUINT, NULL); + + return _ptr_array_new_from_array(data, len, TRUE); +} + +GPtrArray * +_keyman_cleanup_custom_keyboards(gchar **keyboards) { + GPtrArray *ptr_array = _g_ptr_array_new_from_null_terminated_array((gpointer*)keyboards, NULL, NULL, NULL); + + for (int i = 0; i < ptr_array->len; i++) { + if (ptr_array->pdata[i] == NULL) { + continue; + } + g_auto(GStrv) keyboard_tokens = g_strsplit(ptr_array->pdata[i], ":", 2); + if (keyboard_tokens == NULL + || keyboard_tokens[0] == NULL || strlen(keyboard_tokens[0]) == 0 + || keyboard_tokens[1] == NULL || strlen(keyboard_tokens[1]) == 0 + ) { + g_ptr_array_remove_index(ptr_array, i--); + } + } + return ptr_array; +} + +gchar ** keyman_get_custom_keyboards() { g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); - gchar **result = g_settings_get_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY); + g_auto(GStrv) result = g_settings_get_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY); if (result && result[0] == NULL) { - g_strfreev(result); return NULL; } - return result; + g_autoptr(GPtrArray) cleaned_result = _keyman_cleanup_custom_keyboards(result); + return (gchar**)g_ptr_array_steal(cleaned_result, NULL); } void keyman_set_custom_keyboards(gchar ** keyboards) { g_autoptr(GSettings) settings = g_settings_new(KEYMAN_DCONF_ENGINE_NAME); - g_settings_set_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY, (const gchar *const *)keyboards); + g_autoptr(GPtrArray) cleaned_keyboards = _keyman_cleanup_custom_keyboards(keyboards); + g_settings_set_strv(settings, KEYMAN_DCONF_KEYBOARDS_KEY, (const gchar *const *)cleaned_keyboards->pdata); } GHashTable * diff --git a/linux/ibus-keyman/src/test/keymanutil_tests.c b/linux/ibus-keyman/src/test/keymanutil_tests.c index bbecde06c3b..203df5fde62 100644 --- a/linux/ibus-keyman/src/test/keymanutil_tests.c +++ b/linux/ibus-keyman/src/test/keymanutil_tests.c @@ -367,6 +367,25 @@ test_keyman_set_custom_keyboards__delete_key_empty_array() { _delete_tst_kbds_key(); } +void +test_keyman_set_custom_keyboards__invalid_values() { + // Initialize + _delete_tst_kbds_key(); + gchar* keyboards[] = {"invalid", "fr:/tmp/test/test.kmx", "", ":/tmp/bla.kmx", "fr:", NULL}; + + // Execute + keyman_set_custom_keyboards(keyboards); + + // Verify + g_auto(GStrv) result = _get_tst_kbds_key(); + g_assert_nonnull(result); + gchar* expected[] = {"fr:/tmp/test/test.kmx", NULL}; + _kmn_assert_cmpstrv(result, expected); + + // Cleanup + _delete_tst_kbds_key(); +} + //---------------------------------------------------------------------------------------------- void test_keyman_get_custom_keyboards__value() { @@ -385,6 +404,24 @@ test_keyman_get_custom_keyboards__value() { _delete_tst_kbds_key(); } +void +test_keyman_get_custom_keyboards__invalid_values() { + // Initialize + gchar* keyboards[] = {"invalid", "fr:/tmp/test/test.kmx", "", NULL}; + _set_tst_kbds_key(keyboards); + + // Execute + g_auto(GStrv) result = keyman_get_custom_keyboards(); + + // Verify + gchar* expected[] = {"fr:/tmp/test/test.kmx", NULL}; + g_assert_nonnull(result); + _kmn_assert_cmpstrv(result, expected); + + // Cleanup + _delete_tst_kbds_key(); +} + void test_keyman_get_custom_keyboards__no_key() { // Initialize @@ -1031,8 +1068,10 @@ int main(int argc, char* argv[]) { g_test_add_func( "/keymanutil/keyman_set_custom_keyboards/delete_key_empty_array", test_keyman_set_custom_keyboards__delete_key_empty_array); + g_test_add_func("/keymanutil/keyman_set_custom_keyboards/invalid_values", test_keyman_set_custom_keyboards__invalid_values); g_test_add_func("/keymanutil/keyman_get_custom_keyboards/value", test_keyman_get_custom_keyboards__value); + g_test_add_func("/keymanutil/keyman_get_custom_keyboards/invalid_values", test_keyman_get_custom_keyboards__invalid_values); g_test_add_func("/keymanutil/keyman_get_custom_keyboards/no_key", test_keyman_get_custom_keyboards__no_key); g_test_add_func("/keymanutil/keyman_get_custom_keyboards/empty", test_keyman_get_custom_keyboards__empty); From 0eb5065bf4504c71ae5d4cb710fb5080c005c4e7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 16 Oct 2023 18:24:56 +0200 Subject: [PATCH 4/4] chore(linux): Unnecessary to check for NULL before calling g_free `g_free` will return if the parameter is NULL, so we don't have to check for that. See glib [documentation](https://docs.gtk.org/glib/func.free.html). --- linux/ibus-keyman/src/keymanutil.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 716b53ca6e7..d8b6b48d606 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -75,8 +75,7 @@ void free_cust_kbd(gpointer data) { g_free(kbd_data->kb_id_with_lang); g_free(kbd_data->lang->id); - if (kbd_data->lang->name) - g_free(kbd_data->lang->name); + g_free(kbd_data->lang->name); g_free(kbd_data->lang); g_free(kbd_data); }