UI: Remove Windows 3D Object Folder Reference
[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 #include "BLI_ghash.h"
34
35 #include "BLT_translation.h"
36
37 #include "BKE_appdir.h"
38
39 #include "ED_fileselect.h"
40
41 #ifdef WIN32
42 /* Need to include windows.h so _WIN32_IE is defined. */
43 #  include <windows.h>
44 /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
45  * because 'near' is disabled through BLI_windstuff. */
46 #  include <shlobj.h>
47 #  include "BLI_winstuff.h"
48 #endif
49
50 #include "WM_api.h"
51 #include "WM_types.h"
52 #include "UI_interface_icons.h"
53 #include "UI_resources.h"
54
55 #ifdef __APPLE__
56 #  include <Carbon/Carbon.h>
57 #endif /* __APPLE__ */
58
59 #ifdef __linux__
60 #  include <mntent.h>
61 #  include "BLI_fileops_types.h"
62 #endif
63
64 #include "fsmenu.h" /* include ourselves */
65
66 /* FSMENU HANDLING */
67
68 typedef struct FSMenu {
69   FSMenuEntry *fsmenu_system;
70   FSMenuEntry *fsmenu_system_bookmarks;
71   FSMenuEntry *fsmenu_bookmarks;
72   FSMenuEntry *fsmenu_recent;
73   FSMenuEntry *fsmenu_other;
74 } FSMenu;
75
76 static FSMenu *g_fsmenu = NULL;
77
78 FSMenu *ED_fsmenu_get(void)
79 {
80   if (!g_fsmenu) {
81     g_fsmenu = MEM_callocN(sizeof(struct FSMenu), "fsmenu");
82   }
83   return g_fsmenu;
84 }
85
86 struct FSMenuEntry *ED_fsmenu_get_category(struct FSMenu *fsmenu, FSMenuCategory category)
87 {
88   FSMenuEntry *fsm_head = NULL;
89
90   switch (category) {
91     case FS_CATEGORY_SYSTEM:
92       fsm_head = fsmenu->fsmenu_system;
93       break;
94     case FS_CATEGORY_SYSTEM_BOOKMARKS:
95       fsm_head = fsmenu->fsmenu_system_bookmarks;
96       break;
97     case FS_CATEGORY_BOOKMARKS:
98       fsm_head = fsmenu->fsmenu_bookmarks;
99       break;
100     case FS_CATEGORY_RECENT:
101       fsm_head = fsmenu->fsmenu_recent;
102       break;
103     case FS_CATEGORY_OTHER:
104       fsm_head = fsmenu->fsmenu_other;
105       break;
106   }
107   return fsm_head;
108 }
109
110 /* -------------------------------------------------------------------- */
111 /** \name XDG User Directory Support (Unix)
112  *
113  * Generic Unix, Use XDG when available, otherwise fallback to the home directory.
114  * \{ */
115
116 /**
117  * Look for `user-dirs.dirs`, where localized or custom user folders are defined,
118  * and store their paths in a GHash.
119  */
120 static GHash *fsmenu_xdg_user_dirs_parse(const char *home)
121 {
122   /* Add to the default for variable, equals & quotes. */
123   char l[128 + FILE_MAXDIR];
124   FILE *fp;
125
126   /* Check if the config file exists. */
127   {
128     char filepath[FILE_MAX];
129     const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
130     if (xdg_config_home != NULL) {
131       BLI_path_join(filepath, sizeof(filepath), xdg_config_home, "user-dirs.dirs", NULL);
132     }
133     else {
134       BLI_path_join(filepath, sizeof(filepath), home, ".config", "user-dirs.dirs", NULL);
135     }
136     fp = BLI_fopen(filepath, "r");
137     if (!fp) {
138       return NULL;
139     }
140   }
141   /* By default there are 8 paths. */
142   GHash *xdg_map = BLI_ghash_str_new_ex(__func__, 8);
143   while (fgets(l, sizeof(l), fp) != NULL) { /* read a line */
144
145     /* Avoid inserting invalid values. */
146     if (STRPREFIX(l, "XDG_")) {
147       char *l_value = strchr(l, '=');
148       if (l_value != NULL) {
149         *l_value = '\0';
150         l_value++;
151
152         BLI_str_rstrip(l_value);
153         const uint l_value_len = strlen(l_value);
154         if ((l_value[0] == '"') && (l_value_len > 0) && (l_value[l_value_len - 1] == '"')) {
155           l_value[l_value_len - 1] = '\0';
156           l_value++;
157
158           char l_value_expanded[FILE_MAX];
159           char *l_value_final = l_value;
160
161           /* This is currently the only variable used.
162            * Based on the 'user-dirs.dirs' man page,
163            * there is no need to resolve arbitrary environment variables. */
164           if (STRPREFIX(l_value, "$HOME" SEP_STR)) {
165             BLI_path_join(l_value_expanded, sizeof(l_value_expanded), home, l_value + 6, NULL);
166             l_value_final = l_value_expanded;
167           }
168
169           BLI_ghash_insert(xdg_map, BLI_strdup(l), BLI_strdup(l_value_final));
170         }
171       }
172     }
173   }
174   return xdg_map;
175 }
176
177 static void fsmenu_xdg_user_dirs_free(GHash *xdg_map)
178 {
179   if (xdg_map != NULL) {
180     BLI_ghash_free(xdg_map, MEM_freeN, MEM_freeN);
181   }
182 }
183
184 /**
185  *  Add fsmenu entry for system folders on linux.
186  *  - Check if a path is stored in the GHash generated from user-dirs.dirs
187  *  - If not, check for a default path in $HOME
188  *
189  *  \param key: Use `user-dirs.dirs` format "XDG_EXAMPLE_DIR"
190  *  \param default_path: Directory name to check in $HOME, also used for the menu entry name.
191  */
192 static void fsmenu_xdg_insert_entry(GHash *xdg_map,
193                                     struct FSMenu *fsmenu,
194                                     const char *key,
195                                     const char *default_path,
196                                     int icon,
197                                     const char *home)
198 {
199   char xdg_path_buf[FILE_MAXDIR];
200   const char *xdg_path = xdg_map ? BLI_ghash_lookup(xdg_map, key) : NULL;
201   if (xdg_path == NULL) {
202     BLI_path_join(xdg_path_buf, sizeof(xdg_path_buf), home, default_path, NULL);
203     xdg_path = xdg_path_buf;
204   }
205   fsmenu_insert_entry(
206       fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, xdg_path, IFACE_(default_path), icon, FS_INSERT_LAST);
207 }
208
209 /** \} */
210
211 void ED_fsmenu_set_category(struct FSMenu *fsmenu, FSMenuCategory category, FSMenuEntry *fsm_head)
212 {
213   switch (category) {
214     case FS_CATEGORY_SYSTEM:
215       fsmenu->fsmenu_system = fsm_head;
216       break;
217     case FS_CATEGORY_SYSTEM_BOOKMARKS:
218       fsmenu->fsmenu_system_bookmarks = fsm_head;
219       break;
220     case FS_CATEGORY_BOOKMARKS:
221       fsmenu->fsmenu_bookmarks = fsm_head;
222       break;
223     case FS_CATEGORY_RECENT:
224       fsmenu->fsmenu_recent = fsm_head;
225       break;
226     case FS_CATEGORY_OTHER:
227       fsmenu->fsmenu_other = fsm_head;
228       break;
229   }
230 }
231
232 int ED_fsmenu_get_nentries(struct FSMenu *fsmenu, FSMenuCategory category)
233 {
234   FSMenuEntry *fsm_iter;
235   int count = 0;
236
237   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter; fsm_iter = fsm_iter->next) {
238     count++;
239   }
240
241   return count;
242 }
243
244 FSMenuEntry *ED_fsmenu_get_entry(struct FSMenu *fsmenu, FSMenuCategory category, int index)
245 {
246   FSMenuEntry *fsm_iter;
247
248   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && index;
249        fsm_iter = fsm_iter->next) {
250     index--;
251   }
252
253   return fsm_iter;
254 }
255
256 char *ED_fsmenu_entry_get_path(struct FSMenuEntry *fsentry)
257 {
258   return fsentry->path;
259 }
260
261 void ED_fsmenu_entry_set_path(struct FSMenuEntry *fsentry, const char *path)
262 {
263   if ((!fsentry->path || !path || !STREQ(path, fsentry->path)) && (fsentry->path != path)) {
264     char tmp_name[FILE_MAXFILE];
265
266     MEM_SAFE_FREE(fsentry->path);
267
268     fsentry->path = (path && path[0]) ? BLI_strdup(path) : NULL;
269
270     BLI_make_file_string("/",
271                          tmp_name,
272                          BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
273                          BLENDER_BOOKMARK_FILE);
274     fsmenu_write_file(ED_fsmenu_get(), tmp_name);
275   }
276 }
277
278 int ED_fsmenu_entry_get_icon(struct FSMenuEntry *fsentry)
279 {
280   return (fsentry->icon) ? fsentry->icon : ICON_FILE_FOLDER;
281 }
282
283 void ED_fsmenu_entry_set_icon(struct FSMenuEntry *fsentry, const int icon)
284 {
285   fsentry->icon = icon;
286 }
287
288 static void fsmenu_entry_generate_name(struct FSMenuEntry *fsentry, char *name, size_t name_size)
289 {
290   int offset = 0;
291   int len = name_size;
292
293   if (BLI_path_name_at_index(fsentry->path, -1, &offset, &len)) {
294     /* use as size */
295     len += 1;
296   }
297
298   BLI_strncpy(name, &fsentry->path[offset], MIN2(len, name_size));
299   if (!name[0]) {
300     name[0] = '/';
301     name[1] = '\0';
302   }
303 }
304
305 char *ED_fsmenu_entry_get_name(struct FSMenuEntry *fsentry)
306 {
307   if (fsentry->name[0]) {
308     return fsentry->name;
309   }
310   else {
311     /* Here we abuse fsm_iter->name, keeping first char NULL. */
312     char *name = fsentry->name + 1;
313     size_t name_size = sizeof(fsentry->name) - 1;
314
315     fsmenu_entry_generate_name(fsentry, name, name_size);
316     return name;
317   }
318 }
319
320 void ED_fsmenu_entry_set_name(struct FSMenuEntry *fsentry, const char *name)
321 {
322   if (!STREQ(name, fsentry->name)) {
323     char tmp_name[FILE_MAXFILE];
324     size_t tmp_name_size = sizeof(tmp_name);
325
326     fsmenu_entry_generate_name(fsentry, tmp_name, tmp_name_size);
327     if (!name[0] || STREQ(tmp_name, name)) {
328       /* reset name to default behavior. */
329       fsentry->name[0] = '\0';
330     }
331     else {
332       BLI_strncpy(fsentry->name, name, sizeof(fsentry->name));
333     }
334
335     BLI_make_file_string("/",
336                          tmp_name,
337                          BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
338                          BLENDER_BOOKMARK_FILE);
339     fsmenu_write_file(ED_fsmenu_get(), tmp_name);
340   }
341 }
342
343 void fsmenu_entry_refresh_valid(struct FSMenuEntry *fsentry)
344 {
345   if (fsentry->path && fsentry->path[0]) {
346 #ifdef WIN32
347     /* XXX Special case, always consider those as valid.
348      * Thanks to Windows, which can spend five seconds to perform a mere stat() call on those paths
349      * See T43684. */
350     const char *exceptions[] = {"A:\\", "B:\\", NULL};
351     const size_t exceptions_len[] = {strlen(exceptions[0]), strlen(exceptions[1]), 0};
352     int i;
353
354     for (i = 0; exceptions[i]; i++) {
355       if (STRCASEEQLEN(fsentry->path, exceptions[i], exceptions_len[i])) {
356         fsentry->valid = true;
357         return;
358       }
359     }
360 #endif
361     fsentry->valid = BLI_is_dir(fsentry->path);
362   }
363   else {
364     fsentry->valid = false;
365   }
366 }
367
368 short fsmenu_can_save(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
369 {
370   FSMenuEntry *fsm_iter;
371
372   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx;
373        fsm_iter = fsm_iter->next) {
374     idx--;
375   }
376
377   return fsm_iter ? fsm_iter->save : 0;
378 }
379
380 void fsmenu_insert_entry(struct FSMenu *fsmenu,
381                          FSMenuCategory category,
382                          const char *path,
383                          const char *name,
384                          int icon,
385                          FSMenuInsert flag)
386 {
387   const uint path_len = strlen(path);
388   BLI_assert(path_len > 0);
389   if (path_len == 0) {
390     return;
391   }
392   const bool has_trailing_slash = (path[path_len - 1] == SEP);
393   FSMenuEntry *fsm_prev;
394   FSMenuEntry *fsm_iter;
395   FSMenuEntry *fsm_head;
396
397   fsm_head = ED_fsmenu_get_category(fsmenu, category);
398   fsm_prev = fsm_head; /* this is odd and not really correct? */
399
400   for (fsm_iter = fsm_head; fsm_iter; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
401     if (fsm_iter->path) {
402       /* Compare, with/without the trailing slash in 'path'. */
403       const int cmp_ret = BLI_path_ncmp(path, fsm_iter->path, path_len);
404       if (cmp_ret == 0 && STREQ(fsm_iter->path + path_len, has_trailing_slash ? "" : SEP_STR)) {
405         if (flag & FS_INSERT_FIRST) {
406           if (fsm_iter != fsm_head) {
407             fsm_prev->next = fsm_iter->next;
408             fsm_iter->next = fsm_head;
409             ED_fsmenu_set_category(fsmenu, category, fsm_iter);
410           }
411         }
412         return;
413       }
414       else if ((flag & FS_INSERT_SORTED) && cmp_ret < 0) {
415         break;
416       }
417     }
418     else {
419       /* if we're bookmarking this, file should come
420        * before the last separator, only automatically added
421        * current dir go after the last sep. */
422       if (flag & FS_INSERT_SAVE) {
423         break;
424       }
425     }
426   }
427
428   fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme");
429   if (has_trailing_slash) {
430     fsm_iter->path = BLI_strdup(path);
431   }
432   else {
433     fsm_iter->path = BLI_strdupn(path, path_len + 1);
434     fsm_iter->path[path_len] = SEP;
435     fsm_iter->path[path_len + 1] = '\0';
436   }
437   fsm_iter->save = (flag & FS_INSERT_SAVE) != 0;
438
439   /* If entry is also in another list, use that icon and maybe name. */
440   /* On macOS we get icons and names for System Bookmarks from the FS_CATEGORY_OTHER list. */
441   if (ELEM(category, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT)) {
442
443     const FSMenuCategory cats[] = {
444         FS_CATEGORY_OTHER,
445         FS_CATEGORY_SYSTEM,
446         FS_CATEGORY_SYSTEM_BOOKMARKS,
447         FS_CATEGORY_BOOKMARKS,
448     };
449     int i = ARRAY_SIZE(cats);
450     if (category == FS_CATEGORY_BOOKMARKS) {
451       i--;
452     }
453
454     while (i--) {
455       FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, cats[i]);
456       for (; tfsm; tfsm = tfsm->next) {
457         if (STREQ(tfsm->path, fsm_iter->path)) {
458           icon = tfsm->icon;
459           if (tfsm->name[0] && (!name || !name[0])) {
460             name = tfsm->name;
461           }
462           break;
463         }
464       }
465       if (tfsm) {
466         break;
467       }
468     }
469   }
470
471   if (name && name[0]) {
472     BLI_strncpy(fsm_iter->name, name, sizeof(fsm_iter->name));
473   }
474   else {
475     fsm_iter->name[0] = '\0';
476   }
477
478   ED_fsmenu_entry_set_icon(fsm_iter, icon);
479
480   fsmenu_entry_refresh_valid(fsm_iter);
481
482   if (fsm_prev) {
483     if (flag & FS_INSERT_FIRST) {
484       fsm_iter->next = fsm_head;
485       ED_fsmenu_set_category(fsmenu, category, fsm_iter);
486     }
487     else {
488       fsm_iter->next = fsm_prev->next;
489       fsm_prev->next = fsm_iter;
490     }
491   }
492   else {
493     fsm_iter->next = fsm_head;
494     ED_fsmenu_set_category(fsmenu, category, fsm_iter);
495   }
496 }
497
498 void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
499 {
500   FSMenuEntry *fsm_prev = NULL;
501   FSMenuEntry *fsm_iter;
502   FSMenuEntry *fsm_head;
503
504   fsm_head = ED_fsmenu_get_category(fsmenu, category);
505
506   for (fsm_iter = fsm_head; fsm_iter && idx; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
507     idx--;
508   }
509
510   if (fsm_iter) {
511     /* you should only be able to remove entries that were
512      * not added by default, like windows drives.
513      * also separators (where path == NULL) shouldn't be removed */
514     if (fsm_iter->save && fsm_iter->path) {
515
516       /* remove fsme from list */
517       if (fsm_prev) {
518         fsm_prev->next = fsm_iter->next;
519       }
520       else {
521         fsm_head = fsm_iter->next;
522         ED_fsmenu_set_category(fsmenu, category, fsm_head);
523       }
524       /* free entry */
525       MEM_freeN(fsm_iter->path);
526       MEM_freeN(fsm_iter);
527     }
528   }
529 }
530
531 void fsmenu_write_file(struct FSMenu *fsmenu, const char *filename)
532 {
533   FSMenuEntry *fsm_iter = NULL;
534   char fsm_name[FILE_MAX];
535   int nwritten = 0;
536
537   FILE *fp = BLI_fopen(filename, "w");
538   if (!fp) {
539     return;
540   }
541
542   fprintf(fp, "[Bookmarks]\n");
543   for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter;
544        fsm_iter = fsm_iter->next) {
545     if (fsm_iter->path && fsm_iter->save) {
546       fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
547       if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
548         fprintf(fp, "!%s\n", fsm_iter->name);
549       }
550       fprintf(fp, "%s\n", fsm_iter->path);
551     }
552   }
553   fprintf(fp, "[Recent]\n");
554   for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT);
555        fsm_iter && (nwritten < FSMENU_RECENT_MAX);
556        fsm_iter = fsm_iter->next, nwritten++) {
557     if (fsm_iter->path && fsm_iter->save) {
558       fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
559       if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
560         fprintf(fp, "!%s\n", fsm_iter->name);
561       }
562       fprintf(fp, "%s\n", fsm_iter->path);
563     }
564   }
565   fclose(fp);
566 }
567
568 void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filename)
569 {
570   char line[FILE_MAXDIR];
571   char name[FILE_MAXFILE];
572   FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
573   FILE *fp;
574
575   fp = BLI_fopen(filename, "r");
576   if (!fp) {
577     return;
578   }
579
580   name[0] = '\0';
581
582   while (fgets(line, sizeof(line), fp) != NULL) { /* read a line */
583     if (STREQLEN(line, "[Bookmarks]", 11)) {
584       category = FS_CATEGORY_BOOKMARKS;
585     }
586     else if (STREQLEN(line, "[Recent]", 8)) {
587       category = FS_CATEGORY_RECENT;
588     }
589     else if (line[0] == '!') {
590       int len = strlen(line);
591       if (len > 0) {
592         if (line[len - 1] == '\n') {
593           line[len - 1] = '\0';
594         }
595         BLI_strncpy(name, line + 1, sizeof(name));
596       }
597     }
598     else {
599       int len = strlen(line);
600       if (len > 0) {
601         if (line[len - 1] == '\n') {
602           line[len - 1] = '\0';
603         }
604         /* don't do this because it can be slow on network drives,
605          * having a bookmark from a drive that's ejected or so isn't
606          * all _that_ bad */
607 #if 0
608         if (BLI_exists(line))
609 #endif
610         {
611           fsmenu_insert_entry(fsmenu, category, line, name, ICON_FILE_FOLDER, FS_INSERT_SAVE);
612         }
613       }
614       /* always reset name. */
615       name[0] = '\0';
616     }
617   }
618   fclose(fp);
619 }
620
621 #ifdef WIN32
622 /* Add a Windows known folder path to the System list. */
623 static void fsmenu_add_windows_folder(struct FSMenu *fsmenu,
624                                       FSMenuCategory category,
625                                       REFKNOWNFOLDERID rfid,
626                                       const char *name,
627                                       const int icon,
628                                       FSMenuInsert flag)
629 {
630   LPWSTR pPath;
631   char line[FILE_MAXDIR];
632   if (SHGetKnownFolderPath(rfid, 0, NULL, &pPath) == S_OK) {
633     BLI_strncpy_wchar_as_utf8(line, pPath, FILE_MAXDIR);
634     CoTaskMemFree(pPath);
635     fsmenu_insert_entry(fsmenu, category, line, name, icon, flag);
636   }
637 }
638 #endif
639
640 void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
641 {
642   char line[FILE_MAXDIR];
643 #ifdef WIN32
644   /* Add the drive names to the listing */
645   {
646     wchar_t wline[FILE_MAXDIR];
647     __int64 tmp;
648     char tmps[4], *name;
649     int i;
650
651     tmp = GetLogicalDrives();
652
653     for (i = 0; i < 26; i++) {
654       if ((tmp >> i) & 1) {
655         tmps[0] = 'A' + i;
656         tmps[1] = ':';
657         tmps[2] = '\\';
658         tmps[3] = '\0';
659         name = NULL;
660
661         /* Flee from horrible win querying hover floppy drives! */
662         if (i > 1) {
663           /* Try to get a friendly drive description. */
664           SHFILEINFOW shFile = {0};
665           BLI_strncpy_wchar_from_utf8(wline, tmps, 4);
666           if (SHGetFileInfoW(wline, 0, &shFile, sizeof(SHFILEINFOW), SHGFI_DISPLAYNAME)) {
667             BLI_strncpy_wchar_as_utf8(line, shFile.szDisplayName, FILE_MAXDIR);
668             name = line;
669           }
670         }
671         if (name == NULL) {
672           name = tmps;
673         }
674
675         int icon = ICON_DISK_DRIVE;
676         switch (GetDriveType(tmps)) {
677           case DRIVE_REMOVABLE:
678             icon = ICON_EXTERNAL_DRIVE;
679             break;
680           case DRIVE_CDROM:
681             icon = ICON_DISC;
682             break;
683           case DRIVE_FIXED:
684           case DRIVE_RAMDISK:
685             icon = ICON_DISK_DRIVE;
686             break;
687           case DRIVE_REMOTE:
688             icon = ICON_NETWORK_DRIVE;
689             break;
690         }
691
692         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, name, icon, FS_INSERT_SORTED);
693       }
694     }
695
696     /* Get Special Folder Locations. */
697     if (read_bookmarks) {
698
699       /* These items are shown in System List. */
700       fsmenu_add_windows_folder(fsmenu,
701                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
702                                 &FOLDERID_Profile,
703                                 IFACE_("Home"),
704                                 ICON_HOME,
705                                 FS_INSERT_LAST);
706       fsmenu_add_windows_folder(fsmenu,
707                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
708                                 &FOLDERID_Desktop,
709                                 IFACE_("Desktop"),
710                                 ICON_DESKTOP,
711                                 FS_INSERT_LAST);
712       fsmenu_add_windows_folder(fsmenu,
713                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
714                                 &FOLDERID_Documents,
715                                 IFACE_("Documents"),
716                                 ICON_DOCUMENTS,
717                                 FS_INSERT_LAST);
718       fsmenu_add_windows_folder(fsmenu,
719                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
720                                 &FOLDERID_Downloads,
721                                 IFACE_("Downloads"),
722                                 ICON_IMPORT,
723                                 FS_INSERT_LAST);
724       fsmenu_add_windows_folder(fsmenu,
725                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
726                                 &FOLDERID_Music,
727                                 IFACE_("Music"),
728                                 ICON_FILE_SOUND,
729                                 FS_INSERT_LAST);
730       fsmenu_add_windows_folder(fsmenu,
731                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
732                                 &FOLDERID_Pictures,
733                                 IFACE_("Pictures"),
734                                 ICON_FILE_IMAGE,
735                                 FS_INSERT_LAST);
736       fsmenu_add_windows_folder(fsmenu,
737                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
738                                 &FOLDERID_Videos,
739                                 IFACE_("Videos"),
740                                 ICON_FILE_MOVIE,
741                                 FS_INSERT_LAST);
742       fsmenu_add_windows_folder(fsmenu,
743                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
744                                 &FOLDERID_Fonts,
745                                 IFACE_("Fonts"),
746                                 ICON_FILE_FONT,
747                                 FS_INSERT_LAST);
748
749       /* These items are just put in path cache for thumbnail views and if bookmarked. */
750
751       fsmenu_add_windows_folder(
752           fsmenu, FS_CATEGORY_OTHER, &FOLDERID_UserProfiles, NULL, ICON_COMMUNITY, FS_INSERT_LAST);
753
754       fsmenu_add_windows_folder(
755           fsmenu, FS_CATEGORY_OTHER, &FOLDERID_SkyDrive, NULL, ICON_URL, FS_INSERT_LAST);
756     }
757   }
758 #else
759 #  ifdef __APPLE__
760   {
761     /* We store some known macOS system paths and corresponding icons
762      * and names in the FS_CATEGORY_OTHER (not displayed directly) category. */
763     fsmenu_insert_entry(fsmenu,
764                         FS_CATEGORY_OTHER,
765                         "/Library/Fonts/",
766                         IFACE_("Fonts"),
767                         ICON_FILE_FONT,
768                         FS_INSERT_LAST);
769     fsmenu_insert_entry(fsmenu,
770                         FS_CATEGORY_OTHER,
771                         "/Applications/",
772                         IFACE_("Applications"),
773                         ICON_FILE_FOLDER,
774                         FS_INSERT_LAST);
775
776     const char *home = BLI_getenv("HOME");
777
778 #    define FS_MACOS_PATH(path, name, icon) \
779       BLI_snprintf(line, sizeof(line), path, home); \
780       fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, line, name, icon, FS_INSERT_LAST);
781
782     FS_MACOS_PATH("%s/", NULL, ICON_HOME)
783     FS_MACOS_PATH("%s/Desktop/", IFACE_("Desktop"), ICON_DESKTOP)
784     FS_MACOS_PATH("%s/Documents/", IFACE_("Documents"), ICON_DOCUMENTS)
785     FS_MACOS_PATH("%s/Downloads/", IFACE_("Downloads"), ICON_IMPORT)
786     FS_MACOS_PATH("%s/Movies/", IFACE_("Movies"), ICON_FILE_MOVIE)
787     FS_MACOS_PATH("%s/Music/", IFACE_("Music"), ICON_FILE_SOUND)
788     FS_MACOS_PATH("%s/Pictures/", IFACE_("Pictures"), ICON_FILE_IMAGE)
789     FS_MACOS_PATH("%s/Library/Fonts/", IFACE_("Fonts"), ICON_FILE_FONT)
790
791 #    undef FS_MACOS_PATH
792
793     /* Get mounted volumes better method OSX 10.6 and higher, see:
794      * https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html
795      */
796
797     /* We get all volumes sorted including network and do not relay
798      * on user-defined finder visibility, less confusing. */
799
800     CFURLRef cfURL = NULL;
801     CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
802     CFURLEnumeratorRef volEnum = CFURLEnumeratorCreateForMountedVolumes(
803         NULL, kCFURLEnumeratorSkipInvisibles, NULL);
804
805     while (result != kCFURLEnumeratorEnd) {
806       char defPath[FILE_MAX];
807
808       result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, NULL);
809       if (result != kCFURLEnumeratorSuccess) {
810         continue;
811       }
812
813       CFURLGetFileSystemRepresentation(cfURL, false, (UInt8 *)defPath, FILE_MAX);
814
815       /* Get name of the volume. */
816       char name[FILE_MAXFILE] = "";
817       CFStringRef nameString = NULL;
818       CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeLocalizedNameKey, &nameString, NULL);
819       if (nameString != NULL) {
820         CFStringGetCString(nameString, name, sizeof(name), kCFStringEncodingUTF8);
821         CFRelease(nameString);
822       }
823
824       /* Set icon for regular, removable or network drive. */
825       int icon = ICON_DISK_DRIVE;
826       CFBooleanRef localKey = NULL;
827       CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeIsLocalKey, &localKey, NULL);
828       if (localKey != NULL) {
829         if (!CFBooleanGetValue(localKey)) {
830           icon = ICON_NETWORK_DRIVE;
831         }
832         else {
833           CFBooleanRef ejectableKey = NULL;
834           CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeIsEjectableKey, &ejectableKey, NULL);
835           if (ejectableKey != NULL) {
836             if (CFBooleanGetValue(ejectableKey)) {
837               icon = ICON_EXTERNAL_DRIVE;
838             }
839             CFRelease(ejectableKey);
840           }
841         }
842         CFRelease(localKey);
843       }
844
845       fsmenu_insert_entry(
846           fsmenu, FS_CATEGORY_SYSTEM, defPath, name[0] ? name : NULL, icon, FS_INSERT_SORTED);
847     }
848
849     CFRelease(volEnum);
850
851     /* Finally get user favorite places */
852     if (read_bookmarks) {
853       UInt32 seed;
854       LSSharedFileListRef list = LSSharedFileListCreate(
855           NULL, kLSSharedFileListFavoriteItems, NULL);
856       CFArrayRef pathesArray = LSSharedFileListCopySnapshot(list, &seed);
857       CFIndex pathesCount = CFArrayGetCount(pathesArray);
858
859       for (CFIndex i = 0; i < pathesCount; i++) {
860         LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(
861             pathesArray, i);
862
863         CFURLRef cfURL = NULL;
864         OSErr err = LSSharedFileListItemResolve(itemRef,
865                                                 kLSSharedFileListNoUserInteraction |
866                                                     kLSSharedFileListDoNotMountVolumes,
867                                                 &cfURL,
868                                                 NULL);
869         if (err != noErr || !cfURL) {
870           continue;
871         }
872
873         CFStringRef pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
874
875         if (pathString == NULL ||
876             !CFStringGetCString(pathString, line, sizeof(line), kCFStringEncodingUTF8)) {
877           continue;
878         }
879
880         /* Exclude "all my files" as it makes no sense in blender fileselector */
881         /* Exclude "airdrop" if wlan not active as it would show "" ) */
882         if (!strstr(line, "myDocuments.cannedSearch") && (*line != '\0')) {
883           fsmenu_insert_entry(
884               fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, ICON_FILE_FOLDER, FS_INSERT_LAST);
885         }
886
887         CFRelease(pathString);
888         CFRelease(cfURL);
889       }
890
891       CFRelease(pathesArray);
892       CFRelease(list);
893     }
894   }
895 #  else
896   /* unix */
897   {
898     const char *home = BLI_getenv("HOME");
899
900     if (read_bookmarks && home) {
901
902       fsmenu_insert_entry(
903           fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, home, IFACE_("Home"), ICON_HOME, FS_INSERT_LAST);
904
905       /* Follow the XDG spec, check if these are available. */
906       GHash *xdg_map = fsmenu_xdg_user_dirs_parse(home);
907
908       struct {
909         const char *key;
910         const char *default_path;
911         BIFIconID icon;
912       } xdg_items[] = {
913           {"XDG_DESKTOP_DIR", "Desktop", ICON_DESKTOP},
914           {"XDG_DOCUMENTS_DIR", "Documents", ICON_DOCUMENTS},
915           {"XDG_DOWNLOAD_DIR", "Downloads", ICON_IMPORT},
916           {"XDG_VIDEOS_DIR", "Videos", ICON_FILE_MOVIE},
917           {"XDG_PICTURES_DIR", "Pictures", ICON_FILE_IMAGE},
918           {"XDG_MUSIC_DIR", "Music", ICON_FILE_SOUND},
919       };
920
921       for (int i = 0; i < ARRAY_SIZE(xdg_items); i++) {
922         fsmenu_xdg_insert_entry(
923             xdg_map, fsmenu, xdg_items[i].key, xdg_items[i].default_path, xdg_items[i].icon, home);
924       }
925
926       fsmenu_xdg_user_dirs_free(xdg_map);
927     }
928
929     {
930       int found = 0;
931 #    ifdef __linux__
932       /* loop over mount points */
933       struct mntent *mnt;
934       FILE *fp;
935
936       fp = setmntent(MOUNTED, "r");
937       if (fp == NULL) {
938         fprintf(stderr, "could not get a list of mounted filesystems\n");
939       }
940       else {
941         while ((mnt = getmntent(fp))) {
942           if (STRPREFIX(mnt->mnt_dir, "/boot")) {
943             /* Hide share not usable to the user. */
944             continue;
945           }
946           else if (!STRPREFIX(mnt->mnt_fsname, "/dev")) {
947             continue;
948           }
949           else if (STRPREFIX(mnt->mnt_fsname, "/dev/loop")) {
950             /* The dev/loop* entries are SNAPS used by desktop environment
951              * (Gnome) no need for them to show up in the list. */
952             continue;
953           }
954
955           fsmenu_insert_entry(
956               fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, NULL, ICON_DISK_DRIVE, FS_INSERT_SORTED);
957
958           found = 1;
959         }
960         if (endmntent(fp) == 0) {
961           fprintf(stderr, "could not close the list of mounted filesystems\n");
962         }
963       }
964       /* Check gvfs shares. */
965       const char *const xdg_runtime_dir = BLI_getenv("XDG_RUNTIME_DIR");
966       if (xdg_runtime_dir != NULL) {
967         struct direntry *dir;
968         char name[FILE_MAX];
969         BLI_join_dirfile(name, sizeof(name), xdg_runtime_dir, "gvfs/");
970         const uint dir_len = BLI_filelist_dir_contents(name, &dir);
971         for (uint i = 0; i < dir_len; i++) {
972           if ((dir[i].type & S_IFDIR)) {
973             const char *dirname = dir[i].relname;
974             if (dirname[0] != '.') {
975               /* Dir names contain a lot of unwanted text.
976                * Assuming every entry ends with the share name */
977               const char *label = strstr(dirname, "share=");
978               if (label != NULL) {
979                 /* Move pointer so "share=" is trimmed off
980                  * or use full dirname as label. */
981                 const char *label_test = label + 6;
982                 label = *label_test ? label_test : dirname;
983               }
984               BLI_snprintf(line, sizeof(line), "%s%s", name, dirname);
985               fsmenu_insert_entry(
986                   fsmenu, FS_CATEGORY_SYSTEM, line, label, ICON_NETWORK_DRIVE, FS_INSERT_SORTED);
987               found = 1;
988             }
989           }
990         }
991         BLI_filelist_free(dir, dir_len);
992       }
993 #    endif
994
995       /* fallback */
996       if (!found) {
997         fsmenu_insert_entry(
998             fsmenu, FS_CATEGORY_SYSTEM, "/", NULL, ICON_DISK_DRIVE, FS_INSERT_SORTED);
999       }
1000     }
1001   }
1002 #  endif
1003 #endif
1004
1005 #if defined(WIN32) || defined(__APPLE__)
1006   /* Quiet warnings. */
1007   UNUSED_VARS(fsmenu_xdg_insert_entry, fsmenu_xdg_user_dirs_parse, fsmenu_xdg_user_dirs_free);
1008 #endif
1009
1010   /* For all platforms, we add some directories from User Preferences to
1011    * the FS_CATEGORY_OTHER category so that these directories
1012    * have the appropriate icons when they are added to the Bookmarks. */
1013 #define FS_UDIR_PATH(dir, icon) \
1014   if (BLI_strnlen(dir, 3) > 2) { \
1015     fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, dir, NULL, icon, FS_INSERT_LAST); \
1016   }
1017
1018   FS_UDIR_PATH(U.fontdir, ICON_FILE_FONT)
1019   FS_UDIR_PATH(U.textudir, ICON_FILE_IMAGE)
1020   FS_UDIR_PATH(U.pythondir, ICON_FILE_SCRIPT)
1021   FS_UDIR_PATH(U.sounddir, ICON_FILE_SOUND)
1022   FS_UDIR_PATH(U.tempdir, ICON_TEMP)
1023
1024 #undef FS_UDIR_PATH
1025 }
1026
1027 static void fsmenu_free_category(struct FSMenu *fsmenu, FSMenuCategory category)
1028 {
1029   FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
1030
1031   while (fsm_iter) {
1032     FSMenuEntry *fsm_next = fsm_iter->next;
1033
1034     if (fsm_iter->path) {
1035       MEM_freeN(fsm_iter->path);
1036     }
1037     MEM_freeN(fsm_iter);
1038
1039     fsm_iter = fsm_next;
1040   }
1041 }
1042
1043 void fsmenu_refresh_system_category(struct FSMenu *fsmenu)
1044 {
1045   fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
1046   ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM, NULL);
1047
1048   fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
1049   ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, NULL);
1050
1051   /* Add all entries to system category */
1052   fsmenu_read_system(fsmenu, true);
1053 }
1054
1055 static void fsmenu_free_ex(FSMenu **fsmenu)
1056 {
1057   if (*fsmenu != NULL) {
1058     fsmenu_free_category(*fsmenu, FS_CATEGORY_SYSTEM);
1059     fsmenu_free_category(*fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
1060     fsmenu_free_category(*fsmenu, FS_CATEGORY_BOOKMARKS);
1061     fsmenu_free_category(*fsmenu, FS_CATEGORY_RECENT);
1062     fsmenu_free_category(*fsmenu, FS_CATEGORY_OTHER);
1063     MEM_freeN(*fsmenu);
1064   }
1065
1066   *fsmenu = NULL;
1067 }
1068
1069 void fsmenu_free(void)
1070 {
1071   fsmenu_free_ex(&g_fsmenu);
1072 }
1073
1074 static void fsmenu_copy_category(struct FSMenu *fsmenu_dst,
1075                                  struct FSMenu *fsmenu_src,
1076                                  const FSMenuCategory category)
1077 {
1078   FSMenuEntry *fsm_dst_prev = NULL, *fsm_dst_head = NULL;
1079   FSMenuEntry *fsm_src_iter = ED_fsmenu_get_category(fsmenu_src, category);
1080
1081   for (; fsm_src_iter != NULL; fsm_src_iter = fsm_src_iter->next) {
1082     FSMenuEntry *fsm_dst = MEM_dupallocN(fsm_src_iter);
1083     if (fsm_dst->path != NULL) {
1084       fsm_dst->path = MEM_dupallocN(fsm_dst->path);
1085     }
1086
1087     if (fsm_dst_prev != NULL) {
1088       fsm_dst_prev->next = fsm_dst;
1089     }
1090     else {
1091       fsm_dst_head = fsm_dst;
1092     }
1093     fsm_dst_prev = fsm_dst;
1094   }
1095
1096   ED_fsmenu_set_category(fsmenu_dst, category, fsm_dst_head);
1097 }
1098
1099 static FSMenu *fsmenu_copy(FSMenu *fsmenu)
1100 {
1101   FSMenu *fsmenu_copy = MEM_dupallocN(fsmenu);
1102
1103   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_SYSTEM);
1104   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_SYSTEM_BOOKMARKS);
1105   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_BOOKMARKS);
1106   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_RECENT);
1107   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_OTHER);
1108
1109   return fsmenu_copy;
1110 }
1111
1112 int fsmenu_get_active_indices(struct FSMenu *fsmenu, enum FSMenuCategory category, const char *dir)
1113 {
1114   FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
1115   int i;
1116
1117   for (i = 0; fsm_iter; fsm_iter = fsm_iter->next, i++) {
1118     if (BLI_path_cmp(dir, fsm_iter->path) == 0) {
1119       return i;
1120     }
1121   }
1122
1123   return -1;
1124 }
1125
1126 /* Thanks to some bookmarks sometimes being network drives that can have tens of seconds of delay
1127  * before being defined as unreachable by the OS, we need to validate the bookmarks in an async
1128  * job...
1129  */
1130 static void fsmenu_bookmark_validate_job_startjob(void *fsmenuv,
1131                                                   short *stop,
1132                                                   short *do_update,
1133                                                   float *UNUSED(progress))
1134 {
1135   FSMenu *fsmenu = fsmenuv;
1136
1137   int categories[] = {
1138       FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
1139
1140   for (size_t i = ARRAY_SIZE(categories); i--;) {
1141     FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, categories[i]);
1142     for (; fsm_iter; fsm_iter = fsm_iter->next) {
1143       if (*stop) {
1144         return;
1145       }
1146       /* Note that we do not really need atomics primitives or thread locks here, since this only
1147        * sets one short, which is assumed to be 'atomic'-enough for us here. */
1148       fsmenu_entry_refresh_valid(fsm_iter);
1149       *do_update = true;
1150     }
1151   }
1152 }
1153
1154 static void fsmenu_bookmark_validate_job_update(void *fsmenuv)
1155 {
1156   FSMenu *fsmenu_job = fsmenuv;
1157
1158   int categories[] = {
1159       FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
1160
1161   for (size_t i = ARRAY_SIZE(categories); i--;) {
1162     FSMenuEntry *fsm_iter_src = ED_fsmenu_get_category(fsmenu_job, categories[i]);
1163     FSMenuEntry *fsm_iter_dst = ED_fsmenu_get_category(ED_fsmenu_get(), categories[i]);
1164     for (; fsm_iter_dst != NULL; fsm_iter_dst = fsm_iter_dst->next) {
1165       while (fsm_iter_src != NULL && !STREQ(fsm_iter_dst->path, fsm_iter_src->path)) {
1166         fsm_iter_src = fsm_iter_src->next;
1167       }
1168       if (fsm_iter_src == NULL) {
1169         return;
1170       }
1171       fsm_iter_dst->valid = fsm_iter_src->valid;
1172     }
1173   }
1174 }
1175
1176 static void fsmenu_bookmark_validate_job_end(void *fsmenuv)
1177 {
1178   /* In case there would be some dangling update... */
1179   fsmenu_bookmark_validate_job_update(fsmenuv);
1180 }
1181
1182 static void fsmenu_bookmark_validate_job_free(void *fsmenuv)
1183 {
1184   FSMenu *fsmenu = fsmenuv;
1185   fsmenu_free_ex(&fsmenu);
1186 }
1187
1188 static void fsmenu_bookmark_validate_job_start(wmWindowManager *wm)
1189 {
1190   wmJob *wm_job;
1191   FSMenu *fsmenu_job = fsmenu_copy(g_fsmenu);
1192
1193   /* setup job */
1194   wm_job = WM_jobs_get(
1195       wm, wm->winactive, wm, "Validating Bookmarks...", 0, WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE);
1196   WM_jobs_customdata_set(wm_job, fsmenu_job, fsmenu_bookmark_validate_job_free);
1197   WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST);
1198   WM_jobs_callbacks(wm_job,
1199                     fsmenu_bookmark_validate_job_startjob,
1200                     NULL,
1201                     fsmenu_bookmark_validate_job_update,
1202                     fsmenu_bookmark_validate_job_end);
1203
1204   /* start the job */
1205   WM_jobs_start(wm, wm_job);
1206 }
1207
1208 static void fsmenu_bookmark_validate_job_stop(wmWindowManager *wm)
1209 {
1210   WM_jobs_kill_type(wm, wm, WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE);
1211 }
1212
1213 void fsmenu_refresh_bookmarks_status(wmWindowManager *wm, FSMenu *fsmenu)
1214 {
1215   BLI_assert(fsmenu == ED_fsmenu_get());
1216   UNUSED_VARS_NDEBUG(fsmenu);
1217
1218   fsmenu_bookmark_validate_job_stop(wm);
1219   fsmenu_bookmark_validate_job_start(wm);
1220 }