From 6caece77d4cc0fa81b3303a4126e4efbb93b41be Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Sun, 22 Jan 2023 17:25:17 +0100 Subject: [PATCH] I785 (#1789) * [Textbox] Add history to the entrybox. * [Textbox] Add comments and move into sub functions. * [doc] Add conflicting constraint section to manpage. * [Script] Some small memory leak fixes. * [Entry History] Add documentation. fixes: #785 --- doc/rofi-keys.5 | 14 ++++ doc/rofi-keys.5.markdown | 10 +++ doc/rofi-theme.5 | 16 ++++- doc/rofi-theme.5.markdown | 15 ++++- doc/rofi.1 | 22 +++++++ doc/rofi.1.markdown | 16 +++++ include/helper.h | 2 +- include/keyb.h | 2 + include/widgets/textbox.h | 14 ++++ source/helper.c | 11 ++-- source/keyb.c | 8 +++ source/modes/script.c | 15 ++++- source/view.c | 133 +++++++++++++++++++++++++++++++++++++- source/widgets/textbox.c | 9 +++ test/mode-test.c | 2 +- 15 files changed, 271 insertions(+), 18 deletions(-) diff --git a/doc/rofi-keys.5 b/doc/rofi-keys.5 index 7d0ed990..00174a27 100644 --- a/doc/rofi-keys.5 +++ b/doc/rofi-keys.5 @@ -587,6 +587,20 @@ Select row 10 .PP \fBDefault\fP: Super+0 +.SS \fBkb-entry-history-up\fP +.PP +Go up in the entry history. + +.PP +\fBDefault\fP: Control+Up + +.SS \fBkb-entry-history-down\fP +.PP +Go down in the entry history. + +.PP +\fBDefault\fP: Control+Down + .SH Mouse Bindings .SS \fBml-row-left\fP .PP diff --git a/doc/rofi-keys.5.markdown b/doc/rofi-keys.5.markdown index c23abb6d..4ff3ae08 100644 --- a/doc/rofi-keys.5.markdown +++ b/doc/rofi-keys.5.markdown @@ -415,6 +415,16 @@ Select row 10 **Default**: Super+0 +### **kb-entry-history-up** +Go up in the entry history. + +**Default**: Control+Up + +### **kb-entry-history-down** +Go down in the entry history. + +**Default**: Control+Down + ## Mouse Bindings ### **ml-row-left** diff --git a/doc/rofi-theme.5 b/doc/rofi-theme.5 index 62eadb24..98f150b9 100644 --- a/doc/rofi-theme.5 +++ b/doc/rofi-theme.5 @@ -2136,12 +2136,22 @@ It supports the following keys as constraint: .fi .RE +.SH Conflicting constraints +.PP +It is possible to define conflicting constraints in the theme. These conflicts +are not explicitly reported. The most common example is forcing a specific +window size, for example by enabling full-screen mode, having number of lines +set in the listview and having the listview expand to available space. There is +clearly a conflict in these 3 constraints. In this case, listview will not +limit to the number of lines, but tries to fill the available space. It is up +to the theme designer to make sure the theme handles this correctly. + .SH Font Parsing .PP Rofi uses pango -\[la]https://pango.gnome.org/\[ra] for font rendering. The font should be specified in a format that pango -understands. -This normally is the font name followed by the font size. For example: +\[la]https://pango.gnome.org/\[ra] for font rendering. The font should +be specified in a format that pango understands. This normally is the font name +followed by the font size. For example: .PP .RS diff --git a/doc/rofi-theme.5.markdown b/doc/rofi-theme.5.markdown index c1f71c7c..11e52dd3 100644 --- a/doc/rofi-theme.5.markdown +++ b/doc/rofi-theme.5.markdown @@ -1361,12 +1361,21 @@ It supports the following keys as constraint: } ``` +## Conflicting constraints + +It is possible to define conflicting constraints in the theme. These conflicts +are not explicitly reported. The most common example is forcing a specific +window size, for example by enabling full-screen mode, having number of lines +set in the listview and having the listview expand to available space. There is +clearly a conflict in these 3 constraints. In this case, listview will not +limit to the number of lines, but tries to fill the available space. It is up +to the theme designer to make sure the theme handles this correctly. ## Font Parsing -Rofi uses [pango](https://pango.gnome.org/) for font rendering. The font should be specified in a format that pango -understands. -This normally is the font name followed by the font size. For example: +Rofi uses [pango](https://pango.gnome.org/) for font rendering. The font should +be specified in a format that pango understands. This normally is the font name +followed by the font size. For example: ``` mono 18 diff --git a/doc/rofi.1 b/doc/rofi.1 index 115dd38b..493751fa 100644 --- a/doc/rofi.1 +++ b/doc/rofi.1 @@ -1180,6 +1180,28 @@ rofi -filebrowser-cancel-returns-1 true -show filebrowser .PP The \fB\fCshow-hidden\fR can also be triggered with the \fB\fCkb-delete-entry\fR keybinding. +.SS Entry history +.PP +The number of previous inputs for the entry box can be modified by setting +max-history on the entry box. + +.PP +.RS + +.nf +configuration { + entry { + max-history: 30; + } +} + +.fi +.RE + +.PP +By default the file is stored in the systems cache directory, in a file called +\fB\fCrofi-entry-history.txt\fR\&. + .SS Other .PP \fB\fC-drun-use-desktop-cache\fR diff --git a/doc/rofi.1.markdown b/doc/rofi.1.markdown index b709dd94..94e387fe 100644 --- a/doc/rofi.1.markdown +++ b/doc/rofi.1.markdown @@ -726,6 +726,22 @@ rofi -filebrowser-cancel-returns-1 true -show filebrowser The `show-hidden` can also be triggered with the `kb-delete-entry` keybinding. +### Entry history + +The number of previous inputs for the entry box can be modified by setting +max-history on the entry box. + +```css +configuration { + entry { + max-history: 30; + } +} +``` + +By default the file is stored in the systems cache directory, in a file called +`rofi-entry-history.txt`. + ### Other `-drun-use-desktop-cache` diff --git a/include/helper.h b/include/helper.h index c8a0c01a..146ad46b 100644 --- a/include/helper.h +++ b/include/helper.h @@ -397,7 +397,7 @@ char *helper_string_replace_if_exists(char *string, ...); * * @returns path to theme or copy of filename if not found. */ -char *helper_get_theme_path(const char *file, const char **ext); +char *helper_get_theme_path(const char *file, const char **ext) __attribute__((nonnull)); /** * @param name The name of the element to find. diff --git a/include/keyb.h b/include/keyb.h index e938ddcc..850df0e6 100644 --- a/include/keyb.h +++ b/include/keyb.h @@ -143,6 +143,8 @@ typedef enum { SELECT_ELEMENT_8, SELECT_ELEMENT_9, SELECT_ELEMENT_10, + ENTRY_HISTORY_UP, + ENTRY_HISTORY_DOWN, } KeyBindingAction; /** diff --git a/include/widgets/textbox.h b/include/widgets/textbox.h index d5695c0b..01b67a75 100644 --- a/include/widgets/textbox.h +++ b/include/widgets/textbox.h @@ -345,5 +345,19 @@ void textbox_set_ellipsize(textbox *tb, PangoEllipsizeMode mode); * @returns the position of the cursor (0 if no cursor). */ int textbox_get_cursor_x_pos(const textbox *tb); + +/** + * @param tb Handle to the textbox + * + * @returns gets a newly allocated copy of the content of the entrybox. + */ +char *textbox_get_text ( const textbox *tb ); + +/** + * @param tb Handle to the textbox + * + * @returns the position of the cursor. + */ +int textbox_get_cursor ( const textbox *tb ); /**@}*/ #endif // ROFI_TEXTBOX_H diff --git a/source/helper.c b/source/helper.c index bc551483..4b14f361 100644 --- a/source/helper.c +++ b/source/helper.c @@ -1076,17 +1076,16 @@ char *helper_get_theme_path(const char *file, const char **ext) { g_free(filename); gboolean ext_found = FALSE; - if (ext) { - for (const char **i = ext; *i != NULL; i++) { - if (g_str_has_suffix(file, *i)) { - ext_found = TRUE; - break; - } + for (const char **i = ext; *i != NULL; i++) { + if (g_str_has_suffix(file, *i)) { + ext_found = TRUE; + break; } } if (ext_found) { filename = g_strdup(file); } else { + g_assert_nonnull(ext[0]); // TODO: Pick the first extension. needs fixing. filename = g_strconcat(file, ext[0], NULL); } diff --git a/source/keyb.c b/source/keyb.c index 1be12167..c30b1d8c 100644 --- a/source/keyb.c +++ b/source/keyb.c @@ -324,6 +324,14 @@ ActionBindingEntry rofi_bindings[] = { .name = "kb-select-10", .binding = "Super+0", .comment = "Select row 10"}, + {.id = ENTRY_HISTORY_UP, + .name = "kb-entry-history-up", + .binding = "Control+Up", + .comment = "Go up in the history of the entry box"}, + {.id = ENTRY_HISTORY_DOWN, + .name = "kb-entry-history-down", + .binding = "Control+Down", + .comment = "Go down in the history of the entry box"}, /* Mouse-aware bindings */ diff --git a/source/modes/script.c b/source/modes/script.c index 9da931a6..6661f999 100644 --- a/source/modes/script.c +++ b/source/modes/script.c @@ -83,10 +83,13 @@ void dmenuscript_parse_entry_extras(G_GNUC_UNUSED Mode *sw, DmenuScriptEntry *entry, char *buffer, G_GNUC_UNUSED size_t length) { gchar **extras = g_strsplit(buffer, "\x1f", -1); - gchar **extra; - for (extra = extras; *extra != NULL && *(extra + 1) != NULL; extra += 2) { + gchar **extra = extras; + for (; *extra != NULL && *(extra + 1) != NULL; extra += 2) { gchar *key = *extra; gchar *value = *(extra + 1); + // Mark NULL + *(extra) = NULL; + *(extra+1) = NULL; if (strcasecmp(key, "icon") == 0) { entry->icon_name = value; } else if (strcasecmp(key, "meta") == 0) { @@ -107,6 +110,10 @@ void dmenuscript_parse_entry_extras(G_GNUC_UNUSED Mode *sw, } g_free(key); } + // Got an extra entry.. lets free it. + if ( *extras != NULL ) { + g_free(*extras); + } g_free(extras); } @@ -247,7 +254,7 @@ static DmenuScriptEntry *execute_executor(Mode *sw, char *arg, buffer + buf_length, read_length - buf_length); } - retv[(*length) + 1].entry = NULL; + memset(&(retv[(*length)+1]), 0, sizeof(DmenuScriptEntry)); (*length)++; } } @@ -350,6 +357,7 @@ static ModeMode script_mode_result(Mode *sw, int mretv, char **input, g_free(rmpd->cmd_list[i].entry); g_free(rmpd->cmd_list[i].icon_name); g_free(rmpd->cmd_list[i].meta); + g_free(rmpd->cmd_list[i].info); } g_free(rmpd->cmd_list); @@ -522,6 +530,7 @@ void script_mode_gather_user_scripts(void) { } num_scripts++; } + g_dir_close(sd); } g_free(script_dir); diff --git a/source/view.c b/source/view.c index 9bcd3458..36163168 100644 --- a/source/view.c +++ b/source/view.c @@ -48,6 +48,7 @@ #include #include +#include /** Indicated we understand the startup notification api is not yet stable.*/ #define SN_API_NOT_YET_FROZEN @@ -102,6 +103,11 @@ GThreadPool *tpool = NULL; /** Global pointer to the currently active RofiViewState */ RofiViewState *current_active_menu = NULL; + +typedef struct { + char *string; + int index; +} EntryHistoryIndex; /** * Structure holding cached state. */ @@ -148,6 +154,10 @@ struct { gboolean fullscreen; /** Cursor type */ X11CursorType cursor_type; + /** Entry box */ + EntryHistoryIndex *entry_history; + gssize entry_history_length; + gssize entry_history_index; } CacheState = { .main_window = XCB_WINDOW_NONE, .fake_bg = NULL, @@ -165,6 +175,9 @@ struct { .count = 0L, .repaint_source = 0, .fullscreen = FALSE, + .entry_history = NULL, + .entry_history_length = 0, + .entry_history_index = 0 }; void rofi_view_get_current_monitor(int *width, int *height) { @@ -885,7 +898,74 @@ static void open_xim_callback(xcb_xim_t *im, G_GNUC_UNUSED void *user_data) { } #endif +static void input_history_initialize ( void ) +{ + CacheState.entry_history = NULL; + CacheState.entry_history_index = 0; + CacheState.entry_history_length = 0; + + gchar *path = g_build_filename(cache_dir, "rofi-entry-history.txt", NULL); + if ( g_file_test(path, G_FILE_TEST_EXISTS ) ) { + FILE *fp = fopen(path, "r"); + if ( fp ) { + char *line = NULL; + size_t len = 0; + ssize_t nread; + while ((nread = getline(&line, &len, fp)) != -1) { + CacheState.entry_history = g_realloc(CacheState.entry_history, + sizeof(EntryHistoryIndex)*(CacheState.entry_history_length+1)); + if ( line[nread-1] == '\n' ) { + line[nread-1] = '\0'; + nread--; + } + CacheState.entry_history[CacheState.entry_history_length].string = g_strdup(line); + CacheState.entry_history[CacheState.entry_history_length].index = strlen(line); + CacheState.entry_history_length++; + CacheState.entry_history_index++; + } + free(line); + fclose(fp); + } + } + g_free(path); + CacheState.entry_history = g_realloc(CacheState.entry_history, + sizeof(EntryHistoryIndex)*(CacheState.entry_history_length+1)); + CacheState.entry_history[CacheState.entry_history_length].string = g_strdup(""); + CacheState.entry_history[CacheState.entry_history_length].index = 0; + CacheState.entry_history_length++; + +} +static void input_history_save ( void ) +{ + if ( CacheState.entry_history_length > 0 ){ + // History max. + int max_history = 20; + ThemeWidget *wid = rofi_config_find_widget("entry", NULL, TRUE); + if ( wid ) { + Property *p = rofi_theme_find_property(wid, P_INTEGER, "max-history", TRUE); + if ( p != NULL && p->type == P_INTEGER ){ + max_history = p->value.i; + } + } + gchar *path = g_build_filename(cache_dir, "rofi-entry-history.txt", NULL); + g_debug("Entry filename output: '%s'", path); + FILE *fp = fopen(path, "w"); + if ( fp ) { + gssize start = MAX(0, (CacheState.entry_history_length-max_history)); + for ( gssize i = start; i < CacheState.entry_history_length; i++){ + if ( strlen(CacheState.entry_history[i].string) > 0 ){ + fprintf(fp, "%s\n", CacheState.entry_history[i].string); + } + } + fclose(fp); + } + g_free(path); + } +} + void __create_window(MenuFlags menu_flags) { + input_history_initialize(); + uint32_t selmask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_BIT_GRAVITY | XCB_CW_BACKING_STORE | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; @@ -1476,7 +1556,18 @@ void rofi_view_finalize(RofiViewState *state) { * This function should be called when the input of the entry is changed. * TODO: Evaluate if this needs to be a 'signal' on textbox? */ -static void rofi_view_input_changed() { rofi_view_take_action("inputchange"); } +static void rofi_view_input_changed() { + rofi_view_take_action("inputchange"); + + RofiViewState * state = current_active_menu; + if ( state ) { + if ( CacheState.entry_history[CacheState.entry_history_index].string != NULL) { + g_free(CacheState.entry_history[CacheState.entry_history_index].string); + } + CacheState.entry_history[CacheState.entry_history_index].string = textbox_get_text(state->text); + CacheState.entry_history[CacheState.entry_history_index].index = textbox_get_cursor(state->text); + } +} static void rofi_view_trigger_global_action(KeyBindingAction action) { RofiViewState *state = rofi_view_get_active(); @@ -1731,6 +1822,44 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) { state->quit = TRUE; break; } + case ENTRY_HISTORY_DOWN: { + if ( state->text ) { + CacheState.entry_history[CacheState.entry_history_index].index = textbox_get_cursor(state->text); + if ( CacheState.entry_history_index > 0 ) { + CacheState.entry_history_index--; + } + if ( state->text ) { + textbox_text(state->text, CacheState.entry_history[CacheState.entry_history_index].string); + textbox_cursor(state->text,CacheState.entry_history[CacheState.entry_history_index].index ); + state->refilter = TRUE; + } + } + break; + } + case ENTRY_HISTORY_UP: { + if ( state->text ) { + if ( CacheState.entry_history[CacheState.entry_history_index].string != NULL) { + g_free(CacheState.entry_history[CacheState.entry_history_index].string); + } + CacheState.entry_history[CacheState.entry_history_index].string = textbox_get_text(state->text); + CacheState.entry_history[CacheState.entry_history_index].index = textbox_get_cursor(state->text); + // Don't create up if current is empty. + if ( strlen(CacheState.entry_history[CacheState.entry_history_index].string) > 0 ) { + CacheState.entry_history_index++; + if ( CacheState.entry_history_index >= CacheState.entry_history_length ) { + CacheState.entry_history = g_realloc(CacheState.entry_history, + sizeof(EntryHistoryIndex)*(CacheState.entry_history_length+1)); + CacheState.entry_history[CacheState.entry_history_length].string = g_strdup(""); + CacheState.entry_history[CacheState.entry_history_length].index = 0; + CacheState.entry_history_length++; + } + } + textbox_text(state->text, CacheState.entry_history[CacheState.entry_history_index].string); + textbox_cursor(state->text,CacheState.entry_history[CacheState.entry_history_index].index ); + state->refilter = TRUE; + } + break; + } } } @@ -2443,6 +2572,8 @@ void rofi_view_cleanup() { } xcb_flush(xcb->connection); g_assert(g_queue_is_empty(&(CacheState.views))); + + input_history_save(); } void rofi_view_workers_initialize(void) { TICK_N("Setup Threadpool, start"); diff --git a/source/widgets/textbox.c b/source/widgets/textbox.c index 6b7030db..95e3f3b9 100644 --- a/source/widgets/textbox.c +++ b/source/widgets/textbox.c @@ -359,6 +359,15 @@ void textbox_set_pango_attributes(textbox *tb, PangoAttrList *list) { pango_layout_set_attributes(tb->layout, list); } +char *textbox_get_text ( const textbox *tb ) { + if ( tb->text == NULL ) { + return g_strdup(""); + } + return g_strdup( tb->text ); +} +int textbox_get_cursor ( const textbox *tb ) { + return tb->cursor; +} // set the default text to display void textbox_text(textbox *tb, const char *text) { if (tb == NULL) { diff --git a/test/mode-test.c b/test/mode-test.c index 67c55fbb..c67c89a8 100644 --- a/test/mode-test.c +++ b/test/mode-test.c @@ -127,7 +127,7 @@ END_TEST START_TEST(test_mode_num_items) { unsigned int rows = mode_get_num_entries(&help_keys_mode); - ck_assert_int_eq(rows, 77); + ck_assert_int_eq(rows, 79); for (unsigned int i = 0; i < rows; i++) { int state = 0; GList *list = NULL;