mirror of
https://github.com/davatorium/rofi.git
synced 2024-11-25 13:55:34 -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:
parent
9f0a8c9e36
commit
e2a7cfdd52
10 changed files with 351 additions and 166 deletions
|
@ -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};
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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.
|
||||||
|
while ((block = g_async_queue_try_pop(pd->async_queue)) != NULL) {
|
||||||
|
|
||||||
|
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();
|
rofi_view_reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
g_data_input_stream_read_upto_async(pd->data_input_stream, &(pd->separator),
|
static void read_input_sync(DmenuModePrivateData *pd, unsigned int pre_read) {
|
||||||
1, G_PRIORITY_LOW, pd->cancel,
|
size_t nread = 0;
|
||||||
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;
|
||||||
} 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);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
Loading…
Reference in a new issue