Merging in the Recursive file browser.

Squashed commit of the following:

commit 92e730076d461622dc81e44e87ec456317514904
Author: Dave Davenport <qball@gmpclient.org>
Date:   Sun Jun 11 18:17:12 2023 +0200

    [Doc] Add regex filtering to recursivebrowser.

commit ee80c8487f9765b1e6e8ab8219a6baea089cf5af
Author: Dave Davenport <qball@gmpclient.org>
Date:   Sun Jun 11 17:49:29 2023 +0200

    [recursivebrowser] Update manpage.

commit a24b68f52362aaf1461935c2340e3bf5e31da59d
Author: Dave Davenport <qball@gmpclient.org>
Date:   Sun Jun 11 17:37:56 2023 +0200

    [Mode] Add some extra validating of the mode selected to complete.

commit cf497e8685e806521c0f61922827687adce268c9
Author: Dave Davenport <qball@gmpclient.org>
Date:   Sun Jun 4 15:12:31 2023 +0200

    [Recursive browser] Make completer selectable.

commit 722f07a803c28a406d8a610f31a24b3f7247b9ba
Author: Dave Davenport <qball@gmpclient.org>
Date:   Sun Jun 4 14:36:14 2023 +0200

    Add methods for completer to modes.

commit 7972420c30275514751802d1ed517a45bbd83da1
Author: Qball Cow <qball@blame.services>
Date:   Thu Jun 1 21:56:06 2023 +0200

    Prepare updates for new APIs.

commit dd3035a1a61f8196d394f6867701a0e1b3af30ac
Author: Dave Davenport <qball@gmpclient.org>
Date:   Wed May 10 19:24:48 2023 +0200

    [RB] Fix regex and cleanups

commit 4d2941caf32dfb946aee54c467c1319c7a89804a
Author: Dave Davenport <qball@blame.services>
Date:   Wed May 10 18:09:54 2023 +0200

    [RB] Add (unfinished regex test)

commit 848277001fc8cf9afc538067f2afa24a174f8c7f
Author: Dave Davenport <qball@blame.services>
Date:   Wed May 10 17:49:16 2023 +0200

    [RB] Pull the scanning into a separate thread.

commit f369a7f63f618bbcad10c18e73f7e2b117c515f1
Author: Dave Davenport <qball@gmpclient.org>
Date:   Wed May 3 18:35:15 2023 +0200

    [Recursive File Browser] First test version.
This commit is contained in:
Dave Davenport 2023-06-12 19:07:00 +02:00
parent 21ac2d1930
commit d27cee89fa
23 changed files with 833 additions and 20 deletions

View file

@ -11,6 +11,7 @@ if [[ $ROFI_RETV = 0 ]]
then then
echo -en "\x00delim\x1f\\x1\n" echo -en "\x00delim\x1f\\x1\n"
fi fi
echo -en "\x00message\x1fmy line1\nmyline2\nmy line3\x1"
echo -en "\x00prompt\x1fChange prompt\x1" echo -en "\x00prompt\x1fChange prompt\x1"
for a in {1..10} for a in {1..10}
do do

View file

@ -158,4 +158,6 @@ Settings config = {
.refilter_timeout_limit = 300, .refilter_timeout_limit = 300,
/** workaround for broken xserver (#300 on xserver, #611) */ /** workaround for broken xserver (#300 on xserver, #611) */
.xserver_i300_workaround = FALSE, .xserver_i300_workaround = FALSE,
/** What browser to use for completion */
.completer_mode = "recursivebrowser",
}; };

View file

@ -1191,6 +1191,30 @@ rofi -filebrowser-cancel-returns-1 true -show filebrowser
.PP .PP
The \fB\fCshow-hidden\fR can also be triggered with the \fB\fCkb-delete-entry\fR keybinding. The \fB\fCshow-hidden\fR can also be triggered with the \fB\fCkb-delete-entry\fR keybinding.
.SS Recursive Browser settings
.PP
Recursive file browser behavior can be controlled via the following options:
.PP
.RS
.nf
configuration {
recursivebrowser {
/** Directory the file browser starts in. */
directory: "/some/directory";
/** return 1 on cancel. */
cancel-returns-1: true;
/** filter entries using regex */
filter-regex: "(.*cache.*|.*\\.o)";
/** command */
command: "xdg-open";
}
}
.fi
.RE
.SS Entry history .SS Entry history
.PP .PP
The number of previous inputs for the entry box can be modified by setting The number of previous inputs for the entry box can be modified by setting

View file

@ -777,6 +777,25 @@ rofi -filebrowser-cancel-returns-1 true -show filebrowser
The `show-hidden` can also be triggered with the `kb-delete-entry` keybinding. The `show-hidden` can also be triggered with the `kb-delete-entry` keybinding.
### Recursive Browser settings
Recursive file browser behavior can be controlled via the following options:
```css
configuration {
recursivebrowser {
/** Directory the file browser starts in. */
directory: "/some/directory";
/** return 1 on cancel. */
cancel-returns-1: true;
/** filter entries using regex */
filter-regex: "(.*cache.*|.*\.o)";
/** command */
command: "xdg-open";
}
}
```
### Entry history ### Entry history
The number of previous inputs for the entry box can be modified by setting The number of previous inputs for the entry box can be modified by setting

View file

@ -32,7 +32,16 @@
G_BEGIN_DECLS G_BEGIN_DECLS
/** ABI version to check if loaded plugin is compatible. */ /** ABI version to check if loaded plugin is compatible. */
#define ABI_VERSION 6u #define ABI_VERSION 7u
typedef enum {
/** Mode type is not set */
MODE_TYPE_UNSET = 0b0000,
/** A normal mode. */
MODE_TYPE_SWITCHER = 0b0001,
/** A mode that can be used to completer */
MODE_TYPE_COMPLETER = 0b0010,
} ModeType;
/** /**
* @param data Pointer to #Mode object. * @param data Pointer to #Mode object.
@ -151,6 +160,28 @@ typedef char *(*_mode_preprocess_input)(Mode *sw, const char *input);
*/ */
typedef char *(*_mode_get_message)(const Mode *sw); typedef char *(*_mode_get_message)(const Mode *sw);
/**
* Create a new instance of this mode.
* Free (free) result after use, after using mode_destroy.
*
* @returns Instantiate a new instance of this mode.
*/
typedef Mode *(*_mode_create)( void );
/**
* @param sw The #Mode pointer
* @param menu_retv The return value
* @param input The input string
* @param selected_line The selected line
* @param the path that was completed
*
* Handle the user accepting an entry in completion mode.
*
* @returns the next action to take
*/
typedef ModeMode (*_mode_completer_result)(Mode *sw, int menu_retv, char **input,
unsigned int selected_line, char **path);
/** /**
* Structure defining a switcher. * Structure defining a switcher.
* It consists of a name, callback and if enabled * It consists of a name, callback and if enabled
@ -197,6 +228,17 @@ struct rofi_mode {
* And has data in `ed` * And has data in `ed`
*/ */
_mode_free free; _mode_free free;
/**
* Create mode.
*/
_mode_create _create;
/**
* If this mode is used as completer.
*/
_mode_completer_result _completer_result;
/** Extra fields for script */ /** Extra fields for script */
void *ed; void *ed;
@ -206,6 +248,9 @@ struct rofi_mode {
/** Fallack icon.*/ /** Fallack icon.*/
uint32_t fallback_icon_fetch_uid; uint32_t fallback_icon_fetch_uid;
uint32_t fallback_icon_not_found; uint32_t fallback_icon_not_found;
/** type */
ModeType type;
}; };
G_END_DECLS G_END_DECLS
#endif // ROFI_MODE_PRIVATE_H #endif // ROFI_MODE_PRIVATE_H

View file

@ -247,6 +247,36 @@ char *mode_preprocess_input(Mode *mode, const char *input);
* free). * free).
*/ */
char *mode_get_message(const Mode *mode); char *mode_get_message(const Mode *mode);
/**
* @param mode The mode to create an instance off.
*
* @returns a new instance of the mode.
*/
Mode *mode_create(const Mode *mode);
/**
* @param mode The mode to query
* @param menu_retv The menu return value.
* @param input Pointer to the user input string. [in][out]
* @param selected_line the line selected by the user.
* @param path get the path to the selected file. [out]
*
* Acts on the user interaction.
*
* @returns the next #ModeMode.
*/
ModeMode mode_completer_result(Mode *sw, int menu_retv, char **input,
unsigned int selected_line, char **path);
/**
* @param mode The mode to query.
*
* Check if mode is a valid completer.
*
* @returns TRUE if mode can be used as completer.
*/
gboolean mode_is_completer(const Mode *sw);
/**@}*/ /**@}*/
G_END_DECLS G_END_DECLS
#endif #endif

View file

@ -39,6 +39,7 @@
#include "modes/dmenu.h" #include "modes/dmenu.h"
#include "modes/drun.h" #include "modes/drun.h"
#include "modes/filebrowser.h" #include "modes/filebrowser.h"
#include "modes/recursivebrowser.h"
#include "modes/help-keys.h" #include "modes/help-keys.h"
#include "modes/run.h" #include "modes/run.h"
#include "modes/script.h" #include "modes/script.h"

View file

@ -0,0 +1,58 @@
/*
* rofi
*
* MIT/X11 License
* Copyright © 2013-2023 Qball Cow <qball@gmpclient.org>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef ROFI_MODE_RECURSIVE_BROWSER_H
#define ROFI_MODE_RECURSIVE_BROWSER_H
#include "mode.h"
/**
* @defgroup FileBrowserMode FileBrowser
* @ingroup MODES
*
*
* @{
*/
/** #Mode object representing the run dialog. */
extern Mode recursive_browser_mode;
/**
* Create a new filebrowser.
* @returns a new filebrowser structure.
*/
Mode *create_new_recursive_browser(void);
/**
* @param sw Mode object.
* @param mretv return value passed in.
* @param input The user input string.
* @param selected_line The user selected line.
* @param path The full path as output.
*
* @returns the state the user selected.
*/
ModeMode recursive_browser_mode_completer(Mode *sw, int mretv, char **input,
unsigned int selected_line, char **path);
/**@}*/
#endif // ROFI_MODE_RECURSIVE_BROWSER_H

View file

@ -103,6 +103,8 @@ void rofi_quit_main_loop(void);
* @return returns Mode * when found, NULL if not. * @return returns Mode * when found, NULL if not.
*/ */
Mode *rofi_collect_modes_search(const char *name); Mode *rofi_collect_modes_search(const char *name);
const Mode *rofi_get_completer(void);
/** Reset terminal */ /** Reset terminal */
#define color_reset "\033[0m" #define color_reset "\033[0m"
/** Set terminal text bold */ /** Set terminal text bold */

View file

@ -184,6 +184,8 @@ typedef struct {
/** workaround for broken xserver (#300 on xserver, #611) */ /** workaround for broken xserver (#300 on xserver, #611) */
gboolean xserver_i300_workaround; gboolean xserver_i300_workaround;
/** completer mode */
char *completer_mode;
} Settings; } Settings;
/** Default number of lines in the list view */ /** Default number of lines in the list view */

View file

@ -209,6 +209,7 @@ rofi_sources = files(
'source/modes/script.c', 'source/modes/script.c',
'source/modes/help-keys.c', 'source/modes/help-keys.c',
'source/modes/filebrowser.c', 'source/modes/filebrowser.c',
'source/modes/recursivebrowser.c',
'include/display.h', 'include/display.h',
'include/xcb.h', 'include/xcb.h',
'include/xcb-internal.h', 'include/xcb-internal.h',

View file

@ -44,6 +44,17 @@
int mode_init(Mode *mode) { int mode_init(Mode *mode) {
g_return_val_if_fail(mode != NULL, FALSE); g_return_val_if_fail(mode != NULL, FALSE);
g_return_val_if_fail(mode->_init != NULL, FALSE); g_return_val_if_fail(mode->_init != NULL, FALSE);
if (mode->type == MODE_TYPE_UNSET) {
g_warning("Mode '%s' does not have a type set. Please update mode/plugin.",
mode->name);
}
if ((mode->type & MODE_TYPE_COMPLETER) == MODE_TYPE_COMPLETER) {
if (mode->_completer_result == NULL) {
g_error(
"Mode '%s' is incomplete and does not implement _completer_result.",
mode->name);
}
}
// to make sure this is initialized correctly. // to make sure this is initialized correctly.
mode->fallback_icon_fetch_uid = 0; mode->fallback_icon_fetch_uid = 0;
mode->fallback_icon_not_found = FALSE; mode->fallback_icon_not_found = FALSE;
@ -205,4 +216,33 @@ char *mode_get_message(const Mode *mode) {
} }
return NULL; return NULL;
} }
Mode *mode_create(const Mode *mode) {
if (mode->_create) {
return mode->_create();
}
return NULL;
}
ModeMode mode_completer_result(Mode *mode, int menu_retv, char **input,
unsigned int selected_line, char **path) {
if ((mode->type & MODE_TYPE_COMPLETER) == 0) {
g_warning("Trying to call completer_result on non completion mode.");
return 0;
}
if (mode->_completer_result) {
return mode->_completer_result(mode, menu_retv, input, selected_line, path);
}
return 0;
}
gboolean mode_is_completer(const Mode *mode) {
if (mode) {
if ((mode->type & MODE_TYPE_COMPLETER) == MODE_TYPE_COMPLETER) {
return TRUE;
}
}
return FALSE;
}
/**@}*/ /**@}*/

View file

@ -342,4 +342,5 @@ Mode combi_mode = {.name = "combi",
._get_icon = combi_get_icon, ._get_icon = combi_get_icon,
._preprocess_input = combi_preprocess_input, ._preprocess_input = combi_preprocess_input,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER };

View file

@ -257,8 +257,6 @@ static gpointer read_input_thread(gpointer userdata) {
ssize_t nread = 0; ssize_t nread = 0;
ssize_t len = 0; ssize_t len = 0;
char *line = NULL; char *line = NULL;
// Create the message passing queue to the UI thread.
pd->async_queue = g_async_queue_new();
Block *block = NULL; Block *block = NULL;
GTimer *tim = g_timer_new(); GTimer *tim = g_timer_new();
@ -616,6 +614,8 @@ static int dmenu_mode_init(Mode *sw) {
} }
pd->wake_source = pd->wake_source =
g_unix_fd_add(pd->pipefd2[0], G_IO_IN, dmenu_async_read_proc, pd); g_unix_fd_add(pd->pipefd2[0], G_IO_IN, dmenu_async_read_proc, pd);
// Create the message passing queue to the UI thread.
pd->async_queue = g_async_queue_new();
pd->reading_thread = pd->reading_thread =
g_thread_new("dmenu-read", (GThreadFunc)read_input_thread, pd); g_thread_new("dmenu-read", (GThreadFunc)read_input_thread, pd);
pd->loading = TRUE; pd->loading = TRUE;

View file

@ -1182,8 +1182,8 @@ static ModeMode drun_mode_result(Mode *sw, int mretv, char **input,
retv = MODE_EXIT; retv = MODE_EXIT;
} else { } else {
char *path = NULL; char *path = NULL;
retv = file_browser_mode_completer(rmpd->completer, mretv, input, retv = mode_completer_result(rmpd->completer, mretv, input, selected_line,
selected_line, &path); &path);
if (retv == MODE_EXIT) { if (retv == MODE_EXIT) {
exec_cmd_entry(&(rmpd->entry_list[rmpd->selected_line]), path); exec_cmd_entry(&(rmpd->entry_list[rmpd->selected_line]), path);
} }
@ -1247,9 +1247,12 @@ static ModeMode drun_mode_result(Mode *sw, int mretv, char **input,
g_free(*input); g_free(*input);
*input = g_strdup(rmpd->old_completer_input); *input = g_strdup(rmpd->old_completer_input);
rmpd->completer = create_new_file_browser(); const Mode *comp = rofi_get_completer();
mode_init(rmpd->completer); if (comp) {
rmpd->file_complete = TRUE; rmpd->completer = mode_create(comp);
mode_init(rmpd->completer);
rmpd->file_complete = TRUE;
}
} }
g_regex_unref(regex); g_regex_unref(regex);
} }
@ -1483,6 +1486,7 @@ Mode drun_mode = {.name = "drun",
._get_icon = _get_icon, ._get_icon = _get_icon,
._preprocess_input = NULL, ._preprocess_input = NULL,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER};
#endif // ENABLE_DRUN #endif // ENABLE_DRUN

View file

@ -727,6 +727,9 @@ Mode file_browser_mode = {
._get_message = _get_message, ._get_message = _get_message,
._get_completion = _get_completion, ._get_completion = _get_completion,
._preprocess_input = NULL, ._preprocess_input = NULL,
._create = create_new_file_browser,
._completer_result = file_browser_mode_completer,
.private_data = NULL, .private_data = NULL,
.free = NULL, .free = NULL,
.type = MODE_TYPE_SWITCHER|MODE_TYPE_COMPLETER
}; };

View file

@ -118,4 +118,5 @@ Mode help_keys_mode = {.name = "keys",
._get_completion = NULL, ._get_completion = NULL,
._get_display_value = _get_display_value, ._get_display_value = _get_display_value,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER };

View file

@ -0,0 +1,539 @@
/**
* rofi-recursive_browser
*
* MIT/X11 License
* Copyright (c) 2017 Qball Cow <qball@gmpclient.org>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define G_LOG_DOMAIN "Modes.RecursiveBrowser"
#include "config.h"
#include <errno.h>
#include <gio/gio.h>
#include <gmodule.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <glib-unix.h>
#include <glib/gstdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "helper.h"
#include "history.h"
#include "mode-private.h"
#include "mode.h"
#include "modes/recursivebrowser.h"
#include "rofi.h"
#include "theme.h"
#include <stdint.h>
#include "rofi-icon-fetcher.h"
#define DEFAULT_OPEN "xdg-open"
/**
* The internal data structure holding the private data of the TEST Mode.
*/
enum FBFileType {
UP,
DIRECTORY,
RFILE,
NUM_FILE_TYPES,
};
/** Icons to use for the file type */
const char *rb_icon_name[NUM_FILE_TYPES] = {"go-up", "folder", "gtk-file"};
typedef struct {
char *name;
char *path;
enum FBFileType type;
uint32_t icon_fetch_uid;
uint32_t icon_fetch_size;
gboolean link;
time_t time;
} FBFile;
typedef struct {
char *command;
GFile *current_dir;
FBFile *array;
unsigned int array_length;
unsigned int array_length_real;
GThread *reading_thread;
GAsyncQueue *async_queue;
guint wake_source;
guint end_thread;
gboolean loading;
int pipefd2[2];
GRegex *filter_regex;
} FileBrowserModePrivateData;
static void free_list(FileBrowserModePrivateData *pd) {
for (unsigned int i = 0; i < pd->array_length; i++) {
FBFile *fb = &(pd->array[i]);
g_free(fb->name);
g_free(fb->path);
}
g_free(pd->array);
pd->array = NULL;
pd->array_length = 0;
pd->array_length_real = 0;
}
#include <dirent.h>
#include <sys/types.h>
inline static void fb_resize_array(FileBrowserModePrivateData *pd) {
if ((pd->array_length + 1) > pd->array_length_real) {
pd->array_length_real += 10240;
pd->array =
g_realloc(pd->array, (pd->array_length_real + 1) * sizeof(FBFile));
}
}
static void recursive_browser_mode_init_config(Mode *sw) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
char *msg = NULL;
gboolean found_error = FALSE;
ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
Property *p =
rofi_theme_find_property(wid, P_BOOLEAN, "cancel-returns-1", TRUE);
if (p && p->type == P_BOOLEAN && p->value.b == TRUE) {
rofi_set_return_code(1);
}
p = rofi_theme_find_property(wid, P_STRING, "filter-regex", TRUE);
if (p != NULL && p->type == P_STRING) {
GError *error = NULL;
g_debug("compile regex: %s\n", p->value.s);
pd->filter_regex = g_regex_new(p->value.s, G_REGEX_OPTIMIZE, 0, &error);
if (error) {
msg = g_strdup_printf("\"%s\" is not a valid regex for filtering: %s",
p->value.s, error->message);
found_error = TRUE;
g_error_free(error);
}
}
if (pd->filter_regex == NULL) {
g_debug("compile default regex\n");
pd->filter_regex = g_regex_new("^(\\..*)", G_REGEX_OPTIMIZE, 0, NULL);
}
p = rofi_theme_find_property(wid, P_STRING, "command", TRUE);
if (p != NULL && p->type == P_STRING) {
pd->command = g_strdup(p->value.s);
} else {
pd->command = g_strdup(DEFAULT_OPEN);
}
if (found_error) {
rofi_view_error_dialog(msg, FALSE);
g_free(msg);
}
}
static void recursive_browser_mode_init_current_dir(Mode *sw) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
Property *p = rofi_theme_find_property(wid, P_STRING, "directory", TRUE);
gboolean config_has_valid_dir = p != NULL && p->type == P_STRING &&
g_file_test(p->value.s, G_FILE_TEST_IS_DIR);
if (config_has_valid_dir) {
pd->current_dir = g_file_new_for_path(p->value.s);
}
if (pd->current_dir == NULL) {
pd->current_dir = g_file_new_for_path(g_get_home_dir());
}
}
static void scan_dir(FileBrowserModePrivateData *pd, GFile *path) {
char *cdir = g_file_get_path(path);
DIR *dir = opendir(cdir);
if (dir) {
struct dirent *rd = NULL;
while (pd->end_thread == FALSE && (rd = readdir(dir)) != NULL) {
if (g_strcmp0(rd->d_name, "..") == 0) {
continue;
}
if (g_strcmp0(rd->d_name, ".") == 0) {
continue;
}
if (pd->filter_regex &&
g_regex_match(pd->filter_regex, rd->d_name, 0, NULL)) {
continue;
}
switch (rd->d_type) {
case DT_BLK:
case DT_CHR:
case DT_FIFO:
case DT_UNKNOWN:
case DT_SOCK:
default:
break;
case DT_REG: {
FBFile *f = g_malloc0(sizeof(FBFile));
// Rofi expects utf-8, so lets convert the filename.
f->path = g_build_filename(cdir, rd->d_name, NULL);
f->name = g_filename_to_utf8(f->path, -1, NULL, NULL, NULL);
if (f->name == NULL) {
f->name = rofi_force_utf8(rd->d_name, -1);
}
f->type = (rd->d_type == DT_DIR) ? DIRECTORY : RFILE;
f->icon_fetch_uid = 0;
f->icon_fetch_size = 0;
f->link = FALSE;
g_async_queue_push(pd->async_queue, f);
if (g_async_queue_length(pd->async_queue) > 10000) {
write(pd->pipefd2[1], "r", 1);
}
break;
}
case DT_DIR: {
char *d = g_build_filename(cdir, rd->d_name, NULL);
GFile *dirp = g_file_new_for_path(d);
scan_dir(pd, dirp);
g_object_unref(dirp);
g_free(d);
break;
}
case DT_LNK: {
FBFile *f = g_malloc0(sizeof(FBFile));
// Rofi expects utf-8, so lets convert the filename.
f->path = g_build_filename(cdir, rd->d_name, NULL);
f->name = g_filename_to_utf8(f->path, -1, NULL, NULL, NULL);
if (f->name == NULL) {
f->name = rofi_force_utf8(rd->d_name, -1);
}
f->icon_fetch_uid = 0;
f->icon_fetch_size = 0;
f->link = TRUE;
// Default to file.
f->type = RFILE;
g_async_queue_push(pd->async_queue, f);
if (g_async_queue_length(pd->async_queue) > 10000) {
write(pd->pipefd2[1], "r", 1);
}
break;
}
}
}
closedir(dir);
}
g_free(cdir);
}
static gpointer recursive_browser_input_thread(gpointer userdata) {
FileBrowserModePrivateData *pd = (FileBrowserModePrivateData *)userdata;
GTimer *t = g_timer_new();
g_debug("Start scan.\n");
scan_dir(pd, pd->current_dir);
write(pd->pipefd2[1], "r", 1);
write(pd->pipefd2[1], "q", 1);
double f = g_timer_elapsed(t, NULL);
g_debug("End scan: %f\n", f);
g_timer_destroy(t);
return NULL;
}
static gboolean recursive_browser_async_read_proc(gint fd,
GIOCondition condition,
gpointer user_data) {
FileBrowserModePrivateData *pd = (FileBrowserModePrivateData *)user_data;
char command;
// Only interrested in read events.
if ((condition & G_IO_IN) != G_IO_IN) {
return G_SOURCE_CONTINUE;
}
// Read the entry from the pipe that was used to signal this action.
if (read(fd, &command, 1) == 1) {
if (command == 'r') {
FBFile *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) {
fb_resize_array(pd);
pd->array[pd->array_length] = *block;
pd->array_length++;
g_free(block);
changed = TRUE;
}
if (changed) {
rofi_view_reload();
}
} else if (command == 'q') {
if (pd->loading) {
rofi_view_set_overlay(rofi_view_get_active(), NULL);
}
}
}
return G_SOURCE_CONTINUE;
}
static int recursive_browser_mode_init(Mode *sw) {
/**
* Called on startup when enabled (in modes list)
*/
if (mode_get_private_data(sw) == NULL) {
FileBrowserModePrivateData *pd = g_malloc0(sizeof(*pd));
mode_set_private_data(sw, (void *)pd);
recursive_browser_mode_init_config(sw);
recursive_browser_mode_init_current_dir(sw);
// Load content.
if (pipe(pd->pipefd2) == -1) {
g_error("Failed to create pipe");
}
pd->wake_source = g_unix_fd_add(pd->pipefd2[0], G_IO_IN,
recursive_browser_async_read_proc, pd);
// Create the message passing queue to the UI thread.
pd->async_queue = g_async_queue_new();
pd->end_thread = FALSE;
pd->reading_thread = g_thread_new(
"dmenu-read", (GThreadFunc)recursive_browser_input_thread, pd);
pd->loading = TRUE;
}
return TRUE;
}
static unsigned int recursive_browser_mode_get_num_entries(const Mode *sw) {
const FileBrowserModePrivateData *pd =
(const FileBrowserModePrivateData *)mode_get_private_data(sw);
return pd->array_length;
}
static ModeMode recursive_browser_mode_result(Mode *sw, int mretv, char **input,
unsigned int selected_line) {
ModeMode retv = MODE_EXIT;
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
if ((mretv & MENU_CANCEL) == MENU_CANCEL) {
ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
Property *p =
rofi_theme_find_property(wid, P_BOOLEAN, "cancel-returns-1", TRUE);
if (p && p->type == P_BOOLEAN && p->value.b == TRUE) {
rofi_set_return_code(1);
}
return MODE_EXIT;
}
if (mretv & MENU_NEXT) {
retv = NEXT_DIALOG;
} else if (mretv & MENU_PREVIOUS) {
retv = PREVIOUS_DIALOG;
} else if (mretv & MENU_QUICK_SWITCH) {
retv = (mretv & MENU_LOWER_MASK);
} else if (mretv & MENU_CUSTOM_COMMAND) {
retv = (mretv & MENU_LOWER_MASK);
} else if ((mretv & MENU_OK)) {
if (selected_line < pd->array_length) {
if (pd->array[selected_line].type == RFILE) {
char *d_esc = g_shell_quote(pd->array[selected_line].path);
char *cmd = g_strdup_printf("%s %s", pd->command, d_esc);
g_free(d_esc);
char *cdir = g_file_get_path(pd->current_dir);
helper_execute_command(cdir, cmd, FALSE, NULL);
g_free(cdir);
g_free(cmd);
return MODE_EXIT;
}
}
retv = RELOAD_DIALOG;
} else if ((mretv & MENU_CUSTOM_INPUT)) {
retv = RELOAD_DIALOG;
} else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) {
retv = RELOAD_DIALOG;
}
return retv;
}
static void recursive_browser_mode_destroy(Mode *sw) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
if (pd != NULL) {
if (pd->reading_thread) {
pd->end_thread = TRUE;
g_thread_join(pd->reading_thread);
}
if (pd->filter_regex) {
g_regex_unref(pd->filter_regex);
}
g_object_unref(pd->current_dir);
g_free(pd->command);
free_list(pd);
g_free(pd);
mode_set_private_data(sw, NULL);
}
}
static char *_get_display_value(const Mode *sw, unsigned int selected_line,
G_GNUC_UNUSED int *state,
G_GNUC_UNUSED GList **attr_list,
int get_entry) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
// Only return the string if requested, otherwise only set state.
if (!get_entry) {
return NULL;
}
if (pd->array[selected_line].type == UP) {
return g_strdup(" ..");
}
if (pd->array[selected_line].link) {
return g_strconcat("@", pd->array[selected_line].name, NULL);
}
return g_strdup(pd->array[selected_line].name);
}
/**
* @param sw The mode object.
* @param tokens The tokens to match against.
* @param index The index in this plugin to match against.
*
* Match the entry.
*
* @returns try when a match.
*/
static int recursive_browser_token_match(const Mode *sw,
rofi_int_matcher **tokens,
unsigned int index) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
// Call default matching function.
return helper_token_match(tokens, pd->array[index].name);
}
static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line,
unsigned int height) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
g_return_val_if_fail(pd->array != NULL, NULL);
FBFile *dr = &(pd->array[selected_line]);
if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) {
return rofi_icon_fetcher_get(dr->icon_fetch_uid);
}
if (rofi_icon_fetcher_file_is_image(dr->path)) {
dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->path, height);
} else {
dr->icon_fetch_uid =
rofi_icon_fetcher_query(rb_icon_name[dr->type], height);
}
dr->icon_fetch_size = height;
return rofi_icon_fetcher_get(dr->icon_fetch_uid);
}
static char *_get_message(const Mode *sw) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
if (pd->current_dir) {
char *dirname = g_file_get_parse_name(pd->current_dir);
char *str =
g_markup_printf_escaped("<b>Current directory:</b> %s", dirname);
g_free(dirname);
return str;
}
return "n/a";
}
static char *_get_completion(const Mode *sw, unsigned int index) {
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
char *d = g_strescape(pd->array[index].path, NULL);
return d;
}
Mode *create_new_recursive_browser(void) {
Mode *sw = g_malloc0(sizeof(Mode));
*sw = recursive_browser_mode;
sw->private_data = NULL;
return sw;
}
#if 1
ModeMode recursive_browser_mode_completer(Mode *sw, int mretv, char **input,
unsigned int selected_line,
char **path) {
ModeMode retv = MODE_EXIT;
FileBrowserModePrivateData *pd =
(FileBrowserModePrivateData *)mode_get_private_data(sw);
if (mretv & MENU_NEXT) {
retv = NEXT_DIALOG;
} else if (mretv & MENU_PREVIOUS) {
retv = PREVIOUS_DIALOG;
} else if (mretv & MENU_QUICK_SWITCH) {
retv = (mretv & MENU_LOWER_MASK);
} else if ((mretv & MENU_OK)) {
if (selected_line < pd->array_length) {
if (pd->array[selected_line].type == RFILE) {
*path = g_strescape(pd->array[selected_line].path, NULL);
return MODE_EXIT;
}
}
retv = RELOAD_DIALOG;
} else if ((mretv & MENU_CUSTOM_INPUT) && *input) {
retv = RELOAD_DIALOG;
} else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) {
retv = RELOAD_DIALOG;
}
return retv;
}
#endif
Mode recursive_browser_mode = {
.display_name = NULL,
.abi_version = ABI_VERSION,
.name = "recursivebrowser",
.cfg_name_key = "display-recursivebrowser",
._init = recursive_browser_mode_init,
._get_num_entries = recursive_browser_mode_get_num_entries,
._result = recursive_browser_mode_result,
._destroy = recursive_browser_mode_destroy,
._token_match = recursive_browser_token_match,
._get_display_value = _get_display_value,
._get_icon = _get_icon,
._get_message = _get_message,
._get_completion = _get_completion,
._preprocess_input = NULL,
._create = create_new_recursive_browser,
._completer_result = recursive_browser_mode_completer,
.private_data = NULL,
.free = NULL,
.type = MODE_TYPE_SWITCHER | MODE_TYPE_COMPLETER};

View file

@ -440,8 +440,8 @@ static ModeMode run_mode_result(Mode *sw, int mretv, char **input,
retv = MODE_EXIT; retv = MODE_EXIT;
} else { } else {
char *path = NULL; char *path = NULL;
retv = file_browser_mode_completer(rmpd->completer, mretv, input, retv = mode_completer_result(rmpd->completer, mretv, input, selected_line,
selected_line, &path); &path);
if (retv == MODE_EXIT) { if (retv == MODE_EXIT) {
if (path == NULL) { if (path == NULL) {
exec_cmd(rmpd->cmd_list[rmpd->selected_line].entry, run_in_term); exec_cmd(rmpd->cmd_list[rmpd->selected_line].entry, run_in_term);
@ -488,9 +488,12 @@ static ModeMode run_mode_result(Mode *sw, int mretv, char **input,
g_free(*input); g_free(*input);
*input = g_strdup(rmpd->old_completer_input); *input = g_strdup(rmpd->old_completer_input);
rmpd->completer = create_new_file_browser(); const Mode *comp = rofi_get_completer();
mode_init(rmpd->completer); if (comp) {
rmpd->file_complete = TRUE; rmpd->completer = mode_create(comp);
mode_init(rmpd->completer);
rmpd->file_complete = TRUE;
}
} }
} }
return retv; return retv;
@ -572,5 +575,6 @@ Mode run_mode = {.name = "run",
._get_completion = NULL, ._get_completion = NULL,
._preprocess_input = NULL, ._preprocess_input = NULL,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER};
/** @}*/ /** @}*/

View file

@ -645,5 +645,6 @@ Mode ssh_mode = {.name = "ssh",
._get_completion = NULL, ._get_completion = NULL,
._preprocess_input = NULL, ._preprocess_input = NULL,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER };
/**@}*/ /**@}*/

View file

@ -1134,7 +1134,8 @@ Mode window_mode = {.name = "window",
._get_completion = NULL, ._get_completion = NULL,
._preprocess_input = NULL, ._preprocess_input = NULL,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER };
Mode window_mode_cd = {.name = "windowcd", Mode window_mode_cd = {.name = "windowcd",
.cfg_name_key = "display-windowcd", .cfg_name_key = "display-windowcd",
._init = window_mode_init_cd, ._init = window_mode_init_cd,
@ -1147,6 +1148,7 @@ Mode window_mode_cd = {.name = "windowcd",
._get_completion = NULL, ._get_completion = NULL,
._preprocess_input = NULL, ._preprocess_input = NULL,
.private_data = NULL, .private_data = NULL,
.free = NULL}; .free = NULL,
.type = MODE_TYPE_SWITCHER };
#endif // WINDOW_MODE #endif // WINDOW_MODE

View file

@ -165,6 +165,21 @@ static int mode_lookup(const char *name) {
} }
return -1; return -1;
} }
/**
* @param name Name of the mode to lookup.
*
* Find the index of the mode with name.
*
* @returns index of the mode in modes, -1 if not found.
*/
static const Mode *mode_available_lookup(const char *name) {
for (unsigned int i = 0; i < num_available_modes; i++) {
if (strcmp(mode_get_name(available_modes[i]), name) == 0) {
return available_modes[i];
}
}
return NULL;
}
/** /**
* Teardown the gui. * Teardown the gui.
@ -608,6 +623,7 @@ static void rofi_collect_modes(void) {
rofi_collectmodes_add(&combi_mode); rofi_collectmodes_add(&combi_mode);
rofi_collectmodes_add(&help_keys_mode); rofi_collectmodes_add(&help_keys_mode);
rofi_collectmodes_add(&file_browser_mode); rofi_collectmodes_add(&file_browser_mode);
rofi_collectmodes_add(&recursive_browser_mode);
if (find_arg("-no-plugins") < 0) { if (find_arg("-no-plugins") < 0) {
find_arg_str("-plugin-path", &(config.plugin_path)); find_arg_str("-plugin-path", &(config.plugin_path));
@ -1195,3 +1211,14 @@ int rofi_theme_rasi_validate(const char *filename) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Mode *rofi_get_completer(void) {
const Mode *index = mode_available_lookup(config.completer_mode);
if (index != NULL) {
return index;
}
const char *name =
config.completer_mode == NULL ? "(null)" : config.completer_mode;
g_warning("Mode: %s not found or is not valid for use as completer.", name);
return NULL;
}

View file

@ -439,6 +439,12 @@ static XrmOption xrmOptions[] = {
NULL, NULL,
"Workaround for XServer issue #300 (issue #611 for rofi.)", "Workaround for XServer issue #300 (issue #611 for rofi.)",
CONFIG_DEFAULT}, CONFIG_DEFAULT},
{xrm_String,
"completer-mode",
{.str = &(config.completer_mode)},
NULL,
"What completer to use for drun/run.",
CONFIG_DEFAULT},
}; };
/** Dynamic array of extra options */ /** Dynamic array of extra options */