rofi 1.7.9
recursivebrowser.c
Go to the documentation of this file.
1
26#define G_LOG_DOMAIN "Modes.RecursiveBrowser"
27
28#include "config.h"
29#include <errno.h>
30#include <gio/gio.h>
31#include <gmodule.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36
37#include <dirent.h>
38#include <glib-unix.h>
39#include <glib/gstdio.h>
40#include <sys/stat.h>
41#include <sys/types.h>
42
43#include "helper.h"
44#include "history.h"
45#include "mode-private.h"
46#include "mode.h"
48#include "rofi.h"
49#include "theme.h"
50
51#include <stdint.h>
52
53#include "rofi-icon-fetcher.h"
54
56#define DEFAULT_OPEN "xdg-open"
57
67
69const char *rb_icon_name[NUM_FILE_TYPES] = {"go-up", "folder", "gtk-file"};
70typedef struct {
71 char *name;
72 char *path;
73 enum FBFileType type;
74 uint32_t icon_fetch_uid;
75 uint32_t icon_fetch_size;
76 gboolean link;
77 time_t time;
78} FBFile;
79
80typedef struct {
81 char *command;
82 GFile *current_dir;
83 FBFile *array;
84 unsigned int array_length;
85 unsigned int array_length_real;
86
88 GAsyncQueue *async_queue;
91 gboolean loading;
92 int pipefd2[2];
93 GRegex *filter_regex;
95
97 for (unsigned int i = 0; i < pd->array_length; i++) {
98 FBFile *fb = &(pd->array[i]);
99 g_free(fb->name);
100 g_free(fb->path);
101 }
102 g_free(pd->array);
103 pd->array = NULL;
104 pd->array_length = 0;
105 pd->array_length_real = 0;
106}
107#include <dirent.h>
108#include <sys/types.h>
109
111 if ((pd->array_length + 1) > pd->array_length_real) {
112 pd->array_length_real += 10240;
113 pd->array =
114 g_realloc(pd->array, (pd->array_length_real + 1) * sizeof(FBFile));
115 }
116}
117
121 char *msg = NULL;
122 gboolean found_error = FALSE;
123
124 ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
125 Property *p =
126 rofi_theme_find_property(wid, P_BOOLEAN, "cancel-returns-1", TRUE);
127 if (p && p->type == P_BOOLEAN && p->value.b == TRUE) {
129 }
130
131 p = rofi_theme_find_property(wid, P_STRING, "filter-regex", TRUE);
132 if (p != NULL && p->type == P_STRING) {
133 GError *error = NULL;
134 g_debug("compile regex: %s\n", p->value.s);
135 pd->filter_regex = g_regex_new(p->value.s, G_REGEX_OPTIMIZE, 0, &error);
136 if (error) {
137 msg = g_strdup_printf("\"%s\" is not a valid regex for filtering: %s",
138 p->value.s, error->message);
139 found_error = TRUE;
140 g_error_free(error);
141 }
142 }
143 if (pd->filter_regex == NULL) {
144 g_debug("compile default regex\n");
145 pd->filter_regex = g_regex_new("^(\\..*)", G_REGEX_OPTIMIZE, 0, NULL);
146 }
147 p = rofi_theme_find_property(wid, P_STRING, "command", TRUE);
148 if (p != NULL && p->type == P_STRING) {
149 pd->command = g_strdup(p->value.s);
150 } else {
151 pd->command = g_strdup(DEFAULT_OPEN);
152 }
153
154 if (found_error) {
155 rofi_view_error_dialog(msg, FALSE);
156
157 g_free(msg);
158 }
159}
160
164
165 ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
166
167 Property *p = rofi_theme_find_property(wid, P_STRING, "directory", TRUE);
168
169 gboolean config_has_valid_dir = p != NULL && p->type == P_STRING &&
170 g_file_test(p->value.s, G_FILE_TEST_IS_DIR);
171
172 if (config_has_valid_dir) {
173 pd->current_dir = g_file_new_for_path(p->value.s);
174 }
175 if (pd->current_dir == NULL) {
176 pd->current_dir = g_file_new_for_path(g_get_home_dir());
177 }
178}
179
180static void scan_dir(FileBrowserModePrivateData *pd, GFile *path) {
181 GQueue *dirs_to_scan = g_queue_new();
182 g_queue_push_tail(dirs_to_scan, g_object_ref(path));
183 GFile *dir_to_scan = NULL;
184 while ((dir_to_scan = g_queue_pop_head(dirs_to_scan)) != NULL) {
185 char *cdir = g_file_get_path(dir_to_scan);
186 DIR *dir = opendir(cdir);
187 g_object_unref(dir_to_scan);
188 if (dir) {
189 struct dirent *rd = NULL;
190 while (pd->end_thread == FALSE && (rd = readdir(dir)) != NULL) {
191 if (g_strcmp0(rd->d_name, "..") == 0) {
192 continue;
193 }
194 if (g_strcmp0(rd->d_name, ".") == 0) {
195 continue;
196 }
197 if (pd->filter_regex &&
198 g_regex_match(pd->filter_regex, rd->d_name, 0, NULL)) {
199 continue;
200 }
201 switch (rd->d_type) {
202 case DT_BLK:
203 case DT_CHR:
204 case DT_FIFO:
205 case DT_SOCK:
206 default:
207 break;
208 case DT_REG: {
209 FBFile *f = g_malloc0(sizeof(FBFile));
210 // Rofi expects utf-8, so lets convert the filename.
211 f->path = g_build_filename(cdir, rd->d_name, NULL);
212 f->name = g_filename_to_utf8(f->path, -1, NULL, NULL, NULL);
213 if (f->name == NULL) {
214 f->name = rofi_force_utf8(rd->d_name, -1);
215 }
216 if (f->name == NULL) {
217 f->name = g_strdup("n/a");
218 }
219 f->type = (rd->d_type == DT_DIR) ? DIRECTORY : RFILE;
220 f->icon_fetch_uid = 0;
221 f->icon_fetch_size = 0;
222 f->link = FALSE;
223
224 g_async_queue_push(pd->async_queue, f);
225 if (g_async_queue_length(pd->async_queue) > 10000) {
226 write(pd->pipefd2[1], "r", 1);
227 }
228 break;
229 }
230 case DT_DIR: {
231 char *d = g_build_filename(cdir, rd->d_name, NULL);
232 GFile *dirp = g_file_new_for_path(d);
233 g_queue_push_tail(dirs_to_scan, dirp);
234 g_free(d);
235 break;
236 }
237 case DT_UNKNOWN:
238 case DT_LNK: {
239 FBFile *f = g_malloc0(sizeof(FBFile));
240 // Rofi expects utf-8, so lets convert the filename.
241 f->path = g_build_filename(cdir, rd->d_name, NULL);
242 f->name = g_filename_to_utf8(f->path, -1, NULL, NULL, NULL);
243 if (f->name == NULL) {
244 f->name = rofi_force_utf8(rd->d_name, -1);
245 }
246 if (f->name == NULL) {
247 f->name = g_strdup("n/a");
248 }
249 f->icon_fetch_uid = 0;
250 f->icon_fetch_size = 0;
251 // Default to file.
252 f->type = RFILE;
253 if (rd->d_type == DT_LNK) {
254 f->link = TRUE;
255 } else {
256 f->link = FALSE;
257 }
258 {
259 // If we have link, use a stat to fine out what it is, if we fail,
260 // we mark it as file.
261 // TODO have a 'broken link' mode?
262 // Convert full path to right encoding.
263 // DD: Path should be in file encoding, not utf-8
264 // char *file =
265 // g_filename_from_utf8(pd->array[pd->array_length].path,
266 // -1, NULL, NULL, NULL);
267 // TODO: How to handle loops in links.
268 if (f->path) {
269 GStatBuf statbuf;
270 if (g_stat(f->path, &statbuf) == 0) {
271 if (S_ISDIR(statbuf.st_mode)) {
272 char *new_full_path =
273 g_build_filename(cdir, rd->d_name, NULL);
274 g_free(f->path);
275 g_free(f->name);
276 g_free(f);
277 f = NULL;
278 GFile *dirp = g_file_new_for_path(new_full_path);
279 g_queue_push_tail(dirs_to_scan, dirp);
280 g_free(new_full_path);
281 break;
282 } else if (S_ISREG(statbuf.st_mode)) {
283 f->type = RFILE;
284 }
285
286 } else {
287 g_warning("Failed to stat file: %s, %s", f->path,
288 strerror(errno));
289 }
290
291 // g_free(file);
292 }
293 }
294 if (f != NULL) {
295 g_async_queue_push(pd->async_queue, f);
296 if (g_async_queue_length(pd->async_queue) > 10000) {
297 write(pd->pipefd2[1], "r", 1);
298 }
299 }
300 break;
301 }
302 }
303 }
304 closedir(dir);
305 }
306 g_free(cdir);
307 }
308
309 g_queue_free(dirs_to_scan);
310}
311static gpointer recursive_browser_input_thread(gpointer userdata) {
313 GTimer *t = g_timer_new();
314 g_debug("Start scan.\n");
315 scan_dir(pd, pd->current_dir);
316 write(pd->pipefd2[1], "r", 1);
317 write(pd->pipefd2[1], "q", 1);
318 double f = g_timer_elapsed(t, NULL);
319 g_debug("End scan: %f\n", f);
320 g_timer_destroy(t);
321 return NULL;
322}
323static gboolean recursive_browser_async_read_proc(gint fd,
324 GIOCondition condition,
325 gpointer user_data) {
327 char command;
328 // Only interrested in read events.
329 if ((condition & G_IO_IN) != G_IO_IN) {
330 return G_SOURCE_CONTINUE;
331 }
332 // Read the entry from the pipe that was used to signal this action.
333 if (read(fd, &command, 1) == 1) {
334 if (command == 'r') {
335 FBFile *block = NULL;
336 gboolean changed = FALSE;
337 // Empty out the AsyncQueue (that is thread safe) from all blocks pushed
338 // into it.
339 while ((block = g_async_queue_try_pop(pd->async_queue)) != NULL) {
340
341 fb_resize_array(pd);
342 pd->array[pd->array_length] = *block;
343 pd->array_length++;
344 g_free(block);
345 changed = TRUE;
346 }
347 if (changed) {
349 }
350 } else if (command == 'q') {
351 if (pd->loading) {
352 // TODO: add enable.
353 //rofi_view_set_overlay(rofi_view_get_active(), NULL);
354 }
355 }
356 }
357 return G_SOURCE_CONTINUE;
358}
359
364 if (mode_get_private_data(sw) == NULL) {
365 FileBrowserModePrivateData *pd = g_malloc0(sizeof(*pd));
366 mode_set_private_data(sw, (void *)pd);
367
370
371 // Load content.
372 if (pipe(pd->pipefd2) == -1) {
373 g_error("Failed to create pipe");
374 }
375 pd->wake_source = g_unix_fd_add(pd->pipefd2[0], G_IO_IN,
377
378 // Create the message passing queue to the UI thread.
379 pd->async_queue = g_async_queue_new();
380 pd->end_thread = FALSE;
381 pd->reading_thread = g_thread_new(
382 "dmenu-read", (GThreadFunc)recursive_browser_input_thread, pd);
383 pd->loading = TRUE;
384 }
385 return TRUE;
386}
387static unsigned int recursive_browser_mode_get_num_entries(const Mode *sw) {
390 return pd->array_length;
391}
392
394 G_GNUC_UNUSED char **input,
395 unsigned int selected_line) {
396 ModeMode retv = MODE_EXIT;
399
400 if ((mretv & MENU_CANCEL) == MENU_CANCEL) {
401 ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
402 Property *p =
403 rofi_theme_find_property(wid, P_BOOLEAN, "cancel-returns-1", TRUE);
404 if (p && p->type == P_BOOLEAN && p->value.b == TRUE) {
406 }
407
408 return MODE_EXIT;
409 }
410 if (mretv & MENU_CUSTOM_COMMAND) {
411 retv = (mretv & MENU_LOWER_MASK);
412 } else if ((mretv & MENU_OK)) {
413 if (selected_line < pd->array_length) {
414 if (pd->array[selected_line].type == RFILE) {
415 char *d_esc = g_shell_quote(pd->array[selected_line].path);
416 char *cmd = g_strdup_printf("%s %s", pd->command, d_esc);
417 g_free(d_esc);
418 char *cdir = g_file_get_path(pd->current_dir);
419 helper_execute_command(cdir, cmd, FALSE, NULL);
420 g_free(cdir);
421 g_free(cmd);
422 return MODE_EXIT;
423 }
424 }
425 retv = RELOAD_DIALOG;
426 } else if ((mretv & MENU_CUSTOM_INPUT)) {
427 retv = RELOAD_DIALOG;
428 } else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) {
429 retv = RELOAD_DIALOG;
430 }
431 return retv;
432}
433
437 if (pd != NULL) {
438 if (pd->reading_thread) {
439 pd->end_thread = TRUE;
440 g_thread_join(pd->reading_thread);
441 }
442 if (pd->filter_regex) {
443 g_regex_unref(pd->filter_regex);
444 }
445 g_object_unref(pd->current_dir);
446 g_free(pd->command);
447 free_list(pd);
448 g_free(pd);
449 mode_set_private_data(sw, NULL);
450 }
451}
452
453static char *_get_display_value(const Mode *sw, unsigned int selected_line,
454 G_GNUC_UNUSED int *state,
455 G_GNUC_UNUSED GList **attr_list,
456 int get_entry) {
459
460 // Only return the string if requested, otherwise only set state.
461 if (!get_entry) {
462 return NULL;
463 }
464 if (pd->array[selected_line].type == UP) {
465 return g_strdup(" ..");
466 }
467 if (pd->array[selected_line].link) {
468 return g_strconcat("@", pd->array[selected_line].name, NULL);
469 }
470 return g_strdup(pd->array[selected_line].name);
471}
472
483 rofi_int_matcher **tokens,
484 unsigned int index) {
487
488 // Call default matching function.
489 return helper_token_match(tokens, pd->array[index].name);
490}
491
492static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line,
493 unsigned int height) {
496 g_return_val_if_fail(pd->array != NULL, NULL);
497 FBFile *dr = &(pd->array[selected_line]);
500 } else if (dr->type == RFILE) {
501 gchar *_path = g_strconcat("thumbnail://", dr->path, NULL);
502 dr->icon_fetch_uid = rofi_icon_fetcher_query(_path, height);
503 g_free(_path);
504 } else {
505 dr->icon_fetch_uid =
507 }
508 dr->icon_fetch_size = height;
510}
511
512static char *_get_message(const Mode *sw) {
515 if (pd->current_dir) {
516 char *dirname = g_file_get_parse_name(pd->current_dir);
517 char *str =
518 g_markup_printf_escaped("<b>Current directory:</b> %s", dirname);
519 g_free(dirname);
520 return str;
521 }
522 return "n/a";
523}
524
525static char *_get_completion(const Mode *sw, unsigned int index) {
528
529 char *d = g_strescape(pd->array[index].path, NULL);
530 return d;
531}
532
534 Mode *sw = g_malloc0(sizeof(Mode));
535
537
538 sw->private_data = NULL;
539 return sw;
540}
541
542#if 1
543ModeMode recursive_browser_mode_completer(Mode *sw, int mretv, char **input,
544 unsigned int selected_line,
545 char **path) {
546 ModeMode retv = MODE_EXIT;
549 if ((mretv & MENU_OK)) {
550 if (selected_line < pd->array_length) {
551 if (pd->array[selected_line].type == RFILE) {
552 *path = g_strescape(pd->array[selected_line].path, NULL);
553 return MODE_EXIT;
554 }
555 }
556 retv = RELOAD_DIALOG;
557 } else if ((mretv & MENU_CUSTOM_INPUT) && *input) {
558 retv = RELOAD_DIALOG;
559 } else if ((mretv & MENU_ENTRY_DELETE) == MENU_ENTRY_DELETE) {
560 retv = RELOAD_DIALOG;
561 }
562 return retv;
563}
564#endif
565
567 .display_name = NULL,
568 .abi_version = ABI_VERSION,
569 .name = "recursivebrowser",
570 .cfg_name_key = "display-recursivebrowser",
572 ._get_num_entries = recursive_browser_mode_get_num_entries,
575 ._token_match = recursive_browser_token_match,
576 ._get_display_value = _get_display_value,
577 ._get_icon = _get_icon,
578 ._get_message = _get_message,
579 ._get_completion = _get_completion,
580 ._preprocess_input = NULL,
582 ._completer_result = recursive_browser_mode_completer,
583 .private_data = NULL,
584 .free = NULL,
static char * _get_message(const Mode *sw)
static char * _get_completion(const Mode *sw, unsigned int index)
FBFileType
Definition filebrowser.c:65
@ NUM_FILE_TYPES
Definition filebrowser.c:69
@ DIRECTORY
Definition filebrowser.c:67
@ UP
Definition filebrowser.c:66
@ RFILE
Definition filebrowser.c:68
static cairo_surface_t * _get_icon(const Mode *sw, unsigned int selected_line, unsigned int height)
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)
#define DEFAULT_OPEN
Definition filebrowser.c:54
ModeMode recursive_browser_mode_completer(Mode *sw, int mretv, char **input, unsigned int selected_line, char **path)
Mode recursive_browser_mode
Mode * create_new_recursive_browser(void)
Property * rofi_theme_find_property(ThemeWidget *wid, PropertyType type, const char *property, gboolean exact)
Definition theme.c:743
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition helper.c:1071
ThemeWidget * rofi_config_find_widget(const char *name, const char *state, gboolean exact)
Definition theme.c:780
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition helper.c:542
char * rofi_force_utf8(const gchar *data, ssize_t length)
Definition helper.c:856
gboolean rofi_icon_fetcher_file_is_image(const char *const path)
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
struct rofi_mode Mode
Definition mode.h:49
void * mode_get_private_data(const Mode *mode)
Definition mode.c:176
void mode_set_private_data(Mode *mode, void *pd)
Definition mode.c:181
ModeMode
Definition mode.h:54
@ MENU_CUSTOM_COMMAND
Definition mode.h:84
@ MENU_LOWER_MASK
Definition mode.h:92
@ MENU_CANCEL
Definition mode.h:74
@ MENU_ENTRY_DELETE
Definition mode.h:80
@ MENU_OK
Definition mode.h:72
@ MENU_CUSTOM_INPUT
Definition mode.h:78
@ MODE_EXIT
Definition mode.h:56
@ RELOAD_DIALOG
Definition mode.h:60
void rofi_set_return_code(int code)
Definition rofi.c:146
void rofi_view_reload(void)
Definition view.c:602
int rofi_view_error_dialog(const char *msg, int markup)
Definition view.c:2675
@ MODE_TYPE_COMPLETER
@ MODE_TYPE_SWITCHER
#define ABI_VERSION
Definition mode.h:36
static char * _get_message(const Mode *sw)
static void free_list(FileBrowserModePrivateData *pd)
static gboolean recursive_browser_async_read_proc(gint fd, GIOCondition condition, gpointer user_data)
const char * rb_icon_name[NUM_FILE_TYPES]
static char * _get_completion(const Mode *sw, unsigned int index)
static void scan_dir(FileBrowserModePrivateData *pd, GFile *path)
static int recursive_browser_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
static cairo_surface_t * _get_icon(const Mode *sw, unsigned int selected_line, unsigned int height)
static void recursive_browser_mode_init_current_dir(Mode *sw)
static void fb_resize_array(FileBrowserModePrivateData *pd)
static void recursive_browser_mode_init_config(Mode *sw)
static unsigned int recursive_browser_mode_get_num_entries(const Mode *sw)
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)
static void recursive_browser_mode_destroy(Mode *sw)
static gpointer recursive_browser_input_thread(gpointer userdata)
static ModeMode recursive_browser_mode_result(Mode *sw, int mretv, G_GNUC_UNUSED char **input, unsigned int selected_line)
static int recursive_browser_mode_init(Mode *sw)
@ P_BOOLEAN
Definition rofi-types.h:18
@ P_STRING
Definition rofi-types.h:16
struct rofi_int_matcher_t rofi_int_matcher
char * path
Definition filebrowser.c:93
enum FBFileType type
Definition filebrowser.c:94
gboolean link
Definition filebrowser.c:97
uint32_t icon_fetch_size
Definition filebrowser.c:96
char * name
Definition filebrowser.c:92
uint32_t icon_fetch_uid
Definition filebrowser.c:95
PropertyValue value
Definition rofi-types.h:293
PropertyType type
Definition rofi-types.h:291
char * name
void * private_data
GTimer * time
Definition view.c:306