1
0
Fork 0
mirror of https://github.com/davatorium/rofi.git synced 2024-11-18 13:54:36 -05:00

[DMenu] Huge list speedups (#1621)

* 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
This commit is contained in:
Dave Davenport 2022-04-20 21:53:44 +02:00 committed by GitHub
parent 9f0a8c9e36
commit e2a7cfdd52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 351 additions and 166 deletions

View file

@ -153,4 +153,6 @@ Settings config = {
/** steal focus */ /** steal focus */
.steal_focus = FALSE, .steal_focus = FALSE,
/** fallback icon */ /** fallback icon */
.application_fallback_icon = NULL}; .application_fallback_icon = NULL,
/** refilter limit */
.refilter_timeout_limit = 8192};

View file

@ -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, 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\&. 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 .PP
\fB\fC-window-title\fR \fItitle\fP \fB\fC-window-title\fR \fItitle\fP

View file

@ -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, Note: the default asynchronous mode will also be automatically disabled if used with conflicting options,
such as `-dump`, `-only-match` or `-auto-select`. 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* `-window-title` *title*
Set name used for the window title. Will be shown as Rofi - *title* Set name used for the window title. Will be shown as Rofi - *title*

View file

@ -455,6 +455,15 @@ Make \fBrofi\fP react like a normal application window. Useful for scripts like
.PP .PP
Make rofi steal focus on launch and restore close to window that held it when launched. 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 .SS Matching
.PP .PP
\fB\fC-matching\fR \fImethod\fP \fB\fC-matching\fR \fImethod\fP

View file

@ -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. 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
`-matching` *method* `-matching` *method*

View file

@ -177,6 +177,10 @@ typedef struct {
gboolean steal_focus; gboolean steal_focus;
/** fallback icon */ /** fallback icon */
char *application_fallback_icon; char *application_fallback_icon;
/** refilter timeout limit, when more then these entries,go into timeout mode.
*/
unsigned int refilter_timeout_limit;
} Settings; } Settings;
/** Default number of lines in the list view */ /** Default number of lines in the list view */

View file

@ -41,6 +41,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <gio/gunixinputstream.h> #include <gio/gunixinputstream.h>
#include <glib-unix.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -97,16 +98,52 @@ typedef struct {
gchar *column_separator; gchar *column_separator;
gboolean multi_select; gboolean multi_select;
GCancellable *cancel; GThread *reading_thread;
gulong cancel_source; GAsyncQueue *async_queue;
GInputStream *input_stream; gboolean async;
GDataInputStream *data_input_stream; FILE *fd_file;
int fd;
int pipefd[2];
int pipefd2[2];
guint wake_source;
} DmenuModePrivateData; } DmenuModePrivateData;
static void async_close_callback(GObject *source_object, GAsyncResult *res, #define BLOCK_LINES_SIZE 2048
G_GNUC_UNUSED gpointer user_data) { typedef struct {
g_input_stream_close_finish(G_INPUT_STREAM(source_object), res, NULL); unsigned int length;
g_debug("Closing data stream."); 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) { 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++; pd->cmd_list_length++;
} }
static void async_read_callback(GObject *source_object, GAsyncResult *res,
/**
* 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) { gpointer user_data) {
GDataInputStream *stream = (GDataInputStream *)source_object;
DmenuModePrivateData *pd = (DmenuModePrivateData *)user_data; DmenuModePrivateData *pd = (DmenuModePrivateData *)user_data;
gsize len; char command;
char *data = g_data_input_stream_read_upto_finish(stream, res, &len, NULL); // Read the entry from the pipe that was used to signal this action.
if (data != NULL) { if (read(fd, &command, 1) == 1) {
// Absorb separator, already in buffer so should not block. Block *block = NULL;
g_data_input_stream_read_byte(stream, NULL, NULL); gboolean changed = FALSE;
read_add(pd, data, len); // Empty out the AsyncQueue (that is thread safe) from all blocks pushed
g_free(data); // into it.
rofi_view_reload(); while ((block = g_async_queue_try_pop(pd->async_queue)) != NULL) {
g_data_input_stream_read_upto_async(pd->data_input_stream, &(pd->separator), if (pd->cmd_list_real_length < (pd->cmd_list_length + block->length)) {
1, G_PRIORITY_LOW, pd->cancel, pd->cmd_list_real_length = MAX(pd->cmd_list_real_length * 2, 4096);
async_read_callback, pd); pd->cmd_list = g_realloc(pd->cmd_list, sizeof(DmenuScriptEntry) *
return; pd->cmd_list_real_length);
} else { }
GError *error = NULL; memcpy(&(pd->cmd_list[pd->cmd_list_length]), &(block->values[0]),
// Absorb separator, already in buffer so should not block. sizeof(DmenuScriptEntry) * block->length);
// If error == NULL end of stream.. pd->cmd_list_length += block->length;
g_data_input_stream_read_byte(stream, NULL, &error); g_free(block);
if (error == NULL) { changed = TRUE;
// Add empty line. }
read_add(pd, "", 0); if (changed) {
rofi_view_reload(); rofi_view_reload();
}
}
return G_SOURCE_CONTINUE;
}
g_data_input_stream_read_upto_async(pd->data_input_stream, static void read_input_sync(DmenuModePrivateData *pd, unsigned int pre_read) {
&(pd->separator), 1, G_PRIORITY_LOW, size_t nread = 0;
pd->cancel, async_read_callback, pd); 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--;
}
free(line);
return; return;
} }
g_error_free(error); static gpointer read_input_thread(gpointer userdata) {
} DmenuModePrivateData *pd = (DmenuModePrivateData *)userdata;
if (!g_cancellable_is_cancelled(pd->cancel)) { ssize_t nread = 0;
// Hack, don't use get active. size_t len = 0;
g_debug("Clearing overlay"); char *line = NULL;
rofi_view_set_overlay(rofi_view_get_active(), NULL); // Create the message passing queue to the UI thread.
g_input_stream_close_async(G_INPUT_STREAM(stream), G_PRIORITY_LOW, pd->async_queue = g_async_queue_new();
pd->cancel, async_close_callback, pd); Block *block = NULL;
}
}
static void async_read_cancel(G_GNUC_UNUSED GCancellable *cancel, int fd = pd->fd;
G_GNUC_UNUSED gpointer data) { while (1) {
g_debug("Cancelled the async read."); // 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};
static int get_dmenu_async(DmenuModePrivateData *pd, int sync_pre_read) { FD_ZERO(&rfds);
while (sync_pre_read--) { FD_SET(fd, &rfds);
gsize len = 0; FD_SET(pd->pipefd[0], &rfds);
char *data = g_data_input_stream_read_upto(
pd->data_input_stream, &(pd->separator), 1, &len, NULL, NULL); int retval = select(MAX(fd, pd->pipefd[0]) + 1, &rfds, NULL, NULL, &tv);
if (data == NULL) { if (retval == -1) {
g_input_stream_close_async(G_INPUT_STREAM(pd->input_stream), g_warning("select failed, giving up.");
G_PRIORITY_LOW, pd->cancel, break;
async_close_callback, pd); } else if (retval) {
return FALSE; // We get input from the UI thread, this is always an abort.
} if (FD_ISSET(pd->pipefd[0], &rfds)) {
g_data_input_stream_read_byte(pd->data_input_stream, NULL, NULL);
read_add(pd, data, len);
g_free(data);
}
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;
}
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) {
break; break;
} }
g_data_input_stream_read_byte(pd->data_input_stream, NULL, NULL); // Input data is available.
read_add(pd, data, len); if (FD_ISSET(fd, &rfds)) {
g_free(data); ssize_t readbytes = 0;
if ((nread + 1024) > len) {
line = g_realloc(line, (nread + 1024));
len = nread + 1024;
} }
g_input_stream_close_async(G_INPUT_STREAM(pd->input_stream), G_PRIORITY_LOW, readbytes = read(fd, &line[nread], 1023);
pd->cancel, async_close_callback, pd); 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);
}
}
}
free(line);
return NULL;
} }
static unsigned int dmenu_mode_get_num_entries(const Mode *sw) { static unsigned int dmenu_mode_get_num_entries(const Mode *sw) {
const DmenuModePrivateData *rmpd = const DmenuModePrivateData *rmpd =
(const DmenuModePrivateData *)mode_get_private_data(sw); (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, 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) { if (pd->do_markup) {
*state |= 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) { 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); DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw);
if (pd != NULL) { 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++) { for (size_t i = 0; i < pd->cmd_list_length; i++) {
if (pd->cmd_list[i].entry) { 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))); mode_set_private_data(sw, g_malloc0(sizeof(DmenuModePrivateData)));
DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw); 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->separator = '\n';
pd->selected_line = UINT32_MAX; pd->selected_line = UINT32_MAX;
@ -426,12 +519,12 @@ static int dmenu_mode_init(Mode *sw) {
if (find_arg("-i") >= 0) { if (find_arg("-i") >= 0) {
config.case_sensitive = FALSE; config.case_sensitive = FALSE;
} }
int fd = STDIN_FILENO; if (pd->async) {
str = NULL; pd->fd = STDIN_FILENO;
if (find_arg_str("-input", &str)) { if (find_arg_str("-input", &str)) {
char *estr = rofi_expand_path(str); char *estr = rofi_expand_path(str);
fd = open(str, O_RDONLY); pd->fd = open(str, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
if (fd < 0) { if (pd->fd == -1) {
char *msg = g_markup_printf_escaped( char *msg = g_markup_printf_escaped(
"Failed to open file: <b>%s</b>:\n\t<i>%s</i>", estr, "Failed to open file: <b>%s</b>:\n\t<i>%s</i>", estr,
g_strerror(errno)); g_strerror(errno));
@ -442,14 +535,36 @@ static int dmenu_mode_init(Mode *sw) {
} }
g_free(estr); g_free(estr);
} }
// If input is stdin, and a tty, do not read as rofi grabs input and therefore
// blocks. if (pipe(pd->pipefd) == -1) {
if (!(fd == STDIN_FILENO && isatty(fd) == 1)) { g_error("Failed to create pipe");
pd->cancel = g_cancellable_new(); }
pd->cancel_source = g_cancellable_connect( if (pipe(pd->pipefd2) == -1) {
pd->cancel, G_CALLBACK(async_read_cancel), pd, NULL); g_error("Failed to create pipe");
pd->input_stream = g_unix_input_stream_new(fd, fd != STDIN_FILENO); }
pd->data_input_stream = g_data_input_stream_new(pd->input_stream); 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: <b>%s</b>:\n\t<i>%s</i>", 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; gchar *columns = NULL;
if (find_arg_str("-display-columns", &columns)) { 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) { unsigned int index) {
DmenuModePrivateData *rmpd = DmenuModePrivateData *rmpd =
(DmenuModePrivateData *)mode_get_private_data(sw); (DmenuModePrivateData *)mode_get_private_data(sw);
/** Strip out the markup when matching. */ /** Strip out the markup when matching. */
char *esc = NULL; char *esc = NULL;
if (rmpd->do_markup) { 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, static cairo_surface_t *dmenu_get_icon(const Mode *sw,
unsigned int selected_line, int height) { unsigned int selected_line, int height) {
DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw); DmenuModePrivateData *pd = (DmenuModePrivateData *)mode_get_private_data(sw);
g_return_val_if_fail(pd->cmd_list != NULL, NULL); g_return_val_if_fail(pd->cmd_list != NULL, NULL);
DmenuScriptEntry *dr = &(pd->cmd_list[selected_line]); DmenuScriptEntry *dr = &(pd->cmd_list[selected_line]);
if (dr->icon_name == NULL) { 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) { if (dr->icon_fetch_uid > 0) {
return rofi_icon_fetcher_get(dr->icon_fetch_uid); return rofi_icon_fetcher_get(dr->icon_fetch_uid);
} }
dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->icon_name, height); uint32_t uid = dr->icon_fetch_uid =
return rofi_icon_fetcher_get(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) { static void dmenu_finish(RofiViewState *state, int retv) {
@ -557,6 +676,33 @@ static void dmenu_finalize(RofiViewState *state) {
int retv = FALSE; int retv = FALSE;
DmenuModePrivateData *pd = DmenuModePrivateData *pd =
(DmenuModePrivateData *)rofi_view_get_mode(state)->private_data; (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; unsigned int cmd_list_length = pd->cmd_list_length;
DmenuScriptEntry *cmd_list = pd->cmd_list; DmenuScriptEntry *cmd_list = pd->cmd_list;
@ -682,26 +828,7 @@ int dmenu_mode_dialog(void) {
mode_init(&dmenu_mode); mode_init(&dmenu_mode);
MenuFlags menu_flags = MENU_NORMAL; MenuFlags menu_flags = MENU_NORMAL;
DmenuModePrivateData *pd = (DmenuModePrivateData *)dmenu_mode.private_data; 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; char *input = NULL;
unsigned int cmd_list_length = pd->cmd_list_length; unsigned int cmd_list_length = pd->cmd_list_length;
DmenuScriptEntry *cmd_list = pd->cmd_list; DmenuScriptEntry *cmd_list = pd->cmd_list;
@ -766,10 +893,6 @@ int dmenu_mode_dialog(void) {
if (find_arg("-keep-right") >= 0) { if (find_arg("-keep-right") >= 0) {
rofi_view_ellipsize_start(state); 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_selected_line(state, pd->selected_line);
rofi_view_set_active(state); rofi_view_set_active(state);
@ -814,9 +937,6 @@ void print_dmenu_options(void) {
print_help_msg("-sync", "", print_help_msg("-sync", "",
"Force dmenu to first read all input data, then show dialog.", "Force dmenu to first read all input data, then show dialog.",
NULL, is_term); 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.", print_help_msg("-w", "windowid", "Position over window with X11 windowid.",
NULL, is_term); NULL, is_term);
print_help_msg("-keep-right", "", "Set ellipsize to end.", NULL, is_term); print_help_msg("-keep-right", "", "Set ellipsize to end.", NULL, is_term);

View file

@ -111,6 +111,9 @@ struct {
workarea mon; workarea mon;
/** timeout for reloading */ /** timeout for reloading */
guint idle_timeout; guint idle_timeout;
/** timeout for reloading */
guint refilter_timeout;
guint refilter_timeout_count;
/** timeout handling */ /** timeout handling */
guint user_timeout; guint user_timeout;
/** debug counter for redraws */ /** debug counter for redraws */
@ -130,6 +133,8 @@ struct {
.flags = MENU_NORMAL, .flags = MENU_NORMAL,
.views = G_QUEUE_INIT, .views = G_QUEUE_INIT,
.idle_timeout = 0, .idle_timeout = 0,
.refilter_timeout = 0,
.refilter_timeout_count = 0,
.user_timeout = 0, .user_timeout = 0,
.count = 0L, .count = 0L,
.repaint_source = 0, .repaint_source = 0,
@ -269,6 +274,7 @@ static gboolean rofi_view_repaint(G_GNUC_UNUSED void *data) {
// Repaint the view (if needed). // Repaint the view (if needed).
// After a resize the edit_pixmap surface might not contain anything // After a resize the edit_pixmap surface might not contain anything
// anymore. If we already re-painted, this does nothing. // anymore. If we already re-painted, this does nothing.
rofi_view_update(current_active_menu, FALSE); rofi_view_update(current_active_menu, FALSE);
g_debug("expose event"); g_debug("expose event");
TICK_N("Expose"); 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) { static gboolean rofi_view_reload_idle(G_GNUC_UNUSED gpointer data) {
if (current_active_menu) { 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->reload = TRUE;
current_active_menu->refilter = TRUE; current_active_menu->refilter = TRUE;
rofi_view_queue_redraw(); rofi_view_queue_redraw();
@ -1103,9 +1116,11 @@ static void _rofi_view_reload_row(RofiViewState *state) {
rofi_view_reload_message_bar(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) { if (state->sw == NULL) {
return; return G_SOURCE_REMOVE;
} }
TICK_N("Filter start"); TICK_N("Filter start");
if (state->reload) { if (state->reload) {
@ -1131,6 +1146,9 @@ static void rofi_view_refilter(RofiViewState *state) {
* speedup of the whole function. * speedup of the whole function.
*/ */
unsigned int nt = MAX(1, state->num_lines / 500); 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]; thread_state_view states[nt];
GCond cond; GCond cond;
GMutex mutex; GMutex mutex;
@ -1220,6 +1238,33 @@ static void rofi_view_refilter(RofiViewState *state) {
TICK_N("Filter resize window based on window "); TICK_N("Filter resize window based on window ");
state->refilter = FALSE; state->refilter = FALSE;
TICK_N("Filter done"); 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 * @param state The Menu Handle
@ -1433,6 +1478,7 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
break; break;
} }
case ACCEPT_ALT: { case ACCEPT_ALT: {
rofi_view_refilter_force(state);
unsigned int selected = listview_get_selected(state->list_view); unsigned int selected = listview_get_selected(state->list_view);
state->selected_line = UINT32_MAX; state->selected_line = UINT32_MAX;
if (selected < state->filtered_lines) { if (selected < state->filtered_lines) {
@ -1447,18 +1493,21 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
break; break;
} }
case ACCEPT_CUSTOM: { case ACCEPT_CUSTOM: {
rofi_view_refilter_force(state);
state->selected_line = UINT32_MAX; state->selected_line = UINT32_MAX;
state->retv = MENU_CUSTOM_INPUT; state->retv = MENU_CUSTOM_INPUT;
state->quit = TRUE; state->quit = TRUE;
break; break;
} }
case ACCEPT_CUSTOM_ALT: { case ACCEPT_CUSTOM_ALT: {
rofi_view_refilter_force(state);
state->selected_line = UINT32_MAX; state->selected_line = UINT32_MAX;
state->retv = MENU_CUSTOM_INPUT | MENU_CUSTOM_ACTION; state->retv = MENU_CUSTOM_INPUT | MENU_CUSTOM_ACTION;
state->quit = TRUE; state->quit = TRUE;
break; break;
} }
case ACCEPT_ENTRY: { case ACCEPT_ENTRY: {
rofi_view_refilter_force(state);
// If a valid item is selected, return that.. // If a valid item is selected, return that..
unsigned int selected = listview_get_selected(state->list_view); unsigned int selected = listview_get_selected(state->list_view);
state->selected_line = UINT32_MAX; state->selected_line = UINT32_MAX;
@ -1633,6 +1682,7 @@ void rofi_view_maybe_update(RofiViewState *state) {
rofi_view_refilter(state); rofi_view_refilter(state);
} }
rofi_view_update(state, TRUE); rofi_view_update(state, TRUE);
return;
} }
/** /**
@ -2136,6 +2186,10 @@ void rofi_view_cleanup() {
g_source_remove(CacheState.idle_timeout); g_source_remove(CacheState.idle_timeout);
CacheState.idle_timeout = 0; CacheState.idle_timeout = 0;
} }
if (CacheState.refilter_timeout > 0) {
g_source_remove(CacheState.refilter_timeout);
CacheState.refilter_timeout = 0;
}
if (CacheState.user_timeout > 0) { if (CacheState.user_timeout > 0) {
g_source_remove(CacheState.user_timeout); g_source_remove(CacheState.user_timeout);
CacheState.user_timeout = 0; CacheState.user_timeout = 0;

View file

@ -161,9 +161,9 @@ void scrollbar_set_handle_length(scrollbar *sb, unsigned int pos_length) {
*/ */
static void scrollbar_draw(widget *wid, cairo_t *draw) { static void scrollbar_draw(widget *wid, cairo_t *draw) {
scrollbar *sb = (scrollbar *)wid; 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. // 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; unsigned int handle = wid->h - r;
double sec = ((r) / (double)(sb->length - 1)); double sec = ((r) / (double)(sb->length - 1));
unsigned int height = handle; unsigned int height = handle;

View file

@ -424,6 +424,13 @@ static XrmOption xrmOptions[] = {
NULL, NULL,
"Fallback icon to use when the application icon is not found in run/drun.", "Fallback icon to use when the application icon is not found in run/drun.",
CONFIG_DEFAULT}, 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 */ /** Dynamic array of extra options */