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.
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.
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.
16 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17 * All rights reserved.
30 #include "MEM_guardedalloc.h"
32 #include "BLI_utildefines.h"
33 #include "BLI_blenlib.h"
35 #include "BKE_appdir.h"
37 #include "ED_fileselect.h"
40 /* Need to include windows.h so _WIN32_IE is defined. */
42 /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
43 * because 'near' is disabled through BLI_windstuff. */
45 # include "BLI_winstuff.h"
49 #include <Carbon/Carbon.h>
50 #endif /* __APPLE__ */
54 #include "BLI_fileops_types.h"
57 #include "fsmenu.h" /* include ourselves */
62 typedef struct FSMenu {
63 FSMenuEntry *fsmenu_system;
64 FSMenuEntry *fsmenu_system_bookmarks;
65 FSMenuEntry *fsmenu_bookmarks;
66 FSMenuEntry *fsmenu_recent;
69 static FSMenu *g_fsmenu = NULL;
71 FSMenu *ED_fsmenu_get(void)
74 g_fsmenu = MEM_callocN(sizeof(struct FSMenu), "fsmenu");
79 struct FSMenuEntry *ED_fsmenu_get_category(struct FSMenu *fsmenu, FSMenuCategory category)
81 FSMenuEntry *fsm_head = NULL;
84 case FS_CATEGORY_SYSTEM:
85 fsm_head = fsmenu->fsmenu_system;
87 case FS_CATEGORY_SYSTEM_BOOKMARKS:
88 fsm_head = fsmenu->fsmenu_system_bookmarks;
90 case FS_CATEGORY_BOOKMARKS:
91 fsm_head = fsmenu->fsmenu_bookmarks;
93 case FS_CATEGORY_RECENT:
94 fsm_head = fsmenu->fsmenu_recent;
100 void ED_fsmenu_set_category(struct FSMenu *fsmenu, FSMenuCategory category, FSMenuEntry *fsm_head)
103 case FS_CATEGORY_SYSTEM:
104 fsmenu->fsmenu_system = fsm_head;
106 case FS_CATEGORY_SYSTEM_BOOKMARKS:
107 fsmenu->fsmenu_system_bookmarks = fsm_head;
109 case FS_CATEGORY_BOOKMARKS:
110 fsmenu->fsmenu_bookmarks = fsm_head;
112 case FS_CATEGORY_RECENT:
113 fsmenu->fsmenu_recent = fsm_head;
118 int ED_fsmenu_get_nentries(struct FSMenu *fsmenu, FSMenuCategory category)
120 FSMenuEntry *fsm_iter;
123 for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter; fsm_iter = fsm_iter->next) {
130 FSMenuEntry *ED_fsmenu_get_entry(struct FSMenu *fsmenu, FSMenuCategory category, int index)
132 FSMenuEntry *fsm_iter;
134 for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && index; fsm_iter = fsm_iter->next) {
141 char *ED_fsmenu_entry_get_path(struct FSMenuEntry *fsentry)
143 return fsentry->path;
146 void ED_fsmenu_entry_set_path(struct FSMenuEntry *fsentry, const char *path)
148 if ((!fsentry->path || !path || !STREQ(path, fsentry->path)) && (fsentry->path != path)) {
149 char tmp_name[FILE_MAXFILE];
151 MEM_SAFE_FREE(fsentry->path);
153 fsentry->path = (path && path[0]) ? BLI_strdup(path) : NULL;
155 BLI_make_file_string("/", tmp_name, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
156 fsmenu_write_file(ED_fsmenu_get(), tmp_name);
160 static void fsmenu_entry_generate_name(struct FSMenuEntry *fsentry, char *name, size_t name_size)
165 if (BLI_path_name_at_index(fsentry->path, -1, &offset, &len)) {
170 BLI_strncpy(name, &fsentry->path[offset], MIN2(len, name_size));
177 char *ED_fsmenu_entry_get_name(struct FSMenuEntry *fsentry)
179 if (fsentry->name[0]) {
180 return fsentry->name;
183 /* Here we abuse fsm_iter->name, keeping first char NULL. */
184 char *name = fsentry->name + 1;
185 size_t name_size = sizeof(fsentry->name) - 1;
187 fsmenu_entry_generate_name(fsentry, name, name_size);
192 void ED_fsmenu_entry_set_name(struct FSMenuEntry *fsentry, const char *name)
194 if (!STREQ(name, fsentry->name)) {
195 char tmp_name[FILE_MAXFILE];
196 size_t tmp_name_size = sizeof(tmp_name);
198 fsmenu_entry_generate_name(fsentry, tmp_name, tmp_name_size);
199 if (!name[0] || STREQ(tmp_name, name)) {
200 /* reset name to default behavior. */
201 fsentry->name[0] = '\0';
204 BLI_strncpy(fsentry->name, name, sizeof(fsentry->name));
207 BLI_make_file_string("/", tmp_name, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
208 fsmenu_write_file(ED_fsmenu_get(), tmp_name);
212 void fsmenu_entry_refresh_valid(struct FSMenuEntry *fsentry)
214 if (fsentry->path && fsentry->path[0]) {
216 /* XXX Special case, always consider those as valid.
217 * Thanks to Windows, which can spend five seconds to perform a mere stat() call on those paths...
220 const char *exceptions[] = {"A:\\", "B:\\", NULL};
221 const size_t exceptions_len[] = {strlen(exceptions[0]), strlen(exceptions[1]), 0};
224 for (i = 0; exceptions[i]; i++) {
225 if (STRCASEEQLEN(fsentry->path, exceptions[i], exceptions_len[i])) {
226 fsentry->valid = true;
231 fsentry->valid = BLI_is_dir(fsentry->path);
234 fsentry->valid = false;
238 short fsmenu_can_save(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
240 FSMenuEntry *fsm_iter;
242 for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx; fsm_iter = fsm_iter->next) {
246 return fsm_iter ? fsm_iter->save : 0;
249 void fsmenu_insert_entry(struct FSMenu *fsmenu, FSMenuCategory category, const char *path, const char *name, FSMenuInsert flag)
251 FSMenuEntry *fsm_prev;
252 FSMenuEntry *fsm_iter;
253 FSMenuEntry *fsm_head;
255 fsm_head = ED_fsmenu_get_category(fsmenu, category);
256 fsm_prev = fsm_head; /* this is odd and not really correct? */
258 for (fsm_iter = fsm_head; fsm_iter; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
259 if (fsm_iter->path) {
260 const int cmp_ret = BLI_path_cmp(path, fsm_iter->path);
262 if (flag & FS_INSERT_FIRST) {
263 if (fsm_iter != fsm_head) {
264 fsm_prev->next = fsm_iter->next;
265 fsm_iter->next = fsm_head;
266 ED_fsmenu_set_category(fsmenu, category, fsm_iter);
271 else if ((flag & FS_INSERT_SORTED) && cmp_ret < 0) {
276 /* if we're bookmarking this, file should come
277 * before the last separator, only automatically added
278 * current dir go after the last sep. */
279 if (flag & FS_INSERT_SAVE) {
285 fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme");
286 fsm_iter->path = BLI_strdup(path);
287 fsm_iter->save = (flag & FS_INSERT_SAVE) != 0;
289 if ((category == FS_CATEGORY_RECENT) && (!name || !name[0])) {
290 /* Special handling when adding new recent entry - check if dir exists in some other categories,
291 * and try to use name from there if so. */
292 FSMenuCategory cats[] = {FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS};
293 int i = ARRAY_SIZE(cats);
296 FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, cats[i]);
298 for (; tfsm; tfsm = tfsm->next) {
299 if (STREQ(tfsm->path, fsm_iter->path)) {
312 if (name && name[0]) {
313 BLI_strncpy(fsm_iter->name, name, sizeof(fsm_iter->name));
316 fsm_iter->name[0] = '\0';
318 fsmenu_entry_refresh_valid(fsm_iter);
321 if (flag & FS_INSERT_FIRST) {
322 fsm_iter->next = fsm_head;
323 ED_fsmenu_set_category(fsmenu, category, fsm_iter);
326 fsm_iter->next = fsm_prev->next;
327 fsm_prev->next = fsm_iter;
331 fsm_iter->next = fsm_head;
332 ED_fsmenu_set_category(fsmenu, category, fsm_iter);
336 void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
338 FSMenuEntry *fsm_prev = NULL;
339 FSMenuEntry *fsm_iter;
340 FSMenuEntry *fsm_head;
342 fsm_head = ED_fsmenu_get_category(fsmenu, category);
344 for (fsm_iter = fsm_head; fsm_iter && idx; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next)
348 /* you should only be able to remove entries that were
349 * not added by default, like windows drives.
350 * also separators (where path == NULL) shouldn't be removed */
351 if (fsm_iter->save && fsm_iter->path) {
353 /* remove fsme from list */
355 fsm_prev->next = fsm_iter->next;
358 fsm_head = fsm_iter->next;
359 ED_fsmenu_set_category(fsmenu, category, fsm_head);
362 MEM_freeN(fsm_iter->path);
368 void fsmenu_write_file(struct FSMenu *fsmenu, const char *filename)
370 FSMenuEntry *fsm_iter = NULL;
371 char fsm_name[FILE_MAX];
374 FILE *fp = BLI_fopen(filename, "w");
377 fprintf(fp, "[Bookmarks]\n");
378 for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter; fsm_iter = fsm_iter->next) {
379 if (fsm_iter->path && fsm_iter->save) {
380 fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
381 if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
382 fprintf(fp, "!%s\n", fsm_iter->name);
384 fprintf(fp, "%s\n", fsm_iter->path);
387 fprintf(fp, "[Recent]\n");
388 for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsm_iter && (nwritten < FSMENU_RECENT_MAX); fsm_iter = fsm_iter->next, ++nwritten) {
389 if (fsm_iter->path && fsm_iter->save) {
390 fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
391 if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
392 fprintf(fp, "!%s\n", fsm_iter->name);
394 fprintf(fp, "%s\n", fsm_iter->path);
400 void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filename)
402 char line[FILE_MAXDIR];
403 char name[FILE_MAXFILE];
404 FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
407 fp = BLI_fopen(filename, "r");
412 while (fgets(line, sizeof(line), fp) != NULL) { /* read a line */
413 if (STREQLEN(line, "[Bookmarks]", 11)) {
414 category = FS_CATEGORY_BOOKMARKS;
416 else if (STREQLEN(line, "[Recent]", 8)) {
417 category = FS_CATEGORY_RECENT;
419 else if (line[0] == '!') {
420 int len = strlen(line);
422 if (line[len - 1] == '\n') {
423 line[len - 1] = '\0';
425 BLI_strncpy(name, line + 1, sizeof(name));
429 int len = strlen(line);
431 if (line[len - 1] == '\n') {
432 line[len - 1] = '\0';
434 /* don't do this because it can be slow on network drives,
435 * having a bookmark from a drive that's ejected or so isn't
438 if (BLI_exists(line))
441 fsmenu_insert_entry(fsmenu, category, line, name, FS_INSERT_SAVE);
444 /* always reset name. */
451 void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
453 char line[FILE_MAXDIR];
455 /* Add the drive names to the listing */
457 wchar_t wline[FILE_MAXDIR];
462 tmp = GetLogicalDrives();
464 for (i = 0; i < 26; i++) {
465 if ((tmp >> i) & 1) {
472 /* Flee from horrible win querying hover floppy drives! */
474 /* Try to get volume label as well... */
475 BLI_strncpy_wchar_from_utf8(wline, tmps, 4);
476 if (GetVolumeInformationW(wline, wline + 4, FILE_MAXDIR - 4, NULL, NULL, NULL, NULL, 0)) {
479 BLI_strncpy_wchar_as_utf8(line, wline + 4, FILE_MAXDIR - 4);
481 label_len = MIN2(strlen(line), FILE_MAXDIR - 6);
482 BLI_snprintf(line + label_len, 6, " (%.2s)", tmps);
488 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, name, FS_INSERT_SORTED);
492 /* Adding Desktop and My Documents */
493 if (read_bookmarks) {
494 SHGetSpecialFolderPathW(0, wline, CSIDL_PERSONAL, 0);
495 BLI_strncpy_wchar_as_utf8(line, wline, FILE_MAXDIR);
496 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
497 SHGetSpecialFolderPathW(0, wline, CSIDL_DESKTOPDIRECTORY, 0);
498 BLI_strncpy_wchar_as_utf8(line, wline, FILE_MAXDIR);
499 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
505 /* Get mounted volumes better method OSX 10.6 and higher, see: */
506 /*https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html*/
508 /* we get all volumes sorted including network and do not relay
509 * on user-defined finder visibility, less confusing */
511 CFURLRef cfURL = NULL;
512 CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
513 CFURLEnumeratorRef volEnum = CFURLEnumeratorCreateForMountedVolumes(NULL, kCFURLEnumeratorSkipInvisibles, NULL);
515 while (result != kCFURLEnumeratorEnd) {
516 char defPath[FILE_MAX];
518 result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, NULL);
519 if (result != kCFURLEnumeratorSuccess)
522 CFURLGetFileSystemRepresentation(cfURL, false, (UInt8 *)defPath, FILE_MAX);
524 /* Add end slash for consistency with other platforms */
525 BLI_add_slash(defPath);
527 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, defPath, NULL, FS_INSERT_SORTED);
532 /* Finally get user favorite places */
533 if (read_bookmarks) {
535 LSSharedFileListRef list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
536 CFArrayRef pathesArray = LSSharedFileListCopySnapshot(list, &seed);
537 CFIndex pathesCount = CFArrayGetCount(pathesArray);
539 for (CFIndex i = 0; i < pathesCount; i++) {
540 LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
542 CFURLRef cfURL = NULL;
543 OSErr err = LSSharedFileListItemResolve(itemRef,
544 kLSSharedFileListNoUserInteraction |
545 kLSSharedFileListDoNotMountVolumes,
547 if (err != noErr || !cfURL)
550 CFStringRef pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
552 if (pathString == NULL || !CFStringGetCString(pathString, line, sizeof(line), kCFStringEncodingUTF8))
555 /* Add end slash for consistency with other platforms */
558 /* Exclude "all my files" as it makes no sense in blender fileselector */
559 /* Exclude "airdrop" if wlan not active as it would show "" ) */
560 if (!strstr(line, "myDocuments.cannedSearch") && (*line != '\0')) {
561 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_LAST);
564 CFRelease(pathString);
568 CFRelease(pathesArray);
575 const char *home = BLI_getenv("HOME");
577 if (read_bookmarks && home) {
578 BLI_snprintf(line, sizeof(line), "%s/", home);
579 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
580 BLI_snprintf(line, sizeof(line), "%s/Desktop/", home);
581 if (BLI_exists(line)) {
582 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, FS_INSERT_SORTED);
589 /* loop over mount points */
594 fp = setmntent(MOUNTED, "r");
596 fprintf(stderr, "could not get a list of mounted filesystems\n");
599 while ((mnt = getmntent(fp))) {
600 if (STRPREFIX(mnt->mnt_dir, "/boot")) {
601 /* Hide share not usable to the user. */
604 else if (!STRPREFIX(mnt->mnt_fsname, "/dev")) {
607 else if (STRPREFIX(mnt->mnt_fsname, "/dev/loop")) {
608 /* The dev/loop* entries are SNAPS used by desktop environment
609 * (Gnome) no need for them to show up in the list. */
613 len = strlen(mnt->mnt_dir);
614 if (len && mnt->mnt_dir[len - 1] != '/') {
615 BLI_snprintf(line, sizeof(line), "%s/", mnt->mnt_dir);
616 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, NULL, FS_INSERT_SORTED);
619 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, NULL, FS_INSERT_SORTED);
624 if (endmntent(fp) == 0) {
625 fprintf(stderr, "could not close the list of mounted filesystems\n");
628 /* Check gvfs shares. */
629 const char * const xdg_runtime_dir = BLI_getenv("XDG_RUNTIME_DIR");
630 if (xdg_runtime_dir != NULL) {
631 struct direntry *dir;
633 BLI_join_dirfile(name, sizeof(name), xdg_runtime_dir, "gvfs/");
634 const uint dir_len = BLI_filelist_dir_contents(name, &dir);
635 for (uint i = 0; i < dir_len; i++) {
636 if ((dir[i].type & S_IFDIR)) {
637 const char *dirname = dir[i].relname;
638 if (dirname[0] != '.') {
639 /* Dir names contain a lot of unwanted text.
640 * Assuming every entry ends with the share name */
641 const char *label = strstr(dirname, "share=");
643 /* Move pointer so "share=" is trimmed off
644 * or use full dirname as label. */
645 const char *label_test = label + 6;
646 label = *label_test ? label_test : dirname;
648 BLI_snprintf(line, sizeof(line), "%s%s/", name, dirname);
649 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, label, FS_INSERT_SORTED);
654 BLI_filelist_free(dir, dir_len);
660 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, "/", NULL, FS_INSERT_SORTED);
668 static void fsmenu_free_category(struct FSMenu *fsmenu, FSMenuCategory category)
670 FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
673 FSMenuEntry *fsm_next = fsm_iter->next;
675 if (fsm_iter->path) {
676 MEM_freeN(fsm_iter->path);
684 void fsmenu_refresh_system_category(struct FSMenu *fsmenu)
686 fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
687 ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM, NULL);
689 fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
690 ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, NULL);
692 /* Add all entries to system category */
693 fsmenu_read_system(fsmenu, true);
696 void fsmenu_refresh_bookmarks_status(struct FSMenu *fsmenu)
698 int categories[] = {FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
701 for (i = sizeof(categories) / sizeof(*categories); i--; ) {
702 FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, categories[i]);
703 for ( ; fsm_iter; fsm_iter = fsm_iter->next) {
704 fsmenu_entry_refresh_valid(fsm_iter);
709 void fsmenu_free(void)
712 fsmenu_free_category(g_fsmenu, FS_CATEGORY_SYSTEM);
713 fsmenu_free_category(g_fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
714 fsmenu_free_category(g_fsmenu, FS_CATEGORY_BOOKMARKS);
715 fsmenu_free_category(g_fsmenu, FS_CATEGORY_RECENT);
722 int fsmenu_get_active_indices(struct FSMenu *fsmenu, enum FSMenuCategory category, const char *dir)
724 FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
727 for (i = 0; fsm_iter; fsm_iter = fsm_iter->next, i++) {
728 if (BLI_path_cmp(dir, fsm_iter->path) == 0) {