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