diff --git a/Makefile.am b/Makefile.am index 72d32534e..32bc9464f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -129,7 +129,8 @@ SOURCES=\ include/dialogs/filebrowser.h\ include/dialogs/dmenuscriptshared.h\ resources/resources.c\ - resources/resources.h + resources/resources.h\ + source/keyb-layout.c rofi_SOURCES=\ lexer/theme-parser.y\ @@ -355,7 +356,8 @@ helper_pidfile_SOURCES=\ include/helper-theme.h\ include/xrmoptions.h\ source/xrmoptions.c\ - test/helper-pidfile.c + test/helper-pidfile.c\ + source/keyb-layout.c widget_test_LDADD=$(textbox_test_LDADD) widget_test_CFLAGS=$(textbox_test_CFLAGS) @@ -370,7 +372,8 @@ widget_test_SOURCES=\ config/config.c\ lexer/theme-parser.y\ lexer/theme-lexer.l\ - test/widget-test.c + test/widget-test.c\ + source/keyb-layout.c box_test_LDADD=$(textbox_test_LDADD) box_test_CFLAGS=$(textbox_test_CFLAGS) @@ -426,7 +429,8 @@ textbox_test_SOURCES=\ include/xrmoptions.h\ include/helper.h\ include/helper-theme.h\ - test/textbox-test.c + test/textbox-test.c\ + source/keyb-layout.c if USE_CHECK theme_parser_test_CFLAGS=${helper_test_CFLAGS} $(check_CFLAGS) @@ -450,7 +454,8 @@ theme_parser_test_SOURCES=\ source/rofi-types.c\ include/rofi-types.h\ source/css-colors.c\ - test/theme-parser-test.c + test/theme-parser-test.c\ + source/keyb-layout.c endif helper_test_SOURCES=\ @@ -465,7 +470,8 @@ helper_test_SOURCES=\ source/xrmoptions.c\ source/rofi-types.c\ include/rofi-types.h\ - test/helper-test.c + test/helper-test.c\ + source/keyb-layout.c helper_test_CFLAGS=\ @@ -506,7 +512,8 @@ helper_expand_SOURCES=\ source/xrmoptions.c\ source/rofi-types.c\ include/rofi-types.h\ - test/helper-expand.c + test/helper-expand.c\ + source/keyb-layout.c helper_expand_CFLAGS=${helper_test_CFLAGS} @@ -527,7 +534,8 @@ helper_config_cmdline_parser_SOURCES=\ include/helper-theme.h\ include/xrmoptions.h\ source/xrmoptions.c\ - test/helper-config-cmdline-parser.c + test/helper-config-cmdline-parser.c\ + source/keyb-layout.c if USE_CHECK @@ -544,7 +552,8 @@ mode_test_SOURCES=\ source/xrmoptions.c\ source/keyb.c\ include/mode.h\ - include/mode-private.h + include/mode-private.h\ + source/keyb-layout.c helper_tokenize_CFLAGS=$(textbox_test_CFLAGS) $(check_CFLAGS) helper_tokenize_LDADD=$(textbox_test_LDADD) $(check_LIBS) helper_tokenize_SOURCES=\ @@ -559,7 +568,8 @@ helper_tokenize_SOURCES=\ include/helper-theme.h\ include/xrmoptions.h\ source/xrmoptions.c\ - test/helper-tokenize.c + test/helper-tokenize.c\ + source/keyb-layout.c endif diff --git a/doc/rofi.1.markdown b/doc/rofi.1.markdown index 3c2873616..9436eba36 100644 --- a/doc/rofi.1.markdown +++ b/doc/rofi.1.markdown @@ -261,6 +261,7 @@ Current the following methods are supported. * **regex**: match a regex input * **glob**: match a glob pattern * **fuzzy**: do a fuzzy match +* **all_kb_layouts**: normal with non-sensitivity to keyboard layout Default: *normal* diff --git a/include/keyb.h b/include/keyb.h index 1f5259ea0..c48bf788d 100644 --- a/include/keyb.h +++ b/include/keyb.h @@ -184,5 +184,23 @@ gboolean parse_keys_abe ( NkBindings *bindings ); */ void setup_abe ( void ); +/** + * Load the charmap from a keymap + */ +void kbl_charmap_load_from_keymap ( struct xkb_keymap *keymap ); + +/** + * Cleanup the charmap + */ +void kbl_charmap_cleanup ( void ); + +/** + * Allocate and initialize an input buffer state. + * @param chr character in utf8. + * + * @return the allocated string containing corresponding characters + */ +gchar *kbl_charmap_for_char ( const gchar *chr ); + /**@}*/ #endif // ROFI_KEYB_H diff --git a/include/settings.h b/include/settings.h index 491036b5c..510e65550 100644 --- a/include/settings.h +++ b/include/settings.h @@ -37,10 +37,11 @@ */ typedef enum { - MM_NORMAL = 0, - MM_REGEX = 1, - MM_GLOB = 2, - MM_FUZZY = 3 + MM_NORMAL = 0, + MM_REGEX = 1, + MM_GLOB = 2, + MM_FUZZY = 3, + MM_ALL_KB_LAYOUTS = 4 } MatchingMethod; /** diff --git a/meson.build b/meson.build index d44199884..7519b9fe6 100644 --- a/meson.build +++ b/meson.build @@ -143,6 +143,7 @@ rofi_sources = files( 'source/view.c', 'source/mode.c', 'source/keyb.c', + 'source/keyb-layout.c', 'config/config.c', 'source/helper.c', 'source/timings.c', @@ -299,6 +300,7 @@ test('helper_pidfile test', executable('helper_pidfile.test', [ 'source/helper.c', 'source/xrmoptions.c', 'source/rofi-types.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -317,6 +319,7 @@ test('widget test', executable('widget.test', [ 'source/css-colors.c', 'source/helper.c', 'config/config.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -366,6 +369,7 @@ test('textbox test', executable('textbox.test', [ 'source/css-colors.c', 'source/helper.c', 'config/config.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -378,6 +382,7 @@ test('helper test', executable('helper.test', [ 'source/helper.c', 'source/xrmoptions.c', 'source/rofi-types.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -390,6 +395,7 @@ test('helper_expand test', executable('helper_expand.test', [ 'source/helper.c', 'source/xrmoptions.c', 'source/rofi-types.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -402,6 +408,7 @@ test('helper_config_cmdline_parser test', executable('helper_config_cmdline_pars 'source/helper.c', 'source/xrmoptions.c', 'source/rofi-types.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -421,6 +428,7 @@ if check.found() 'source/theme.c', 'source/rofi-types.c', 'source/css-colors.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -436,6 +444,7 @@ if check.found() 'source/xrmoptions.c', 'source/rofi-types.c', 'source/keyb.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) @@ -448,6 +457,7 @@ if check.found() 'source/helper.c', 'source/xrmoptions.c', 'source/rofi-types.c', + 'source/keyb-layout.c', ]), dependencies: deps, )) diff --git a/source/helper.c b/source/helper.c index 33e94d250..b4ebe4e12 100644 --- a/source/helper.c +++ b/source/helper.c @@ -54,6 +54,7 @@ #include "settings.h" #include "rofi.h" #include "view.h" +#include "keyb.h" /** * Textual description of positioning rofi. @@ -178,6 +179,23 @@ static gchar *fuzzy_to_regex ( const char * input ) g_string_free ( str, FALSE ); return retv; } +static gchar *all_kb_layouts_to_regex ( const char * input ) +{ + GString *str = g_string_new ( "" ); + gchar *iter; + for ( iter = input; iter && *iter != '\0'; iter = g_utf8_next_char ( iter ) ) { + gchar *kblchs = kbl_charmap_for_char ( iter ); + gchar *r = g_regex_escape_string ( kblchs, -1 ); + + g_string_append_printf ( str, "[%s]", r ); + g_free ( kblchs ); + g_free ( r ); + } + + gchar *retv = str->str; + g_string_free ( str, FALSE ); + return retv; +} static char *utf8_helper_simplify_string ( const char *s) { @@ -247,6 +265,11 @@ static rofi_int_matcher * create_regex ( const char *input, int case_sensitive ) retv = R ( r, case_sensitive ); g_free ( r ); break; + case MM_ALL_KB_LAYOUTS: + r = all_kb_layouts_to_regex ( input ); + retv = R ( r, case_sensitive ); + g_free ( r ); + break; default: r = g_regex_escape_string ( input, -1 ); retv = R ( r, case_sensitive ); @@ -627,11 +650,14 @@ int config_sanity_check ( void ) else if ( g_strcmp0 ( config.matching, "fuzzy" ) == 0 ) { config.matching_method = MM_FUZZY; } + else if ( g_strcmp0 ( config.matching, "all_kb_layouts" ) == 0 ) { + config.matching_method = MM_ALL_KB_LAYOUTS; + } else if ( g_strcmp0 ( config.matching, "normal" ) == 0 ) { config.matching_method = MM_NORMAL;; } else { - g_string_append_printf ( msg, "\tconfig.matching=%s is not a valid matching strategy.\nValid options are: glob, regex, fuzzy or normal.\n", + g_string_append_printf ( msg, "\tconfig.matching=%s is not a valid matching strategy.\nValid options are: glob, regex, fuzzy, all_kb_layouts or normal.\n", config.matching ); found_error = 1; } diff --git a/source/keyb-layout.c b/source/keyb-layout.c new file mode 100644 index 000000000..df4590045 --- /dev/null +++ b/source/keyb-layout.c @@ -0,0 +1,154 @@ +/* + * rofi + * + * MIT/X11 License + * Copyright © 2013-2017 Qball Cow + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "rofi.h" + +/** + * Data structure corresponding to the relationship of the character to the layout + */ +struct kbl_charmap +{ + xkb_layout_index_t layout; + xkb_level_index_t level; + xkb_keycode_t keycode; + int keysim; + gunichar chr; +}; + +/** + * Array of relationships of the character to the layout. Sorted by keycode + */ +static GArray *rofi_kbl_charmap = NULL; + +static void kbl_charmap_key_iter ( struct xkb_keymap *keymap, xkb_keycode_t key, void *data ) +{ + const xkb_keysym_t *keysims; + char buffer[255]; + GArray *char_array = ( GArray * ) data; + + xkb_layout_index_t lt_count = xkb_keymap_num_layouts_for_key ( keymap, key ); + if ( lt_count <= 1 ) { + return; + } + for ( xkb_layout_index_t lt_index = 0; lt_index < lt_count; lt_index++ ) { + xkb_level_index_t lvl_count = xkb_keymap_num_levels_for_key ( keymap, key, lt_index ); + for ( xkb_level_index_t lvl_index = 0; lvl_index < lvl_count; lvl_index++ ) { + int ks_count = xkb_keymap_key_get_syms_by_level ( keymap, key, lt_index, lvl_index, &keysims ); + for ( int ks_index = 0; ks_index < ks_count; ks_index++ ) { + int char_count = xkb_keysym_to_utf8 ( keysims[ks_index], buffer, 255 ); + if ( char_count > 0 ) { + gunichar chr = g_utf8_get_char ( buffer ); + if ( g_unichar_isdefined ( chr ) ) { + struct kbl_charmap charmap_item = { + .layout = lt_index, + .level = lvl_index, + .keycode = key, + .keysim = ks_index, + .chr = g_utf8_get_char ( buffer ) + }; + g_array_append_val ( char_array, charmap_item ); + } + } + } + } + } +} + +void kbl_charmap_load_from_keymap ( struct xkb_keymap *keymap ) +{ + rofi_kbl_charmap = NULL; + xkb_layout_index_t layout_count = xkb_keymap_num_layouts ( keymap ); + if ( layout_count <= 1 ) { + return; + } + rofi_kbl_charmap = g_array_new ( FALSE, FALSE, sizeof ( struct kbl_charmap ) ); + xkb_keymap_key_for_each ( keymap, kbl_charmap_key_iter, rofi_kbl_charmap ); +} + +void kbl_charmap_cleanup ( void ) +{ + if ( rofi_kbl_charmap ) { + g_array_free ( rofi_kbl_charmap, TRUE ); + } +} + +static gint kbl_charmap_find_char_key ( GArray *charmap, gunichar chr, struct kbl_charmap *key, gint next ) +{ + for ( guint i = ( guint ) next; i < charmap->len; i++ ) { + struct kbl_charmap *charmap_item = &g_array_index ( charmap, struct kbl_charmap, i ); + if ( charmap_item->chr == chr ) { + key->layout = charmap_item->layout; + key->level = charmap_item->level; + key->keycode = charmap_item->keycode; + key->keysim = charmap_item->keysim; + key->chr = charmap_item->chr; + return ( gint ) i; + } + } + return -1; +} + +gchar *kbl_charmap_for_char ( const gchar *chr ) +{ + gint found = 0; + struct kbl_charmap key_for_char; + gunichar uchar = g_utf8_get_char ( chr ); + GString * str = g_string_new ( "" ); + if ( rofi_kbl_charmap ) { + while ( ( found = kbl_charmap_find_char_key ( rofi_kbl_charmap, uchar, &key_for_char, found ) ) >= 0 ) { + for ( gint i = found; i >= 0; i-- ) { + struct kbl_charmap *charmap_item = &g_array_index ( rofi_kbl_charmap, struct kbl_charmap, i ); + if ( key_for_char.level == charmap_item->level && + key_for_char.keycode == charmap_item->keycode && + key_for_char.keysim == charmap_item->keysim ) { + g_string_append_unichar ( str, charmap_item->chr ); + } + if ( key_for_char.keycode != charmap_item->keycode ) { + break; + } + } + for ( guint i = ( guint ) found + 1; i < rofi_kbl_charmap->len; i++ ) { + struct kbl_charmap *charmap_item = &g_array_index ( rofi_kbl_charmap, struct kbl_charmap, i ); + if ( key_for_char.level == charmap_item->level && + key_for_char.keycode == charmap_item->keycode && + key_for_char.keysim == charmap_item->keysim ) { + g_string_append_unichar ( str, charmap_item->chr ); + } + if ( key_for_char.keycode != charmap_item->keycode ) { + break; + } + } + found++; + } + } + if ( str->len == 0 ) { + g_string_append_unichar ( str, uchar ); + } + gchar *retv = str->str; + g_string_free ( str, FALSE ); + return retv; +} diff --git a/source/xcb.c b/source/xcb.c index b5f25a47e..e5568934b 100644 --- a/source/xcb.c +++ b/source/xcb.c @@ -953,7 +953,7 @@ static void main_loop_x11_event_handler_view ( xcb_generic_event_t *event ) for ( gint32 by = 0; by < 31; ++by ) { for ( gint8 bi = 0; bi < 7; ++bi ) { if ( kne->keys[by] & ( 1 << bi ) ) { - // X11 keycodes starts at 8 + // X11 keycodes starts at 8 nk_bindings_seat_handle_key ( xcb->bindings_seat, NULL, ( 8 * by + bi ) + 8, NK_BINDINGS_KEY_STATE_PRESSED ); } } @@ -1247,6 +1247,13 @@ gboolean display_setup ( GMainLoop *main_loop, NkBindings *bindings ) g_warning ( "Failed to get Keymap for current keyboard device." ); return FALSE; } + + char *matching_str = NULL; + find_arg_str ( "-matching", &matching_str ); + if ( g_strcmp0 ( matching_str, "all_kb_layouts" ) == 0 ) { + kbl_charmap_load_from_keymap ( keymap ); + } + struct xkb_state *state = xkb_x11_state_new_from_device ( keymap, xcb->connection, xcb->xkb.device_id ); if ( state == NULL ) { g_warning ( "Failed to get state object for current keyboard device." ); @@ -1426,6 +1433,7 @@ void display_cleanup ( void ) xcb->connection = NULL; xcb->screen = NULL; xcb->screen_nbr = 0; + kbl_charmap_cleanup (); } void x11_disable_decoration ( xcb_window_t window ) diff --git a/source/xrmoptions.c b/source/xrmoptions.c index 9e4289d0a..9861abb66 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -169,7 +169,7 @@ static XrmOption xrmOptions[] = { { xrm_String, "combi-modi", { .str = &config.combi_modi }, NULL, "Set the modi to combine in combi mode", CONFIG_DEFAULT }, { xrm_String, "matching", { .str = &config.matching }, NULL, - "Set the matching algorithm. (normal, regex, glob, fuzzy)", CONFIG_DEFAULT }, + "Set the matching algorithm. (normal, regex, glob, fuzzy, all_kb_layouts)", CONFIG_DEFAULT }, { xrm_Boolean, "tokenize", { .num = &config.tokenize }, NULL, "Tokenize input string", CONFIG_DEFAULT }, { xrm_String, "monitor", { .str = &config.monitor }, NULL, diff --git a/test/helper-tokenize.c b/test/helper-tokenize.c index 66cfb60f3..543c7ea60 100644 --- a/test/helper-tokenize.c +++ b/test/helper-tokenize.c @@ -391,6 +391,86 @@ START_TEST ( test_tokenizer_match_regex_single_two_word_till_end ) } END_TEST +START_TEST ( test_tokenizer_match_all_lt_single_ci ) +{ + config.matching_method = MM_ALL_KB_LAYOUTS; + rofi_int_matcher **tokens = helper_tokenize ( "noot", FALSE ); + + ck_assert_int_eq ( helper_token_match ( tokens, "aap noot mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nootap mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap Noot mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "Nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "noOTap mies") , TRUE ); + + helper_tokenize_free ( tokens ); +} +END_TEST + +START_TEST ( test_tokenizer_match_all_lt_single_cs ) +{ + config.matching_method = MM_ALL_KB_LAYOUTS; + rofi_int_matcher **tokens = helper_tokenize ( "noot", TRUE ); + + ck_assert_int_eq ( helper_token_match ( tokens, "aap noot mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nootap mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap Noot mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "Nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "noOTap mies") , FALSE ); + helper_tokenize_free ( tokens ); +} +END_TEST + +START_TEST ( test_tokenizer_match_all_lt_multiple_ci ) +{ + config.matching_method = MM_ALL_KB_LAYOUTS; + rofi_int_matcher **tokens = helper_tokenize ( "no ot", FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap noot mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nootap mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "noap miesot") , TRUE ); + helper_tokenize_free ( tokens ); + + tokens = helper_tokenize ( "n ot", FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap noot mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nootap mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "noap miesot") , TRUE); + helper_tokenize_free ( tokens ); +} +END_TEST + +START_TEST ( test_tokenizer_match_all_lt_single_ci_split ) +{ + config.matching_method = MM_ALL_KB_LAYOUTS; + rofi_int_matcher **tokens = helper_tokenize ( "ont", FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap noot mies") , FALSE); + ck_assert_int_eq ( helper_token_match ( tokens, "aap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nootap nmiest") , FALSE ); + helper_tokenize_free ( tokens ); +} +END_TEST + +START_TEST ( test_tokenizer_match_all_lt_multiple_ci_split ) +{ + config.matching_method = MM_ALL_KB_LAYOUTS; + rofi_int_matcher **tokens = helper_tokenize ( "o n t", FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap noot mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "aap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nooaap mies") , FALSE ); + ck_assert_int_eq ( helper_token_match ( tokens, "nootap mies") , TRUE ); + ck_assert_int_eq ( helper_token_match ( tokens, "noap miesot") , TRUE); + ck_assert_int_eq ( helper_token_match ( tokens, "ot nap mies") , TRUE); + helper_tokenize_free ( tokens ); +} +END_TEST + static Suite * helper_tokenizer_suite (void) { Suite *s; @@ -442,6 +522,15 @@ static Suite * helper_tokenizer_suite (void) tcase_add_test(tc_regex, test_tokenizer_match_regex_multiple_ci); suite_add_tcase(s, tc_regex); } + { + TCase *tc_all_layaut = tcase_create ("AllLayout"); + tcase_add_test(tc_all_layaut, test_tokenizer_match_all_lt_single_ci); + tcase_add_test(tc_all_layaut, test_tokenizer_match_all_lt_single_cs); + tcase_add_test(tc_all_layaut, test_tokenizer_match_all_lt_single_ci_split); + tcase_add_test(tc_all_layaut, test_tokenizer_match_all_lt_multiple_ci); + tcase_add_test(tc_all_layaut, test_tokenizer_match_all_lt_multiple_ci_split); + suite_add_tcase(s, tc_all_layaut); + } return s;