From 4f13d9c88e8a00ccb9ba8d5380db2f93d3f67908 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Wed, 5 Oct 2022 14:35:12 +0100 Subject: Add options to hide input for passwords etc. --- completions/tofi | 2 ++ doc/config | 9 +++++++++ doc/tofi.5.md | 15 +++++++++++++++ doc/tofi.5.scd | 28 +++++++++++++++++++++------- src/config.c | 34 ++++++++++++++++++++++++++++++++++ src/entry.h | 3 +++ src/entry_backend/harfbuzz.c | 9 ++++++++- src/entry_backend/pango.c | 17 ++++++++++++++++- src/main.c | 5 +++++ 9 files changed, 113 insertions(+), 9 deletions(-) diff --git a/completions/tofi b/completions/tofi index 0e21eed..4d46d27 100644 --- a/completions/tofi +++ b/completions/tofi @@ -45,6 +45,8 @@ _tofi() --history --fuzzy-match --require-match + --hide-input + --hidden-character --drun-launch --hint-font --late-keyboard-init diff --git a/doc/config b/doc/config index f95a367..4f81163 100644 --- a/doc/config +++ b/doc/config @@ -135,6 +135,15 @@ # In drun mode, this is always true. require-match = true + # If true, typed input will be hidden, and what is displayed (if + # anything) is determined by the hidden-character option. + hide-input = false + + # Replace displayed input characters with a character. If the empty + # string is given, input will be completely hidden. + # This option only has an effect when hide-input is set to true. + hidden-character = "*" + # If true, directly launch applications on selection when in drun mode. # Otherwise, just print the command line to stdout. drun-launch = false diff --git a/doc/tofi.5.md b/doc/tofi.5.md index b330278..319171d 100644 --- a/doc/tofi.5.md +++ b/doc/tofi.5.md @@ -60,6 +60,21 @@ options. > > Default: true +**hide-input**=*true\|false* + +> If true, typed input will be hidden, and what is displayed (if +> anything) is determined by the **hidden-character** option. +> +> Default: false + +**hidden-character**=*char* + +> Replace displayed input characters with *char*. If *char* is set to +> the empty string, input will be completely hidden. This option only +> has an effect when **hide-input** is set to true. +> +> Default: \* + **drun-launch**=*true\|false* > If true, directly launch applications on selection when in drun mode. diff --git a/doc/tofi.5.scd b/doc/tofi.5.scd index e0faa2d..cad1dbb 100644 --- a/doc/tofi.5.scd +++ b/doc/tofi.5.scd @@ -25,10 +25,11 @@ options. # SPECIAL OPTIONS *include*=_path_ - Include the contents of another config file. If _path_ is a relative path, - it is interpreted as relative to this config file's path (or the current - directory if *--include* is passed on the command line). Inclusion happens - immediately, before the rest of the current file's contents are parsed. + Include the contents of another config file. If _path_ is a relative + path, it is interpreted as relative to this config file's path (or the + current directory if *--include* is passed on the command line). + Inclusion happens immediately, before the rest of the current file's + contents are parsed. # BEHAVIOUR OPTIONS @@ -56,6 +57,19 @@ options. Default: true +*hide-input*=_true|false_ + If true, typed input will be hidden, and what is displayed (if + anything) is determined by the *hidden-character* option. + + Default: false + +*hidden-character*=_char_ + Replace displayed input characters with _char_. If _char_ is set to the + empty string, input will be completely hidden. + This option only has an effect when *hide-input* is set to true. + + Default: \* + *drun-launch*=_true|false_ If true, directly launch applications on selection when in drun mode. Otherwise, just print the path of the .desktop file to stdout. @@ -206,9 +220,9 @@ options. *scale*=_true|false_ Scale the window by the output's scale factor. - *WARNING*: In the current version of tofi, the default value has changed to - true, so you may need to update your config. Additionally, font scaling will - no longer occur when this is set to _false_. + *WARNING*: In the current version of tofi, the default value has + changed to true, so you may need to update your config. Additionally, + font scaling will no longer occur when this is set to _false_. Default: true diff --git a/src/config.c b/src/config.c index fb00fd6..59b8012 100644 --- a/src/config.c +++ b/src/config.c @@ -33,6 +33,7 @@ static uint32_t fixup_percentage(uint32_t value, uint32_t base, bool is_percent, static uint32_t parse_anchor(const char *filename, size_t lineno, const char *str, bool *err); static bool parse_bool(const char *filename, size_t lineno, const char *str, bool *err); +static wchar_t parse_wchar(const char *filename, size_t lineno, const char *str, bool *err); static struct color parse_color(const char *filename, size_t lineno, const char *str, bool *err); static uint32_t parse_uint32(const char *filename, size_t lineno, const char *str, bool *err); static int32_t parse_int32(const char *filename, size_t lineno, const char *str, bool *err); @@ -357,6 +358,22 @@ bool parse_option(struct tofi *tofi, const char *filename, size_t lineno, const tofi->fuzzy_match = parse_bool(filename, lineno, value, &err); } else if (strcasecmp(option, "require-match") == 0) { tofi->require_match = parse_bool(filename, lineno, value, &err); + } else if (strcasecmp(option, "hide-input") == 0) { + tofi->window.entry.hide_input = parse_bool(filename, lineno, value, &err); + } else if (strcasecmp(option, "hidden-character") == 0) { + /* Unicode handling is ugly. */ + wchar_t wc = parse_wchar(filename, lineno, value, &err); + size_t buf_len = N_ELEM(tofi->window.entry.hidden_character_mb) + 1; + char *buf = xmalloc(buf_len); + size_t char_len = snprintf(buf, buf_len, "%lc", wc); + if (char_len >= buf_len) { + PARSE_ERROR(filename, lineno, "Character \"%lc\" is too large.\n", value); + err = true; + } else { + memcpy(tofi->window.entry.hidden_character_mb, buf, char_len); + tofi->window.entry.hidden_character_mb_length = char_len; + } + free(buf); } else if (strcasecmp(option, "drun-launch") == 0) { tofi->drun_launch = parse_bool(filename, lineno, value, &err); } else if (strcasecmp(option, "drun-print-exec") == 0) { @@ -507,6 +524,23 @@ bool parse_bool(const char *filename, size_t lineno, const char *str, bool *err) return false; } +wchar_t parse_wchar(const char *filename, size_t lineno, const char *str, bool *err) +{ + size_t len = strlen(str); + wchar_t ch; + size_t ret = mbrtowc(&ch, str, len, NULL); + if (ret == (size_t)-2) { + return 0; + } else if (ret != len) { + PARSE_ERROR(filename, lineno, "Failed to parse \"%s\" as a character.\n", str); + if (err) { + *err = true; + } + return 0; + } + return ch; +} + uint32_t parse_anchor(const char *filename, size_t lineno, const char *str, bool *err) { if(strcasecmp(str, "top-left") == 0) { diff --git a/src/entry.h b/src/entry.h index 46ec65f..6b547e0 100644 --- a/src/entry.h +++ b/src/entry.h @@ -49,6 +49,9 @@ struct entry { /* Options */ bool drun; bool horizontal; + bool hide_input; + char hidden_character_mb[4]; + uint32_t hidden_character_mb_length; uint32_t num_results; uint32_t num_results_drawn; uint32_t last_num_results_drawn; diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c index 2834d9d..c5fc07e 100644 --- a/src/entry_backend/harfbuzz.c +++ b/src/entry_backend/harfbuzz.c @@ -230,7 +230,14 @@ void entry_backend_harfbuzz_update(struct entry *entry) /* Render the entry text */ hb_buffer_clear_contents(buffer); setup_hb_buffer(buffer); - hb_buffer_add_utf8(buffer, entry->input_mb, -1, 0, -1); + if (entry->hide_input) { + size_t char_len = N_ELEM(entry->hidden_character_mb); + for (size_t i = 0; i < entry->input_length; i++) { + hb_buffer_add_utf8(buffer, entry->hidden_character_mb, char_len, 0, char_len); + } + } else { + hb_buffer_add_utf8(buffer, entry->input_mb, -1, 0, -1); + } hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); extents = render_hb_buffer(cr, buffer); extents.x_advance = MAX(extents.x_advance, entry->input_width); diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c index d4737a1..01fc153 100644 --- a/src/entry_backend/pango.c +++ b/src/entry_backend/pango.c @@ -85,7 +85,22 @@ void entry_backend_pango_update(struct entry *entry) cairo_translate(cr, entry->prompt_padding, 0); /* Render the entry text */ - pango_layout_set_text(layout, entry->input_mb, -1); + if (entry->hide_input) { + /* + * Pango needs to be passed the whole text at once, so we need + * to manually replicate the replacement character in a buffer. + */ + static char buf[sizeof(entry->input_mb)]; + uint32_t char_len = entry->hidden_character_mb_length; + for (size_t i = 0; i < entry->input_length; i++) { + for (size_t j = 0; j < char_len; j++) { + buf[i * char_len + j] = entry->hidden_character_mb[j]; + } + } + pango_layout_set_text(layout, buf, -1); + } else { + pango_layout_set_text(layout, entry->input_mb, -1); + } pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); diff --git a/src/main.c b/src/main.c index 55283b4..c0852f4 100644 --- a/src/main.c +++ b/src/main.c @@ -814,6 +814,8 @@ static void usage() " --history Sort results by number of usages.\n" " --fuzzy-match Use fuzzy matching for searching.\n" " --require-match Require a match for selection.\n" +" --hide-input Hide sensitive input such as passwords.\n" +" --hidden-character Replacement character for hidden input.\n" " --drun-launch Launch apps directly in drun mode.\n" " --drun-print-exec Print a command line in drun mode.\n" " This is now always the case,\n" @@ -864,6 +866,8 @@ const struct option long_options[] = { {"history", required_argument, NULL, 0}, {"fuzzy-match", required_argument, NULL, 0}, {"require-match", required_argument, NULL, 0}, + {"hide-input", required_argument, NULL, 0}, + {"hidden-character", required_argument, NULL, 0}, {"drun-launch", required_argument, NULL, 0}, {"drun-print-exec", required_argument, NULL, 0}, {"hint-font", required_argument, NULL, 0}, @@ -1007,6 +1011,7 @@ int main(int argc, char *argv[]) .font_name = "Sans", .font_size = 24, .prompt_text = "run: ", + .hidden_character_mb = "*", .padding_top = 8, .padding_bottom = 8, .padding_left = 8, -- cgit v1.2.3