Cleanup: style, use braces for editors
[blender.git] / source / blender / editors / space_file / fsmenu.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup spfile
22  */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <math.h>
28
29 #include "MEM_guardedalloc.h"
30
31 #include "BLI_utildefines.h"
32 #include "BLI_blenlib.h"
33
34 #include "BKE_appdir.h"
35
36 #include "ED_fileselect.h"
37
38 #ifdef WIN32
39 /* Need to include windows.h so _WIN32_IE is defined. */
40 #  include <windows.h>
41 /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
42  * because 'near' is disabled through BLI_windstuff. */
43 #  include <shlobj.h>
44 #  include "BLI_winstuff.h"
45 #endif
46
47 #ifdef __APPLE__
48 #  include <Carbon/Carbon.h>
49 #endif /* __APPLE__ */
50
51 #ifdef __linux__
52 #  include <mntent.h>
53 #  include "BLI_fileops_types.h"
54 #endif
55
56 #include "fsmenu.h" /* include ourselves */
57
58 /* FSMENU HANDLING */
59
60 typedef struct FSMenu {
61   FSMenuEntry *fsmenu_system;
62   FSMenuEntry *fsmenu_system_bookmarks;
63   FSMenuEntry *fsmenu_bookmarks;
64   FSMenuEntry *fsmenu_recent;
65 } FSMenu;
66
67 static FSMenu *g_fsmenu = NULL;
68
69 FSMenu *ED_fsmenu_get(void)
70 {
71   if (!g_fsmenu) {
72     g_fsmenu = MEM_callocN(sizeof(struct FSMenu), "fsmenu");
73   }
74   return g_fsmenu;
75 }
76
77 struct FSMenuEntry *ED_fsmenu_get_category(struct FSMenu *fsmenu, FSMenuCategory category)
78 {
79   FSMenuEntry *fsm_head = NULL;
80
81   switch (category) {
82     case FS_CATEGORY_SYSTEM:
83       fsm_head = fsmenu->fsmenu_system;
84       break;
85     case FS_CATEGORY_SYSTEM_BOOKMARKS:
86       fsm_head = fsmenu->fsmenu_system_bookmarks;
87       break;
88     case FS_CATEGORY_BOOKMARKS:
89       fsm_head = fsmenu->fsmenu_bookmarks;
90       break;
91     case FS_CATEGORY_RECENT:
92       fsm_head = fsmenu->fsmenu_recent;
93       break;
94   }
95   return fsm_head;
96 }
97
98 void ED_fsmenu_set_category(struct FSMenu *fsmenu, FSMenuCategory category, FSMenuEntry *fsm_head)
99 {
100   switch (category) {
101     case FS_CATEGORY_SYSTEM:
102       fsmenu->fsmenu_system = fsm_head;
103       break;
104     case FS_CATEGORY_SYSTEM_BOOKMARKS:
105       fsmenu->fsmenu_system_bookmarks = fsm_head;
106       break;
107     case FS_CATEGORY_BOOKMARKS:
108       fsmenu->fsmenu_bookmarks = fsm_head;
109       break;
110     case FS_CATEGORY_RECENT:
111       fsmenu->fsmenu_recent = fsm_head;
112       break;
113   }
114 }
115
116 int ED_fsmenu_get_nentries(struct FSMenu *fsmenu, FSMenuCategory category)
117 {
118   FSMenuEntry *fsm_iter;
119   int count = 0;
120
121   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter; fsm_iter = fsm_iter->next) {
122     count++;
123   }
124
125   return count;
126 }
127
128 FSMenuEntry *ED_fsmenu_get_entry(struct FSMenu *fsmenu, FSMenuCategory category, int index)
129 {
130   FSMenuEntry *fsm_iter;
131
132   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && index;
133        fsm_iter = fsm_iter->next) {
134     index--;
135   }
136
137   return fsm_iter;
138 }
139
140 char *ED_fsmenu_entry_get_path(struct FSMenuEntry *fsentry)
141 {
142   return fsentry->path;
143 }
144
145 void ED_fsmenu_entry_set_path(struct FSMenuEntry *fsentry, const char *path)
146 {
147   if ((!fsentry->path || !path || !STREQ(path, fsentry->path)) && (fsentry->path != path)) {
148     char tmp_name[FILE_MAXFILE];
149
150     MEM_SAFE_FREE(fsentry->path);
151
152     fsentry->path = (path && path[0]) ? BLI_strdup(path) : NULL;
153
154     BLI_make_file_string("/",
155                          tmp_name,
156                          BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
157                          BLENDER_BOOKMARK_FILE);
158     fsmenu_write_file(ED_fsmenu_get(), tmp_name);
159   }
160 }
161
162 static void fsmenu_entry_generate_name(struct FSMenuEntry *fsentry, char *name, size_t name_size)
163 {
164   int offset = 0;
165   int len = name_size;
166
167   if (BLI_path_name_at_index(fsentry->path, -1, &offset, &len)) {
168     /* use as size */
169     len += 1;
170   }
171
172   BLI_strncpy(name, &fsentry->path[offset], MIN2(len, name_size));
173   if (!name[0]) {
174     name[0] = '/';
175     name[1] = '\0';
176   }
177 }
178
179 char *ED_fsmenu_entry_get_name(struct FSMenuEntry *fsentry)
180 {
181   if (fsentry->name[0]) {
182     return fsentry->name;
183   }
184   else {
185     /* Here we abuse fsm_iter->name, keeping first char NULL. */
186     char *name = fsentry->name + 1;
187     size_t name_size = sizeof(fsentry->name) - 1;
188
189     fsmenu_entry_generate_name(fsentry, name, name_size);
190     return name;
191   }
192 }
193
194 void ED_fsmenu_entry_set_name(struct FSMenuEntry *fsentry, const char *name)
195 {
196   if (!STREQ(name, fsentry->name)) {
197     char tmp_name[FILE_MAXFILE];
198     size_t tmp_name_size = sizeof(tmp_name);
199
200     fsmenu_entry_generate_name(fsentry, tmp_name, tmp_name_size);
201     if (!name[0] || STREQ(tmp_name, name)) {
202       /* reset name to default behavior. */
203       fsentry->name[0] = '\0';
204     }
205     else {
206       BLI_strncpy(fsentry->name, name, sizeof(fsentry->name));
207     }
208
209     BLI_make_file_string("/",
210                          tmp_name,
211                          BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
212                          BLENDER_BOOKMARK_FILE);
213     fsmenu_write_file(ED_fsmenu_get(), tmp_name);
214   }
215 }
216
217 void fsmenu_entry_refresh_valid(struct FSMenuEntry *fsentry)
218 {
219   if (fsentry->path && fsentry->path[0]) {
220 #ifdef WIN32
221     /* XXX Special case, always consider those as valid.
222      * Thanks to Windows, which can spend five seconds to perform a mere stat() call on those paths
223      * See T43684. */
224     const char *exceptions[] = {"A:\\", "B:\\", NULL};
225     const size_t exceptions_len[] = {strlen(exceptions[0]), strlen(exceptions[1]), 0};
226     int i;
227
228     for (i = 0; exceptions[i]; i++) {
229       if (STRCASEEQLEN(fsentry->path, exceptions[i], exceptions_len[i])) {
230         fsentry->valid = true;
231         return;
232       }
233     }
234 #endif
235     fsentry->valid = BLI_is_dir(fsentry->path);
236   }
237   else {
238     fsentry->valid = false;
239   }
240 }
241
242 short fsmenu_can_save(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
243 {
244   FSMenuEntry *fsm_iter;
245
246   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx;
247        fsm_iter = fsm_iter->next) {
248     idx--;
249   }
250
251   return fsm_iter ? fsm_iter->save : 0;
252 }
253
254 void fsmenu_insert_entry(struct FSMenu *fsmenu,
255                          FSMenuCategory category,
256                          const char *path,
257                          const char *name,
258                          FSMenuInsert flag)
259 {
260   FSMenuEntry *fsm_prev;
261   FSMenuEntry *fsm_iter;
262   FSMenuEntry *fsm_head;
263
264   fsm_head = ED_fsmenu_get_category(fsmenu, category);
265   fsm_prev = fsm_head; /* this is odd and not really correct? */
266
267   for (fsm_iter = fsm_head; fsm_iter; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
268     if (fsm_iter->path) {
269       const int cmp_ret = BLI_path_cmp(path, fsm_iter->path);
270       if (cmp_ret == 0) {
271         if (flag & FS_INSERT_FIRST) {
272           if (fsm_iter != fsm_head) {
273             fsm_prev->next = fsm_iter->next;
274             fsm_iter->next = fsm_head;
275             ED_fsmenu_set_category(fsmenu, category, fsm_iter);
276           }
277         }
278         return;
279       }
280       else if ((flag & FS_INSERT_SORTED) && cmp_ret < 0) {
281         break;
282       }
283     }
284     else {
285       /* if we're bookmarking this, file should come
286        * before the last separator, only automatically added
287        * current dir go after the last sep. */
288       if (flag & FS_INSERT_SAVE) {
289         break;
290       }
291     }
292   }
293
294   fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme");
295   fsm_iter->path = BLI_strdup(path);
296   fsm_iter->save = (flag & FS_INSERT_SAVE) != 0;
297
298   if ((category == FS_CATEGORY_RECENT) && (!name || !name[0])) {
299     /* Special handling when adding new recent entry - check if dir exists in some other categories,
300      * and try to use name from there if so. */
301     FSMenuCategory cats[] = {
302         FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS};
303     int i = ARRAY_SIZE(cats);
304
305     while (i--) {
306       FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, cats[i]);
307
308       for (; tfsm; tfsm = tfsm->next) {
309         if (STREQ(tfsm->path, fsm_iter->path)) {
310           if (tfsm->name[0]) {
311             name = tfsm->name;
312           }
313           break;
314         }
315       }
316       if (tfsm) {
317         break;
318       }
319     }
320   }
321
322   if (name && name[0]) {
323     BLI_strncpy(fsm_iter->name, name, sizeof(fsm_iter->name));
324   }
325   else {
326     fsm_iter->name[0] = '\0';
327   }
328   fsmenu_entry_refresh_valid(fsm_iter);
329
330   if (fsm_prev) {
331     if (flag & FS_INSERT_FIRST) {
332       fsm_iter->next = fsm_head;
333       ED_fsmenu_set_category(fsmenu, category, fsm_iter);
334     }
335     else {
336       fsm_iter->next = fsm_prev->next;
337       fsm_prev->next = fsm_iter;
338     }
339   }
340   else {
341     fsm_iter->next = fsm_head;
342     ED_fsmenu_set_category(fsmenu, category, fsm_iter);
343   }
344 }
345
346 void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
347 {
348   FSMenuEntry *fsm_prev = NULL;
349   FSMenuEntry *fsm_iter;
350   FSMenuEntry *fsm_head;
351
352   fsm_head = ED_fsmenu_get_category(fsmenu, category);
353
354   for (fsm_iter = fsm_head; fsm_iter && idx; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
355     idx--;
356   }
357
358   if (fsm_iter) {
359     /* you should only be able to remove entries that were
360      * not added by default, like windows drives.
361      * also separators (where path == NULL) shouldn't be removed */
362     if (fsm_iter->save && fsm_iter->path) {
363
364       /* remove fsme from list */
365       if (fsm_prev) {
366         fsm_prev->next = fsm_iter->next;
367       }
368       else {
369         fsm_head = fsm_iter->next;
370         ED_fsmenu_set_category(fsmenu, category, fsm_head);
371       }
372       /* free entry */
373       MEM_freeN(fsm_iter->path);
374       MEM_freeN(fsm_iter);
375     }
376   }
377 }
378
379 void fsmenu_write_file(struct FSMenu *fsmenu, const char *filename)
380 {
381   FSMenuEntry *fsm_iter = NULL;
382   char fsm_name[FILE_MAX];
383   int nwritten = 0;
384
385   FILE *fp = BLI_fopen(filename, "w");
386   if (!fp) {
387     return;
388   }
389
390   fprintf(fp, "[Bookmarks]\n");
391   for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter;
392        fsm_iter = fsm_iter->next) {
393     if (fsm_iter->path && fsm_iter->save) {
394       fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
395       if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
396         fprintf(fp, "!%s\n", fsm_iter->name);
397       }
398       fprintf(fp, "%s\n", fsm_iter->path);
399     }
400   }
401   fprintf(fp, "[Recent]\n");
402   for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT);
403        fsm_iter && (nwritten < FSMENU_RECENT_MAX);
404        fsm_iter = fsm_iter->next, ++nwritten) {
405     if (fsm_iter->path && fsm_iter->save) {
406       fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
407       if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
408         fprintf(fp, "!%s\n", fsm_iter->name);
409       }
410       fprintf(fp, "%s\n", fsm_iter->path);
411     }
412   }
413   fclose(fp);
414 }
415
416 void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filename)
417 {
418   char line[FILE_MAXDIR];
419   char name[FILE_MAXFILE];
420   FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
421   FILE *fp;
422
423   fp = BLI_fopen(filename, "r");
424   if (!fp) {
425     return;
426   }
427
428   name[0] = '\0';
429
430   while (fgets(line, sizeof(line), fp) != NULL) { /* read a line */
431     if (STREQLEN(line, "[Bookmarks]", 11)) {
432       category = FS_CATEGORY_BOOKMARKS;
433     }
434     else if (STREQLEN(line, "[Recent]", 8)) {
435       category = FS_CATEGORY_RECENT;
436     }
437     else if (line[0] == '!') {
438       int len = strlen(line);
439       if (len > 0) {
440         if (line[len - 1] == '\n') {
441           line[len - 1] = '\0';
442         }
443         BLI_strncpy(name, line + 1, sizeof(name));
444       }
445     }
446     else {
447       int len = strlen(line);
448       if (len > 0) {
449         if (line[len - 1] == '\n') {
450           line[len - 1] = '\0';
451         }
452         /* don't do this because it can be slow on network drives,
453          * having a bookmark from a drive that's ejected or so isn't
454          * all _that_ bad */
455 #if 0
456         if (BLI_exists(line))
457 #endif
458         {
459           fsmenu_insert_entry(fsmenu, category, line, name, FS_INSERT_SAVE);
460         }
461       }
462       /* always reset name. */
463       name[0] = '\0';
464     }
465   }
466   fclose(fp);
467 }
468
469 void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
470 {
471   char line[FILE_MAXDIR];
472 #ifdef WIN32
473   /* Add the drive names to the listing */
474   {
475     wchar_t wline[FILE_MAXDIR];
476     __int64 tmp;
477     char tmps[4], *name;
478     int i;
479
480     tmp = GetLogicalDrives();
481
482     for (i = 0; i < 26; i++) {
483       if ((tmp >> i) & 1) {
484         tmps[0] = 'A' + i;
485         tmps[1] = ':';
486         tmps[2] = '\\';
487         tmps[3] = '\0';
488         name = NULL;
489
490         /* Flee from horrible win querying hover floppy drives! */
491         if (i > 1) {
492           /* Try to get volume label as well... */
493           BLI_strncpy_wchar_from_utf8(wline, tmps, 4);
494           if (GetVolumeInformationW(
495                   wline, wline + 4, FILE_MAXDIR - 4, NULL, NULL, NULL, NULL, 0)) {
496             size_t label_len;
497
498             BLI_strncpy_wchar_as_utf8(line, wline + 4, FILE_MAXDIR - 4);
499
500             label_len = MIN2(strlen(line), FILE_MAXDIR - 6);
501             BLI_snprintf(line + label_len, 6, " (%.2s)", tmps);
502
503             name = line;
504           }
505         }
506
507         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, name, FS_INSERT_SORTED);
508       }
509     }
510
511     /* Adding Desktop and My Documents */
512     if (read_bookmarks) {
513       SHGetSpecialFolderPathW(0, wline, CSIDL_PERSONAL, 0);
514       BLI_strncpy_wchar_as_utf8(line, wline, FILE_MAXDIR);
515       fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
516       SHGetSpecialFolderPathW(0, wline, CSIDL_DESKTOPDIRECTORY, 0);
517       BLI_strncpy_wchar_as_utf8(line, wline, FILE_MAXDIR);
518       fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
519     }
520   }
521 #else
522 #  ifdef __APPLE__
523   {
524     /* Get mounted volumes better method OSX 10.6 and higher, see:
525      * https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html */
526
527     /* We get all volumes sorted including network and do not relay
528      * on user-defined finder visibility, less confusing. */
529
530     CFURLRef cfURL = NULL;
531     CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
532     CFURLEnumeratorRef volEnum = CFURLEnumeratorCreateForMountedVolumes(
533         NULL, kCFURLEnumeratorSkipInvisibles, NULL);
534
535     while (result != kCFURLEnumeratorEnd) {
536       char defPath[FILE_MAX];
537
538       result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, NULL);
539       if (result != kCFURLEnumeratorSuccess) {
540         continue;
541       }
542
543       CFURLGetFileSystemRepresentation(cfURL, false, (UInt8 *)defPath, FILE_MAX);
544
545       /* Add end slash for consistency with other platforms */
546       BLI_add_slash(defPath);
547
548       fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, defPath, NULL, FS_INSERT_SORTED);
549     }
550
551     CFRelease(volEnum);
552
553     /* Finally get user favorite places */
554     if (read_bookmarks) {
555       UInt32 seed;
556       LSSharedFileListRef list = LSSharedFileListCreate(
557           NULL, kLSSharedFileListFavoriteItems, NULL);
558       CFArrayRef pathesArray = LSSharedFileListCopySnapshot(list, &seed);
559       CFIndex pathesCount = CFArrayGetCount(pathesArray);
560
561       for (CFIndex i = 0; i < pathesCount; i++) {
562         LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(
563             pathesArray, i);
564
565         CFURLRef cfURL = NULL;
566         OSErr err = LSSharedFileListItemResolve(itemRef,
567                                                 kLSSharedFileListNoUserInteraction |
568                                                     kLSSharedFileListDoNotMountVolumes,
569                                                 &cfURL,
570                                                 NULL);
571         if (err != noErr || !cfURL) {
572           continue;
573         }
574
575         CFStringRef pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
576
577         if (pathString == NULL ||
578             !CFStringGetCString(pathString, line, sizeof(line), kCFStringEncodingUTF8)) {
579           continue;
580         }
581
582         /* Add end slash for consistency with other platforms */
583         BLI_add_slash(line);
584
585         /* Exclude "all my files" as it makes no sense in blender fileselector */
586         /* Exclude "airdrop" if wlan not active as it would show "" ) */
587         if (!strstr(line, "myDocuments.cannedSearch") && (*line != '\0')) {
588           fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_LAST);
589         }
590
591         CFRelease(pathString);
592         CFRelease(cfURL);
593       }
594
595       CFRelease(pathesArray);
596       CFRelease(list);
597     }
598   }
599 #  else
600   /* unix */
601   {
602     const char *home = BLI_getenv("HOME");
603
604     if (read_bookmarks && home) {
605       BLI_snprintf(line, sizeof(line), "%s/", home);
606       fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
607       BLI_snprintf(line, sizeof(line), "%s/Desktop/", home);
608       if (BLI_exists(line)) {
609         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
610       }
611     }
612
613     {
614       int found = 0;
615 #    ifdef __linux__
616       /* loop over mount points */
617       struct mntent *mnt;
618       int len;
619       FILE *fp;
620
621       fp = setmntent(MOUNTED, "r");
622       if (fp == NULL) {
623         fprintf(stderr, "could not get a list of mounted filesystems\n");
624       }
625       else {
626         while ((mnt = getmntent(fp))) {
627           if (STRPREFIX(mnt->mnt_dir, "/boot")) {
628             /* Hide share not usable to the user. */
629             continue;
630           }
631           else if (!STRPREFIX(mnt->mnt_fsname, "/dev")) {
632             continue;
633           }
634           else if (STRPREFIX(mnt->mnt_fsname, "/dev/loop")) {
635             /* The dev/loop* entries are SNAPS used by desktop environment
636              * (Gnome) no need for them to show up in the list. */
637             continue;
638           }
639
640           len = strlen(mnt->mnt_dir);
641           if (len && mnt->mnt_dir[len - 1] != '/') {
642             BLI_snprintf(line, sizeof(line), "%s/", mnt->mnt_dir);
643             fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, NULL, FS_INSERT_SORTED);
644           }
645           else {
646             fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, NULL, FS_INSERT_SORTED);
647           }
648
649           found = 1;
650         }
651         if (endmntent(fp) == 0) {
652           fprintf(stderr, "could not close the list of mounted filesystems\n");
653         }
654       }
655       /* Check gvfs shares. */
656       const char *const xdg_runtime_dir = BLI_getenv("XDG_RUNTIME_DIR");
657       if (xdg_runtime_dir != NULL) {
658         struct direntry *dir;
659         char name[FILE_MAX];
660         BLI_join_dirfile(name, sizeof(name), xdg_runtime_dir, "gvfs/");
661         const uint dir_len = BLI_filelist_dir_contents(name, &dir);
662         for (uint i = 0; i < dir_len; i++) {
663           if ((dir[i].type & S_IFDIR)) {
664             const char *dirname = dir[i].relname;
665             if (dirname[0] != '.') {
666               /* Dir names contain a lot of unwanted text.
667                * Assuming every entry ends with the share name */
668               const char *label = strstr(dirname, "share=");
669               if (label != NULL) {
670                 /* Move pointer so "share=" is trimmed off
671                  * or use full dirname as label. */
672                 const char *label_test = label + 6;
673                 label = *label_test ? label_test : dirname;
674               }
675               BLI_snprintf(line, sizeof(line), "%s%s/", name, dirname);
676               fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, label, FS_INSERT_SORTED);
677               found = 1;
678             }
679           }
680         }
681         BLI_filelist_free(dir, dir_len);
682       }
683 #    endif
684
685       /* fallback */
686       if (!found) {
687         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, "/", NULL, FS_INSERT_SORTED);
688       }
689     }
690   }
691 #  endif
692 #endif
693 }
694
695 static void fsmenu_free_category(struct FSMenu *fsmenu, FSMenuCategory category)
696 {
697   FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
698
699   while (fsm_iter) {
700     FSMenuEntry *fsm_next = fsm_iter->next;
701
702     if (fsm_iter->path) {
703       MEM_freeN(fsm_iter->path);
704     }
705     MEM_freeN(fsm_iter);
706
707     fsm_iter = fsm_next;
708   }
709 }
710
711 void fsmenu_refresh_system_category(struct FSMenu *fsmenu)
712 {
713   fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
714   ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM, NULL);
715
716   fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
717   ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, NULL);
718
719   /* Add all entries to system category */
720   fsmenu_read_system(fsmenu, true);
721 }
722
723 void fsmenu_refresh_bookmarks_status(struct FSMenu *fsmenu)
724 {
725   int categories[] = {
726       FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
727   int i;
728
729   for (i = sizeof(categories) / sizeof(*categories); i--;) {
730     FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, categories[i]);
731     for (; fsm_iter; fsm_iter = fsm_iter->next) {
732       fsmenu_entry_refresh_valid(fsm_iter);
733     }
734   }
735 }
736
737 void fsmenu_free(void)
738 {
739   if (g_fsmenu) {
740     fsmenu_free_category(g_fsmenu, FS_CATEGORY_SYSTEM);
741     fsmenu_free_category(g_fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
742     fsmenu_free_category(g_fsmenu, FS_CATEGORY_BOOKMARKS);
743     fsmenu_free_category(g_fsmenu, FS_CATEGORY_RECENT);
744     MEM_freeN(g_fsmenu);
745   }
746
747   g_fsmenu = NULL;
748 }
749
750 int fsmenu_get_active_indices(struct FSMenu *fsmenu, enum FSMenuCategory category, const char *dir)
751 {
752   FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
753   int i;
754
755   for (i = 0; fsm_iter; fsm_iter = fsm_iter->next, i++) {
756     if (BLI_path_cmp(dir, fsm_iter->path) == 0) {
757       return i;
758     }
759   }
760
761   return -1;
762 }