From e2a7cfdd5241e0897e46c806d2d11713b5206109 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Wed, 20 Apr 2022 21:53:44 +0200 Subject: [PATCH] [DMenu] Huge list speedups (#1621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don´t refilter on each key-press. * Make sure refiltering is not completely starved. * Force refilter before accepting entry * Go into 'timeout' refilter mode only after certain # elements. * [DMenu] threading with getdelim to speed up reading. * [View] Remove debug output. * [dmenu] Fix pre-read. * [DMenu] Make sure that async mode cannot block * Remove mutex for IPC via pipes. * [Dmenu] Small cleanup. * [Scrollbar] Fix scrollbar overflowing on big lists. * Fix stack overflow by creating to many filter jobs. * [Doc] Add new option to manpage --- config/config.c | 4 +- doc/rofi-dmenu.5 | 10 - doc/rofi-dmenu.5.markdown | 7 - doc/rofi.1 | 9 + doc/rofi.1.markdown | 6 + include/settings.h | 4 + source/modes/dmenu.c | 408 ++++++++++++++++++++++++------------- source/view.c | 58 +++++- source/widgets/scrollbar.c | 4 +- source/xrmoptions.c | 7 + 10 files changed, 351 insertions(+), 166 deletions(-) diff --git a/config/config.c b/config/config.c index c0e6e008..c1610453 100644 --- a/config/config.c +++ b/config/config.c @@ -153,4 +153,6 @@ Settings config = { /** steal focus */ .steal_focus = FALSE, /** fallback icon */ - .application_fallback_icon = NULL}; + .application_fallback_icon = NULL, + /** refilter limit */ + .refilter_timeout_limit = 8192}; diff --git a/doc/rofi-dmenu.5 b/doc/rofi-dmenu.5 index f361af08..eb3c7599 100644 --- a/doc/rofi-dmenu.5 +++ b/doc/rofi-dmenu.5 @@ -247,16 +247,6 @@ Force \fBrofi\fP mode to first read all data from stdin before showing the selec Note: the default asynchronous mode will also be automatically disabled if used with conflicting options, such as \fB\fC-dump\fR, \fB\fC-only-match\fR or \fB\fC-auto-select\fR\&. -.PP -\fB\fC-async-pre-read\fR \fInumber\fP - -.PP -Reads the first \fInumber\fP entries blocking, then switches to async mode. -This makes it feel more 'snappy'. - -.PP -\fIdefault\fP: 25 - .PP \fB\fC-window-title\fR \fItitle\fP diff --git a/doc/rofi-dmenu.5.markdown b/doc/rofi-dmenu.5.markdown index fc5be9f8..aa868cee 100644 --- a/doc/rofi-dmenu.5.markdown +++ b/doc/rofi-dmenu.5.markdown @@ -158,13 +158,6 @@ Force **rofi** mode to first read all data from stdin before showing the selecti Note: the default asynchronous mode will also be automatically disabled if used with conflicting options, such as `-dump`, `-only-match` or `-auto-select`. -`-async-pre-read` *number* - -Reads the first *number* entries blocking, then switches to async mode. -This makes it feel more 'snappy'. - -*default*: 25 - `-window-title` *title* Set name used for the window title. Will be shown as Rofi - *title* diff --git a/doc/rofi.1 b/doc/rofi.1 index 436c8c76..ecdea8e1 100644 --- a/doc/rofi.1 +++ b/doc/rofi.1 @@ -455,6 +455,15 @@ Make \fBrofi\fP react like a normal application window. Useful for scripts like .PP Make rofi steal focus on launch and restore close to window that held it when launched. +.PP +\fB\fC-refilter-timeout-limit\fR + +.PP +The limit of elements that is used to switch from instant to delayed filter mode. + +.PP +Default: 8192 + .SS Matching .PP \fB\fC-matching\fR \fImethod\fP diff --git a/doc/rofi.1.markdown b/doc/rofi.1.markdown index 3ab114ca..0449804b 100644 --- a/doc/rofi.1.markdown +++ b/doc/rofi.1.markdown @@ -274,6 +274,12 @@ Make **rofi** react like a normal application window. Useful for scripts like Cl Make rofi steal focus on launch and restore close to window that held it when launched. +`-refilter-timeout-limit` + +The limit of elements that is used to switch from instant to delayed filter mode. + + Default: 8192 + ### Matching `-matching` *method* diff --git a/include/settings.h b/include/settings.h index f77e818a..5128a379 100644 --- a/include/settings.h +++ b/include/settings.h @@ -177,6 +177,10 @@ typedef struct { gboolean steal_focus; /** fallback icon */ char *application_fallback_icon; + + /** refilter timeout limit, when more then these entries,go into timeout mode. + */ + unsigned int refilter_timeout_limit; } Settings; /** Default number of lines in the list view */ diff --git a/source/modes/dmenu.c b/source/modes/dmenu.c index 1a274b7e..453e4ec6 100644 --- a/source/modes/dmenu.c +++ b/source/modes/dmenu.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -97,16 +98,52 @@ typedef struct { gchar *column_separator; gboolean multi_select; - GCancellable *cancel; - gulong cancel_source; - GInputStream *input_stream; - GDataInputStream *data_input_stream; + GThread *reading_thread; + GAsyncQueue *async_queue; + gboolean async; + FILE *fd_file; + int fd; + int pipefd[2]; + int pipefd2[2]; + guint wake_source; } DmenuModePrivateData; -static void async_close_callback(GObject *source_object, GAsyncResult *res, - G_GNUC_UNUSED gpointer user_data) { - g_input_stream_close_finish(G_INPUT_STREAM(source_object), res, NULL); - g_debug("Closing data stream."); +#define BLOCK_LINES_SIZE 2048 +typedef struct { + unsigned int length; + DmenuScriptEntry values[BLOCK_LINES_SIZE]; + DmenuModePrivateData *pd; +} Block; + +static void read_add_block(DmenuModePrivateData *pd, Block **block, char *data, + gsize len) { + + if ((*block) == NULL) { + (*block) = g_malloc0(sizeof(Block)); + (*block)->pd = pd; + (*block)->length = 0; + } + gsize data_len = len; + // Init. + (*block)->values[(*block)->length].icon_fetch_uid = 0; + (*block)->values[(*block)->length].icon_name = NULL; + (*block)->values[(*block)->length].meta = NULL; + (*block)->values[(*block)->length].info = NULL; + (*block)->values[(*block)->length].nonselectable = FALSE; + char *end = data; + while (end < data + len && *end != '\0') { + end++; + } + if (end != data + len) { + data_len = end - data; + dmenuscript_parse_entry_extras(NULL, &((*block)->values[(*block)->length]), + end + 1, len - data_len); + } + char *utfstr = rofi_force_utf8(data, data_len); + (*block)->values[(*block)->length].entry = utfstr; + (*block)->values[(*block)->length + 1].entry = NULL; + + (*block)->length++; } static void read_add(DmenuModePrivateData *pd, char *data, gsize len) { @@ -137,94 +174,152 @@ static void read_add(DmenuModePrivateData *pd, char *data, gsize len) { pd->cmd_list_length++; } -static void async_read_callback(GObject *source_object, GAsyncResult *res, - gpointer user_data) { - GDataInputStream *stream = (GDataInputStream *)source_object; + +/** + * This method is called from a GSource that responds to READ available event + * on the file descriptor of the IPC pipe with the reading thread. + * This method runs in the same thread as the UI and updates the dmenu mode + * internal administratinos with new items. + * + * The data is copied not via the pipe, but via the Async Queue. + * A maximal BLOCK_LINES_SIZE items are added with one block. + */ +static gboolean dmenu_async_read_proc(gint fd, GIOCondition condition, + gpointer user_data) { DmenuModePrivateData *pd = (DmenuModePrivateData *)user_data; - gsize len; - char *data = g_data_input_stream_read_upto_finish(stream, res, &len, NULL); - if (data != NULL) { - // Absorb separator, already in buffer so should not block. - g_data_input_stream_read_byte(stream, NULL, NULL); - read_add(pd, data, len); - g_free(data); - rofi_view_reload(); + char command; + // Read the entry from the pipe that was used to signal this action. + if (read(fd, &command, 1) == 1) { + Block *block = NULL; + gboolean changed = FALSE; + // Empty out the AsyncQueue (that is thread safe) from all blocks pushed + // into it. + while ((block = g_async_queue_try_pop(pd->async_queue)) != NULL) { - g_data_input_stream_read_upto_async(pd->data_input_stream, &(pd->separator), - 1, G_PRIORITY_LOW, pd->cancel, - async_read_callback, pd); - return; - } else { - GError *error = NULL; - // Absorb separator, already in buffer so should not block. - // If error == NULL end of stream.. - g_data_input_stream_read_byte(stream, NULL, &error); - if (error == NULL) { - // Add empty line. - read_add(pd, "", 0); + if (pd->cmd_list_real_length < (pd->cmd_list_length + block->length)) { + pd->cmd_list_real_length = MAX(pd->cmd_list_real_length * 2, 4096); + pd->cmd_list = g_realloc(pd->cmd_list, sizeof(DmenuScriptEntry) * + pd->cmd_list_real_length); + } + memcpy(&(pd->cmd_list[pd->cmd_list_length]), &(block->values[0]), + sizeof(DmenuScriptEntry) * block->length); + pd->cmd_list_length += block->length; + g_free(block); + changed = TRUE; + } + if (changed) { rofi_view_reload(); - - g_data_input_stream_read_upto_async(pd->data_input_stream, - &(pd->separator), 1, G_PRIORITY_LOW, - pd->cancel, async_read_callback, pd); - return; } - g_error_free(error); - } - if (!g_cancellable_is_cancelled(pd->cancel)) { - // Hack, don't use get active. - g_debug("Clearing overlay"); - rofi_view_set_overlay(rofi_view_get_active(), NULL); - g_input_stream_close_async(G_INPUT_STREAM(stream), G_PRIORITY_LOW, - pd->cancel, async_close_callback, pd); } + return G_SOURCE_CONTINUE; } -static void async_read_cancel(G_GNUC_UNUSED GCancellable *cancel, - G_GNUC_UNUSED gpointer data) { - g_debug("Cancelled the async read."); -} - -static int get_dmenu_async(DmenuModePrivateData *pd, int sync_pre_read) { - while (sync_pre_read--) { - gsize len = 0; - char *data = g_data_input_stream_read_upto( - pd->data_input_stream, &(pd->separator), 1, &len, NULL, NULL); - if (data == NULL) { - g_input_stream_close_async(G_INPUT_STREAM(pd->input_stream), - G_PRIORITY_LOW, pd->cancel, - async_close_callback, pd); - return FALSE; - } - g_data_input_stream_read_byte(pd->data_input_stream, NULL, NULL); - read_add(pd, data, len); - g_free(data); +static void read_input_sync(DmenuModePrivateData *pd, unsigned int pre_read) { + size_t nread = 0; + size_t len = 0; + char *line = NULL; + while (pre_read > 0 && + (nread = getdelim(&line, &len, pd->separator, pd->fd_file)) != -1) { + read_add(pd, line, len); + pre_read--; } - g_data_input_stream_read_upto_async(pd->data_input_stream, &(pd->separator), - 1, G_PRIORITY_LOW, pd->cancel, - async_read_callback, pd); - return TRUE; + free(line); + return; } -static void get_dmenu_sync(DmenuModePrivateData *pd) { - while (TRUE) { - gsize len = 0; - char *data = g_data_input_stream_read_upto( - pd->data_input_stream, &(pd->separator), 1, &len, NULL, NULL); - if (data == NULL) { +static gpointer read_input_thread(gpointer userdata) { + DmenuModePrivateData *pd = (DmenuModePrivateData *)userdata; + ssize_t nread = 0; + size_t len = 0; + char *line = NULL; + // Create the message passing queue to the UI thread. + pd->async_queue = g_async_queue_new(); + Block *block = NULL; + + int fd = pd->fd; + while (1) { + // Wait for input from the input or from the main thread. + fd_set rfds; + // We wait for 0.25 seconds, before we flush what we have. + struct timeval tv = {.tv_sec = 0, .tv_usec = 250000}; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + FD_SET(pd->pipefd[0], &rfds); + + int retval = select(MAX(fd, pd->pipefd[0]) + 1, &rfds, NULL, NULL, &tv); + if (retval == -1) { + g_warning("select failed, giving up."); break; + } else if (retval) { + // We get input from the UI thread, this is always an abort. + if (FD_ISSET(pd->pipefd[0], &rfds)) { + break; + } + // Input data is available. + if (FD_ISSET(fd, &rfds)) { + ssize_t readbytes = 0; + if ((nread + 1024) > len) { + line = g_realloc(line, (nread + 1024)); + len = nread + 1024; + } + readbytes = read(fd, &line[nread], 1023); + if (readbytes > 0) { + nread += readbytes; + line[nread] = '\0'; + size_t i = 0; + while (i < nread) { + if (line[i] == pd->separator) { + line[i] = '\0'; + read_add_block(pd, &block, line, i); + memmove(&line[0], &line[i + 1], nread - (i + 1)); + nread -= (i + 1); + i = 0; + if (block && block->length == BLOCK_LINES_SIZE) { + g_async_queue_push(pd->async_queue, block); + block = NULL; + write(pd->pipefd2[1], "r", 1); + } + } else { + i++; + } + } + } else { + // remainder in buffer, then quit. + if (nread > 0) { + line[nread] = '\0'; + read_add_block(pd, &block, line, nread); + } + if (block) { + g_async_queue_push(pd->async_queue, block); + block = NULL; + write(pd->pipefd2[1], "r", 1); + } + break; + } + } + } else { + // Timeout, pushout remainder data. + if (nread > 0) { + line[nread] = '\0'; + read_add_block(pd, &block, line, nread); + nread = 0; + } + if (block) { + g_async_queue_push(pd->async_queue, block); + block = NULL; + write(pd->pipefd2[1], "r", 1); + } } - g_data_input_stream_read_byte(pd->data_input_stream, NULL, NULL); - read_add(pd, data, len); - g_free(data); } - g_input_stream_close_async(G_INPUT_STREAM(pd->input_stream), G_PRIORITY_LOW, - pd->cancel, async_close_callback, pd); + free(line); + return NULL; } static unsigned int dmenu_mode_get_num_entries(const Mode *sw) { const DmenuModePrivateData *rmpd = (const DmenuModePrivateData *)mode_get_private_data(sw); - return rmpd->cmd_list_length; + unsigned int retv = rmpd->cmd_list_length; + return retv; } static gchar *dmenu_format_output_string(const DmenuModePrivateData *pd, @@ -296,7 +391,9 @@ static char *get_display_data(const Mode *data, unsigned int index, int *state, if (pd->do_markup) { *state |= MARKUP; } - return get_entry ? dmenu_format_output_string(pd, retv[index].entry) : NULL; + char *my_retv = + (get_entry ? dmenu_format_output_string(pd, retv[index].entry) : NULL); + return my_retv; } static void dmenu_mode_free(Mode *sw) { @@ -305,20 +402,6 @@ static void dmenu_mode_free(Mode *sw) { } DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw); if (pd != NULL) { - if (pd->cancel) { - // If open, cancel reads. - if (pd->input_stream && !g_input_stream_is_closed(pd->input_stream)) { - g_cancellable_cancel(pd->cancel); - } - // This blocks until cancel is done. - g_cancellable_disconnect(pd->cancel, pd->cancel_source); - if (pd->input_stream) { - // Should close the stream if not yet done. - g_object_unref(pd->data_input_stream); - g_object_unref(pd->input_stream); - } - g_object_unref(pd->cancel); - } for (size_t i = 0; i < pd->cmd_list_length; i++) { if (pd->cmd_list[i].entry) { @@ -363,6 +446,16 @@ static int dmenu_mode_init(Mode *sw) { mode_set_private_data(sw, g_malloc0(sizeof(DmenuModePrivateData))); DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw); + pd->async = TRUE; + + // For now these only work in sync mode. + if (find_arg("-sync") >= 0 || find_arg("-dump") >= 0 || + find_arg("-select") >= 0 || find_arg("-no-custom") >= 0 || + find_arg("-only-match") >= 0 || config.auto_select || + find_arg("-selected-row") >= 0) { + pd->async = FALSE; + } + pd->separator = '\n'; pd->selected_line = UINT32_MAX; @@ -426,30 +519,52 @@ static int dmenu_mode_init(Mode *sw) { if (find_arg("-i") >= 0) { config.case_sensitive = FALSE; } - int fd = STDIN_FILENO; - str = NULL; - if (find_arg_str("-input", &str)) { - char *estr = rofi_expand_path(str); - fd = open(str, O_RDONLY); - if (fd < 0) { - char *msg = g_markup_printf_escaped( - "Failed to open file: %s:\n\t%s", estr, - g_strerror(errno)); - rofi_view_error_dialog(msg, TRUE); - g_free(msg); + if (pd->async) { + pd->fd = STDIN_FILENO; + if (find_arg_str("-input", &str)) { + char *estr = rofi_expand_path(str); + pd->fd = open(str, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (pd->fd == -1) { + char *msg = g_markup_printf_escaped( + "Failed to open file: %s:\n\t%s", estr, + g_strerror(errno)); + rofi_view_error_dialog(msg, TRUE); + g_free(msg); + g_free(estr); + return TRUE; + } g_free(estr); - return TRUE; } - g_free(estr); - } - // If input is stdin, and a tty, do not read as rofi grabs input and therefore - // blocks. - if (!(fd == STDIN_FILENO && isatty(fd) == 1)) { - pd->cancel = g_cancellable_new(); - pd->cancel_source = g_cancellable_connect( - pd->cancel, G_CALLBACK(async_read_cancel), pd, NULL); - pd->input_stream = g_unix_input_stream_new(fd, fd != STDIN_FILENO); - pd->data_input_stream = g_data_input_stream_new(pd->input_stream); + + if (pipe(pd->pipefd) == -1) { + g_error("Failed to create pipe"); + } + if (pipe(pd->pipefd2) == -1) { + g_error("Failed to create pipe"); + } + pd->wake_source = + g_unix_fd_add(pd->pipefd2[0], G_IO_IN, dmenu_async_read_proc, pd); + pd->reading_thread = + g_thread_new("dmenu-read", (GThreadFunc)read_input_thread, pd); + } else { + pd->fd_file = stdin; + str = NULL; + if (find_arg_str("-input", &str)) { + char *estr = rofi_expand_path(str); + pd->fd_file = fopen(str, "r"); + if (pd->fd_file == NULL) { + char *msg = g_markup_printf_escaped( + "Failed to open file: %s:\n\t%s", estr, + g_strerror(errno)); + rofi_view_error_dialog(msg, TRUE); + g_free(msg); + g_free(estr); + return TRUE; + } + g_free(estr); + } + + read_input_sync(pd, -1); } gchar *columns = NULL; if (find_arg_str("-display-columns", &columns)) { @@ -464,6 +579,7 @@ static int dmenu_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index) { DmenuModePrivateData *rmpd = (DmenuModePrivateData *)mode_get_private_data(sw); + /** Strip out the markup when matching. */ char *esc = NULL; if (rmpd->do_markup) { @@ -506,6 +622,7 @@ static char *dmenu_get_message(const Mode *sw) { static cairo_surface_t *dmenu_get_icon(const Mode *sw, unsigned int selected_line, int height) { DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw); + g_return_val_if_fail(pd->cmd_list != NULL, NULL); DmenuScriptEntry *dr = &(pd->cmd_list[selected_line]); if (dr->icon_name == NULL) { @@ -514,8 +631,10 @@ static cairo_surface_t *dmenu_get_icon(const Mode *sw, if (dr->icon_fetch_uid > 0) { return rofi_icon_fetcher_get(dr->icon_fetch_uid); } - dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->icon_name, height); - return rofi_icon_fetcher_get(dr->icon_fetch_uid); + uint32_t uid = dr->icon_fetch_uid = + rofi_icon_fetcher_query(dr->icon_name, height); + + return rofi_icon_fetcher_get(uid); } static void dmenu_finish(RofiViewState *state, int retv) { @@ -557,6 +676,33 @@ static void dmenu_finalize(RofiViewState *state) { int retv = FALSE; DmenuModePrivateData *pd = (DmenuModePrivateData *)rofi_view_get_mode(state)->private_data; + + if (pd->reading_thread) { + // Stop listinig to new messages from reading thread. + if (pd->wake_source > 0) { + g_source_remove(pd->wake_source); + } + // signal stop. + write(pd->pipefd[1], "q", 1); + g_thread_join(pd->reading_thread); + pd->reading_thread = NULL; + /* empty the queue, remove idle callbacks if still pending. */ + g_async_queue_lock(pd->async_queue); + Block *block = NULL; + while ((block = g_async_queue_try_pop_unlocked(pd->async_queue)) != NULL) { + g_free(block); + } + g_async_queue_unlock(pd->async_queue); + g_async_queue_unref(pd->async_queue); + pd->async_queue = NULL; + close(pd->pipefd[0]); + close(pd->pipefd[1]); + } + if (pd->fd_file != NULL) { + if (pd->fd_file != stdin) { + fclose(pd->fd_file); + } + } unsigned int cmd_list_length = pd->cmd_list_length; DmenuScriptEntry *cmd_list = pd->cmd_list; @@ -682,26 +828,7 @@ int dmenu_mode_dialog(void) { mode_init(&dmenu_mode); MenuFlags menu_flags = MENU_NORMAL; DmenuModePrivateData *pd = (DmenuModePrivateData *)dmenu_mode.private_data; - int async = TRUE; - // For now these only work in sync mode. - if (find_arg("-sync") >= 0 || find_arg("-dump") >= 0 || - find_arg("-select") >= 0 || find_arg("-no-custom") >= 0 || - find_arg("-only-match") >= 0 || config.auto_select || - find_arg("-selected-row") >= 0) { - async = FALSE; - } - - // Check if the subsystem is setup for reading, otherwise do not read. - if (pd->cancel != NULL) { - if (async) { - unsigned int pre_read = 25; - find_arg_uint("-async-pre-read", &pre_read); - async = get_dmenu_async(pd, pre_read); - } else { - get_dmenu_sync(pd); - } - } char *input = NULL; unsigned int cmd_list_length = pd->cmd_list_length; DmenuScriptEntry *cmd_list = pd->cmd_list; @@ -766,10 +893,6 @@ int dmenu_mode_dialog(void) { if (find_arg("-keep-right") >= 0) { rofi_view_ellipsize_start(state); } - // @TODO we should do this better. - if (async && (pd->cancel != NULL)) { - rofi_view_set_overlay(state, "Loading.. "); - } rofi_view_set_selected_line(state, pd->selected_line); rofi_view_set_active(state); @@ -814,9 +937,6 @@ void print_dmenu_options(void) { print_help_msg("-sync", "", "Force dmenu to first read all input data, then show dialog.", NULL, is_term); - print_help_msg("-async-pre-read", "[number]", - "Read several entries blocking before switching to async mode", - "25", is_term); print_help_msg("-w", "windowid", "Position over window with X11 windowid.", NULL, is_term); print_help_msg("-keep-right", "", "Set ellipsize to end.", NULL, is_term); diff --git a/source/view.c b/source/view.c index 27d99593..6aa9504f 100644 --- a/source/view.c +++ b/source/view.c @@ -111,6 +111,9 @@ struct { workarea mon; /** timeout for reloading */ guint idle_timeout; + /** timeout for reloading */ + guint refilter_timeout; + guint refilter_timeout_count; /** timeout handling */ guint user_timeout; /** debug counter for redraws */ @@ -130,6 +133,8 @@ struct { .flags = MENU_NORMAL, .views = G_QUEUE_INIT, .idle_timeout = 0, + .refilter_timeout = 0, + .refilter_timeout_count = 0, .user_timeout = 0, .count = 0L, .repaint_source = 0, @@ -269,6 +274,7 @@ static gboolean rofi_view_repaint(G_GNUC_UNUSED void *data) { // Repaint the view (if needed). // After a resize the edit_pixmap surface might not contain anything // anymore. If we already re-painted, this does nothing. + rofi_view_update(current_active_menu, FALSE); g_debug("expose event"); TICK_N("Expose"); @@ -448,6 +454,13 @@ static void rofi_view_reload_message_bar(RofiViewState *state) { static gboolean rofi_view_reload_idle(G_GNUC_UNUSED gpointer data) { if (current_active_menu) { + // For UI update on this. + if (current_active_menu->tb_total_rows) { + char *r = + g_strdup_printf("%u", mode_get_num_entries(current_active_menu->sw)); + textbox_text(current_active_menu->tb_total_rows, r); + g_free(r); + } current_active_menu->reload = TRUE; current_active_menu->refilter = TRUE; rofi_view_queue_redraw(); @@ -1103,9 +1116,11 @@ static void _rofi_view_reload_row(RofiViewState *state) { rofi_view_reload_message_bar(state); } -static void rofi_view_refilter(RofiViewState *state) { +static gboolean rofi_view_refilter_real(RofiViewState *state) { + CacheState.refilter_timeout = 0; + CacheState.refilter_timeout_count = 0; if (state->sw == NULL) { - return; + return G_SOURCE_REMOVE; } TICK_N("Filter start"); if (state->reload) { @@ -1131,6 +1146,9 @@ static void rofi_view_refilter(RofiViewState *state) { * speedup of the whole function. */ unsigned int nt = MAX(1, state->num_lines / 500); + // Limit the number of jobs, it could cause stack overflow if we don´t + // limit. + nt = MIN(nt, config.threads * 4); thread_state_view states[nt]; GCond cond; GMutex mutex; @@ -1220,6 +1238,33 @@ static void rofi_view_refilter(RofiViewState *state) { TICK_N("Filter resize window based on window "); state->refilter = FALSE; TICK_N("Filter done"); + rofi_view_update(state, TRUE); + return G_SOURCE_REMOVE; +} +static void rofi_view_refilter(RofiViewState *state) { + CacheState.refilter_timeout_count++; + if (CacheState.refilter_timeout != 0) { + + g_source_remove(CacheState.refilter_timeout); + CacheState.refilter_timeout = 0; + } + if (state->num_lines > config.refilter_timeout_limit && + CacheState.refilter_timeout_count < 25 && state->text && + strlen(state->text->text) > 0) { + CacheState.refilter_timeout = + g_timeout_add(200, (GSourceFunc)rofi_view_refilter_real, state); + } else { + rofi_view_refilter_real(state); + } +} +static void rofi_view_refilter_force(RofiViewState *state) { + if (CacheState.refilter_timeout != 0) { + g_source_remove(CacheState.refilter_timeout); + CacheState.refilter_timeout = 0; + } + if (state->refilter) { + rofi_view_refilter_real(state); + } } /** * @param state The Menu Handle @@ -1433,6 +1478,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) { break; } case ACCEPT_ALT: { + rofi_view_refilter_force(state); unsigned int selected = listview_get_selected(state->list_view); state->selected_line = UINT32_MAX; if (selected < state->filtered_lines) { @@ -1447,18 +1493,21 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) { break; } case ACCEPT_CUSTOM: { + rofi_view_refilter_force(state); state->selected_line = UINT32_MAX; state->retv = MENU_CUSTOM_INPUT; state->quit = TRUE; break; } case ACCEPT_CUSTOM_ALT: { + rofi_view_refilter_force(state); state->selected_line = UINT32_MAX; state->retv = MENU_CUSTOM_INPUT | MENU_CUSTOM_ACTION; state->quit = TRUE; break; } case ACCEPT_ENTRY: { + rofi_view_refilter_force(state); // If a valid item is selected, return that.. unsigned int selected = listview_get_selected(state->list_view); state->selected_line = UINT32_MAX; @@ -1633,6 +1682,7 @@ void rofi_view_maybe_update(RofiViewState *state) { rofi_view_refilter(state); } rofi_view_update(state, TRUE); + return; } /** @@ -2136,6 +2186,10 @@ void rofi_view_cleanup() { g_source_remove(CacheState.idle_timeout); CacheState.idle_timeout = 0; } + if (CacheState.refilter_timeout > 0) { + g_source_remove(CacheState.refilter_timeout); + CacheState.refilter_timeout = 0; + } if (CacheState.user_timeout > 0) { g_source_remove(CacheState.user_timeout); CacheState.user_timeout = 0; diff --git a/source/widgets/scrollbar.c b/source/widgets/scrollbar.c index 3573a688..46ffd69e 100644 --- a/source/widgets/scrollbar.c +++ b/source/widgets/scrollbar.c @@ -161,9 +161,9 @@ void scrollbar_set_handle_length(scrollbar *sb, unsigned int pos_length) { */ static void scrollbar_draw(widget *wid, cairo_t *draw) { scrollbar *sb = (scrollbar *)wid; - unsigned int wh = widget_padding_get_remaining_height(wid); + double wh = widget_padding_get_remaining_height(wid); // Calculate position and size. - unsigned int r = (sb->length * wh) / ((double)(sb->length + sb->pos_length)); + double r = (sb->length * wh) / ((double)(sb->length + sb->pos_length)); unsigned int handle = wid->h - r; double sec = ((r) / (double)(sb->length - 1)); unsigned int height = handle; diff --git a/source/xrmoptions.c b/source/xrmoptions.c index b692fb91..fb272712 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -424,6 +424,13 @@ static XrmOption xrmOptions[] = { NULL, "Fallback icon to use when the application icon is not found in run/drun.", CONFIG_DEFAULT}, + {xrm_Number, + "refilter-timeout-limit", + {.num = &(config.refilter_timeout_limit)}, + NULL, + "When there are more entries then this limit, only refilter after a " + "timeout.", + CONFIG_DEFAULT}, }; /** Dynamic array of extra options */