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