From d27cee89fae9ad09949f70dc940aed45f444e3cf Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Mon, 12 Jun 2023 19:07:00 +0200 Subject: Merging in the Recursive file browser. Squashed commit of the following: commit 92e730076d461622dc81e44e87ec456317514904 Author: Dave Davenport Date: Sun Jun 11 18:17:12 2023 +0200 [Doc] Add regex filtering to recursivebrowser. commit ee80c8487f9765b1e6e8ab8219a6baea089cf5af Author: Dave Davenport Date: Sun Jun 11 17:49:29 2023 +0200 [recursivebrowser] Update manpage. commit a24b68f52362aaf1461935c2340e3bf5e31da59d Author: Dave Davenport 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 Date: Sun Jun 4 15:12:31 2023 +0200 [Recursive browser] Make completer selectable. commit 722f07a803c28a406d8a610f31a24b3f7247b9ba Author: Dave Davenport Date: Sun Jun 4 14:36:14 2023 +0200 Add methods for completer to modes. commit 7972420c30275514751802d1ed517a45bbd83da1 Author: Qball Cow Date: Thu Jun 1 21:56:06 2023 +0200 Prepare updates for new APIs. commit dd3035a1a61f8196d394f6867701a0e1b3af30ac Author: Dave Davenport Date: Wed May 10 19:24:48 2023 +0200 [RB] Fix regex and cleanups commit 4d2941caf32dfb946aee54c467c1319c7a89804a Author: Dave Davenport Date: Wed May 10 18:09:54 2023 +0200 [RB] Add (unfinished regex test) commit 848277001fc8cf9afc538067f2afa24a174f8c7f Author: Dave Davenport Date: Wed May 10 17:49:16 2023 +0200 [RB] Pull the scanning into a separate thread. commit f369a7f63f618bbcad10c18e73f7e2b117c515f1 Author: Dave Davenport Date: Wed May 3 18:35:15 2023 +0200 [Recursive File Browser] First test version. --- Examples/test_script_mode_delim.sh | 1 + config/config.c | 2 + doc/rofi.1 | 24 ++ doc/rofi.1.markdown | 19 ++ include/mode-private.h | 47 +++- include/mode.h | 30 +++ include/modes/modes.h | 1 + include/modes/recursivebrowser.h | 58 ++++ include/rofi.h | 2 + include/settings.h | 2 + meson.build | 1 + source/mode.c | 40 +++ source/modes/combi.c | 3 +- source/modes/dmenu.c | 4 +- source/modes/drun.c | 16 +- source/modes/filebrowser.c | 3 + source/modes/help-keys.c | 3 +- source/modes/recursivebrowser.c | 539 +++++++++++++++++++++++++++++++++++++ source/modes/run.c | 16 +- source/modes/ssh.c | 3 +- source/modes/window.c | 6 +- source/rofi.c | 27 ++ source/xrmoptions.c | 6 + 23 files changed, 833 insertions(+), 20 deletions(-) create mode 100644 include/modes/recursivebrowser.h create mode 100644 source/modes/recursivebrowser.c diff --git a/Examples/test_script_mode_delim.sh b/Examples/test_script_mode_delim.sh index ff3bfff8..5f6db6c5 100755 --- a/Examples/test_script_mode_delim.sh +++ b/Examples/test_script_mode_delim.sh @@ -11,6 +11,7 @@ if [[ $ROFI_RETV = 0 ]] then echo -en "\x00delim\x1f\\x1\n" fi +echo -en "\x00message\x1fmy line1\nmyline2\nmy line3\x1" echo -en "\x00prompt\x1fChange prompt\x1" for a in {1..10} do diff --git a/config/config.c b/config/config.c index 05dd91be..7bde9703 100644 --- a/config/config.c +++ b/config/config.c @@ -158,4 +158,6 @@ Settings config = { .refilter_timeout_limit = 300, /** workaround for broken xserver (#300 on xserver, #611) */ .xserver_i300_workaround = FALSE, + /** What browser to use for completion */ + .completer_mode = "recursivebrowser", }; diff --git a/doc/rofi.1 b/doc/rofi.1 index 832f5ff9..51414db2 100644 --- a/doc/rofi.1 +++ b/doc/rofi.1 @@ -1191,6 +1191,30 @@ rofi -filebrowser-cancel-returns-1 true -show filebrowser .PP The \fB\fCshow-hidden\fR can also be triggered with the \fB\fCkb-delete-entry\fR keybinding. +.SS 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 .PP The number of previous inputs for the entry box can be modified by setting diff --git a/doc/rofi.1.markdown b/doc/rofi.1.markdown index 6801c102..c8befe97 100644 --- a/doc/rofi.1.markdown +++ b/doc/rofi.1.markdown @@ -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. +### 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 The number of previous inputs for the entry box can be modified by setting diff --git a/include/mode-private.h b/include/mode-private.h index 796bcc6f..6ccdee37 100644 --- a/include/mode-private.h +++ b/include/mode-private.h @@ -32,7 +32,16 @@ G_BEGIN_DECLS /** 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. @@ -151,6 +160,28 @@ typedef char *(*_mode_preprocess_input)(Mode *sw, const char *input); */ 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. * It consists of a name, callback and if enabled @@ -197,6 +228,17 @@ struct rofi_mode { * And has data in `ed` */ _mode_free free; + + /** + * Create mode. + */ + _mode_create _create; + + /** + * If this mode is used as completer. + */ + _mode_completer_result _completer_result; + /** Extra fields for script */ void *ed; @@ -206,6 +248,9 @@ struct rofi_mode { /** Fallack icon.*/ uint32_t fallback_icon_fetch_uid; uint32_t fallback_icon_not_found; + + /** type */ + ModeType type; }; G_END_DECLS #endif // ROFI_MODE_PRIVATE_H diff --git a/include/mode.h b/include/mode.h index 3af46281..0a7b839c 100644 --- a/include/mode.h +++ b/include/mode.h @@ -247,6 +247,36 @@ char *mode_preprocess_input(Mode *mode, const char *input); * free). */ 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 #endif diff --git a/include/modes/modes.h b/include/modes/modes.h index 72b8b6ee..f5c2e9a7 100644 --- a/include/modes/modes.h +++ b/include/modes/modes.h @@ -39,6 +39,7 @@ #include "modes/dmenu.h" #include "modes/drun.h" #include "modes/filebrowser.h" +#include "modes/recursivebrowser.h" #include "modes/help-keys.h" #include "modes/run.h" #include "modes/script.h" diff --git a/include/modes/recursivebrowser.h b/include/modes/recursivebrowser.h new file mode 100644 index 00000000..7c5c9a22 --- /dev/null +++ b/include/modes/recursivebrowser.h @@ -0,0 +1,58 @@ +/* + * rofi + * + * MIT/X11 License + * Copyright © 2013-2023 Qball Cow + * + * 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 diff --git a/include/rofi.h b/include/rofi.h index 7ae59fee..38f4c44a 100644 --- a/include/rofi.h +++ b/include/rofi.h @@ -103,6 +103,8 @@ void rofi_quit_main_loop(void); * @return returns Mode * when found, NULL if not. */ Mode *rofi_collect_modes_search(const char *name); + +const Mode *rofi_get_completer(void); /** Reset terminal */ #define color_reset "\033[0m" /** Set terminal text bold */ diff --git a/include/settings.h b/include/settings.h index 7a15112c..1af9b952 100644 --- a/include/settings.h +++ b/include/settings.h @@ -184,6 +184,8 @@ typedef struct { /** workaround for broken xserver (#300 on xserver, #611) */ gboolean xserver_i300_workaround; + /** completer mode */ + char *completer_mode; } Settings; /** Default number of lines in the list view */ diff --git a/meson.build b/meson.build index 9415b999..2d889503 100644 --- a/meson.build +++ b/meson.build @@ -209,6 +209,7 @@ rofi_sources = files( 'source/modes/script.c', 'source/modes/help-keys.c', 'source/modes/filebrowser.c', + 'source/modes/recursivebrowser.c', 'include/display.h', 'include/xcb.h', 'include/xcb-internal.h', diff --git a/source/mode.c b/source/mode.c index e2e5d85d..7cb070de 100644 --- a/source/mode.c +++ b/source/mode.c @@ -44,6 +44,17 @@ int mode_init(Mode *mode) { g_return_val_if_fail(mode != 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. mode->fallback_icon_fetch_uid = 0; mode->fallback_icon_not_found = FALSE; @@ -205,4 +216,33 @@ char *mode_get_message(const Mode *mode) { } 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; +} + /**@}*/ diff --git a/source/modes/combi.c b/source/modes/combi.c index 3542e72a..25b6ce85 100644 --- a/source/modes/combi.c +++ b/source/modes/combi.c @@ -342,4 +342,5 @@ Mode combi_mode = {.name = "combi", ._get_icon = combi_get_icon, ._preprocess_input = combi_preprocess_input, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER }; diff --git a/source/modes/dmenu.c b/source/modes/dmenu.c index b005cfb0..31b7c2dc 100644 --- a/source/modes/dmenu.c +++ b/source/modes/dmenu.c @@ -257,8 +257,6 @@ static gpointer read_input_thread(gpointer userdata) { ssize_t nread = 0; ssize_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; GTimer *tim = g_timer_new(); @@ -616,6 +614,8 @@ static int dmenu_mode_init(Mode *sw) { } pd->wake_source = 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 = g_thread_new("dmenu-read", (GThreadFunc)read_input_thread, pd); pd->loading = TRUE; diff --git a/source/modes/drun.c b/source/modes/drun.c index 635fed7b..69151f84 100644 --- a/source/modes/drun.c +++ b/source/modes/drun.c @@ -1182,8 +1182,8 @@ static ModeMode drun_mode_result(Mode *sw, int mretv, char **input, retv = MODE_EXIT; } else { char *path = NULL; - retv = file_browser_mode_completer(rmpd->completer, mretv, input, - selected_line, &path); + retv = mode_completer_result(rmpd->completer, mretv, input, selected_line, + &path); if (retv == MODE_EXIT) { 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); *input = g_strdup(rmpd->old_completer_input); - rmpd->completer = create_new_file_browser(); - mode_init(rmpd->completer); - rmpd->file_complete = TRUE; + const Mode *comp = rofi_get_completer(); + if (comp) { + rmpd->completer = mode_create(comp); + mode_init(rmpd->completer); + rmpd->file_complete = TRUE; + } } g_regex_unref(regex); } @@ -1483,6 +1486,7 @@ Mode drun_mode = {.name = "drun", ._get_icon = _get_icon, ._preprocess_input = NULL, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER}; #endif // ENABLE_DRUN diff --git a/source/modes/filebrowser.c b/source/modes/filebrowser.c index f9930ee7..b32d83f0 100644 --- a/source/modes/filebrowser.c +++ b/source/modes/filebrowser.c @@ -727,6 +727,9 @@ Mode file_browser_mode = { ._get_message = _get_message, ._get_completion = _get_completion, ._preprocess_input = NULL, + ._create = create_new_file_browser, + ._completer_result = file_browser_mode_completer, .private_data = NULL, .free = NULL, + .type = MODE_TYPE_SWITCHER|MODE_TYPE_COMPLETER }; diff --git a/source/modes/help-keys.c b/source/modes/help-keys.c index 728219ea..0b8a565a 100644 --- a/source/modes/help-keys.c +++ b/source/modes/help-keys.c @@ -118,4 +118,5 @@ Mode help_keys_mode = {.name = "keys", ._get_completion = NULL, ._get_display_value = _get_display_value, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER }; diff --git a/source/modes/recursivebrowser.c b/source/modes/recursivebrowser.c new file mode 100644 index 00000000..6c7a5b4f --- /dev/null +++ b/source/modes/recursivebrowser.c @@ -0,0 +1,539 @@ +/** + * rofi-recursive_browser + * + * MIT/X11 License + * Copyright (c) 2017 Qball Cow + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "helper.h" +#include "history.h" +#include "mode-private.h" +#include "mode.h" +#include "modes/recursivebrowser.h" +#include "rofi.h" +#include "theme.h" + +#include + +#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 +#include + +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("Current directory: %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}; diff --git a/source/modes/run.c b/source/modes/run.c index 0fc018a1..4bc5ec03 100644 --- a/source/modes/run.c +++ b/source/modes/run.c @@ -440,8 +440,8 @@ static ModeMode run_mode_result(Mode *sw, int mretv, char **input, retv = MODE_EXIT; } else { char *path = NULL; - retv = file_browser_mode_completer(rmpd->completer, mretv, input, - selected_line, &path); + retv = mode_completer_result(rmpd->completer, mretv, input, selected_line, + &path); if (retv == MODE_EXIT) { if (path == NULL) { 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); *input = g_strdup(rmpd->old_completer_input); - rmpd->completer = create_new_file_browser(); - mode_init(rmpd->completer); - rmpd->file_complete = TRUE; + const Mode *comp = rofi_get_completer(); + if (comp) { + rmpd->completer = mode_create(comp); + mode_init(rmpd->completer); + rmpd->file_complete = TRUE; + } } } return retv; @@ -572,5 +575,6 @@ Mode run_mode = {.name = "run", ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER}; /** @}*/ diff --git a/source/modes/ssh.c b/source/modes/ssh.c index aca8f7ad..05484cc4 100644 --- a/source/modes/ssh.c +++ b/source/modes/ssh.c @@ -645,5 +645,6 @@ Mode ssh_mode = {.name = "ssh", ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER }; /**@}*/ diff --git a/source/modes/window.c b/source/modes/window.c index 5ac4a8f8..91ad3fc7 100644 --- a/source/modes/window.c +++ b/source/modes/window.c @@ -1134,7 +1134,8 @@ Mode window_mode = {.name = "window", ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER }; Mode window_mode_cd = {.name = "windowcd", .cfg_name_key = "display-windowcd", ._init = window_mode_init_cd, @@ -1147,6 +1148,7 @@ Mode window_mode_cd = {.name = "windowcd", ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, - .free = NULL}; + .free = NULL, + .type = MODE_TYPE_SWITCHER }; #endif // WINDOW_MODE diff --git a/source/rofi.c b/source/rofi.c index aa9c686b..214a78f6 100644 --- a/source/rofi.c +++ b/source/rofi.c @@ -165,6 +165,21 @@ static int mode_lookup(const char *name) { } 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. @@ -608,6 +623,7 @@ static void rofi_collect_modes(void) { rofi_collectmodes_add(&combi_mode); rofi_collectmodes_add(&help_keys_mode); rofi_collectmodes_add(&file_browser_mode); + rofi_collectmodes_add(&recursive_browser_mode); if (find_arg("-no-plugins") < 0) { find_arg_str("-plugin-path", &(config.plugin_path)); @@ -1195,3 +1211,14 @@ int rofi_theme_rasi_validate(const char *filename) { 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; +} diff --git a/source/xrmoptions.c b/source/xrmoptions.c index 34d2fff5..833031a3 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -439,6 +439,12 @@ static XrmOption xrmOptions[] = { NULL, "Workaround for XServer issue #300 (issue #611 for rofi.)", 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 */ -- cgit v1.2.3