2

How can I get file path to Desktop directory as a string on macOS. I need it to be done in pure C or with some C-level framework.

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143
  • Check this out: http://stackoverflow.com/questions/5319976/how-to-find-desktop-path-using-c-language – Derick Alangi Sep 22 '16 at 22:37
  • It's `/Users/username/Desktop` – Barmar Sep 22 '16 at 22:51
  • If you can be bothered with an Objective-C file in your project, you can get it with `[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]`. You can then copy the result to a C string. Otherwise, you can concatenate `getenv("HOME")` and `"Desktop"` with `asprintf` fairly easily, but if `$HOME` isn't defined, you're out of luck. – zneak Sep 22 '16 at 22:54
  • 1
    For the current user you can use [`realpath`](http://man7.org/linux/man-pages/man3/realpath.3.html) with `"~/Desktop"`... this works in pure C (`realpath` is a unix thing, you'll need to include the headers for it). – Myst Sep 22 '16 at 23:27
  • @d3r1ck - the link refers to a Windows related question and doesn't answer how to get the information using macOS. – Myst Sep 22 '16 at 23:33

3 Answers3

1

Here's a short function, which works on more Unix based systems than just macOS and returns the current user's desktop folder:

#include <limits.h>
#include <stdlib.h>
/**
 * Returns the path to the current user's desktop.
 */
char *path2desktop(void) {
  static char real_public_path[PATH_MAX + 1] = {0};
  if (real_public_path[0])
    return real_public_path;
  strcpy(real_public_path, getenv("HOME"));
  memcpy(real_public_path + strlen(real_public_path), "/Desktop", 8);
  return real_public_path;
}

The path will only be computed once.

If the function is called more than once, the old result will be returned (not thread-safe, unless the first call was protected).

Myst
  • 18,516
  • 2
  • 45
  • 67
0

If you insist on using only C (why?), then your only choice is to use deprecated APIs:

#include <limits.h>
#include <CoreServices/CoreServices.h>

...

FSRef fsref;
UInt8 path[PATH_MAX];

if (FSFindFolder(kUserDomain, kDesktopFolderType, kDontCreateFolder, &fsref) == noErr &&
    FSRefMakePath(&fsref, path, sizeof(path)) == noErr)
{
    // Make use of path
}

If you need a CFURL rather than a path, you can use CFURLCreateFromFSRef() rather than FSRefMakePath().

Actually, while researching this, I found an API I hadn't known about. Apparently, you can use this, which apparently comes from Cocoa but uses only C types:

#include <limits.h>
#include <NSSystemDirectories.h>

char path[PATH_MAX];
NSSearchPathEnumerationState state = NSStartSearchPathEnumeration(NSDesktopDirectory, NSUserDomainMask);
while (state = NSGetNextSearchPathEnumeration(state, path))
{
    // Handle path
}

The form of the API is that it may return multiple results (one on each iteration of the loop), but you should get only one for the specific use here. In that case, you can change the while to and if.

Note that, with this API, returned paths for directories in the user domain may use "~" rather than the absolute path to the user's home directory. You'll have to resolve that yourself.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Ken, I loved the information in the answer, but I think you're ignoring the easy way... since MacOS has BSD in it's core, there's really no need to go beyond POSIX. – Myst Sep 23 '16 at 02:57
  • @Myst: There's no guarantee that the user's desktop is at `$HOME/Desktop`. It might be named something else and might be in a different location. That's why these APIs exist. See [Apple's docs](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Strings/Articles/ManipulatingPaths.html#//apple_ref/doc/uid/20000152-SW2): "Note that you should typically use the function NSSearchPathForDirectoriesInDomains to locate standard directories for the current user [instead of concatenating hard-coded directories with the home directory]". – Ken Thomases Sep 23 '16 at 05:37
  • Ken, I'm not sure you're right about this one... there is no way I know of that the `Desktop` folder can be moved away from the root of the home folder. It is always the `~/Desktop` folder... People have tried moving it and - to the best of my knowledge - creating aliases is as close as people got.... It's possible to move the `$HOME` root, but not the structure within `$HOME`. As for the Apple Docs: "Directory paths returned in the user domain will contain "~" to refer to the user's directory" ... so `NSGetNextSearchPathEnumeration` is incomplete. – Myst Sep 23 '16 at 05:57
  • I prefer C API, as I have very fragmented product, sometimes for GUI I am using Cocoa apps, but joining C and Cocoa is additional work needed. – Michał Ziobro Sep 23 '16 at 10:56
  • This second approach NSSearchPathEnumerationState seems to be now also deprecated from macOS Sierra I think there is equivalent sysdir_search_path_enumeration_state, but the problem here is that this both returns just ~/Desktop and not the absolute path. So I cannot use it to create CFURLRef with CFURLCreateWithString() and cannot save image in such URL :( – Michał Ziobro Sep 23 '16 at 11:54
  • how can I make URL from ~/Desktop returned by NSSearchPathEnumerationState? – Michał Ziobro Sep 23 '16 at 12:05
  • @MichałZiobro: Again, this is simple in Cocoa but a nuisance in C. You can get the user's home directory using `confstr(_CS_DARWIN_USER_DIR, …)`. – Ken Thomases Sep 23 '16 at 15:05
  • @Myst: It may not be possible for a user to change it, but it may be possible for Apple to do so. They already did that with sandboxing with respect to a variety of other standard directory locations. – Ken Thomases Sep 23 '16 at 15:06
  • Ken, I read through your answer and the docs you linked to and I'm thankful for your insistence and free proving me mostly wrong... however, having the system call return a relative path requires, in my understanding, an extra step. Again, thank you for your answer. – Myst Sep 23 '16 at 15:12
0

I ended with usage of Objective-C in such way:

//
//  main.m
//  search_path_for_dir
//
//  Created by Michal Ziobro on 23/09/2016.
//  Copyright © 2016 Michal Ziobro. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    if(argc != 3)
        return 1;

    @autoreleasepool {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(atoi(argv[1]), atoi(argv[2]), YES);
        NSString *path = [paths objectAtIndex:0];
        [path writeToFile:@"/dev/stdout" atomically:NO encoding:NSUTF8StringEncoding error:nil];
    }
    return 0;
}

And than from command line I can execute this program in that way:

./search_path_for_dir 12 1 

12 - NSDesktopDirectory

1 - NSUserDomainMask

I am using script in C that executes this program from command line and retrieves its output.

Here's C example calling this mini Cocoa App:

CFStringRef FSGetFilePath(int directory, int domainMask) {

    CFStringRef scheme = CFSTR("file:///");
    CFStringRef absolutePath = FSGetAbsolutePath(directory, domainMask);

    CFMutableStringRef filePath = CFStringCreateMutable(NULL, 0);
    if (filePath) {

        CFStringAppend(filePath, scheme);
        CFStringAppend(filePath, absolutePath);

    }

    CFRelease(scheme);
    CFRelease(absolutePath);

    return filePath;
}

CFStringRef FSGetAbsolutePath(int directory, int domainMask) {

    char path_cmd[BUF_SIZE];
    sprintf(path_cmd, "./tools/search_path_for_dir %d %d", directory, domainMask);
    char *path = exec_cmd(path_cmd);

    return CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
}
Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143