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