Search code examples
macosrust

Registering MacOS.app file to be default when open file extension, pass file clicked on as an argument


I've got an app called FileOpener.app, it has the following contents:

FileOpener.app
  - Contents
    Info.plist
    - MacOS
      - FileOpener
    - Resources

Contents of Info.plist are:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleExecutable</key>
    <string>FileOpener</string>
    <key>CFBundleIdentifier</key>
    <string>com.example.FileOpener</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>FileOpener</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>

    <!-- Document Types -->
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string>Test File</string>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>LSHandlerRank</key>
            <string>Owner</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.example.testfile</string>
            </array>
        </dict>
    </array>

    <key>UTExportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeIdentifier</key>
            <string>com.example.testfile</string>
            <key>UTTypeDescription</key>
            <string>Test File</string>
            <key>UTTypeConformsTo</key>
            <array>
                <string>public.data</string>
            </array>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>testfile</string>
                </array>
                <key>public.mime-type</key>
                <string>application/x-testfile</string>
            </dict>
        </dict>
    </array>


    <key>NSDocumentsFolderUsageDescription</key>
    <string>Description goes here</string>

    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>file</string>
    </array>
</dict>
</plist>

I have some super simple rust code:

fn log_message(message: &str) {
    let mut log_file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("/tmp/fileopener.log")
        .unwrap();
    writeln!(log_file, "{}", message).unwrap();
}

fn main() -> notify::Result<()> {
    let args: Vec<String> = env::args().collect();
    log_message(&format!("Received arguments: {:?}", args));

    if args.len() != 2 {
        eprintln!("Usage: {} <path to .testfile file>", args[0]);
        log_message("Incorrect number of arguments.");
        return Ok(());
    }
}

So any time the binary is called, it logs args out to a tmp file, expecting that a secondary arg of a filepath should be passed into it.

If I run ./FileOpener/Contents/MacOS/FileOpener ./path/to/file.testfile

The args are passed in correctly.

If I try to drag file.testfile onto FileOpener.app, the path to file.testfile is not passed in as an arg, the only arg is the path to FileOpener.app. If I try to double click on file.testfile, FileOpener.app DOES open up, but the path to file.testfile is not passed in as an arg, only arg is the path to FileOpener.app.

What I'm looking to do is register .testfile extension so that when double clicked, it opens FileOpener.app, and passes the path of the clicked on (or dragged on) file as a second argument.

I've also tried code signing, sudo codesign --deep --force --sign - ./FileOpener.app, copying that into /Applications, enabling full-disk access through security, etc. The secondary arg that I need, meaning the path to the file that was clicked, is never getting passed in.


Solution

  • macOS does not open documents by passing them in args. Instead, after the application’s event loop has started, it receives an event for the opened file(s).

    (Note that macOS, unlike Windows and Linux, but like Android and iOS, defaults to having only one process per application; so something like this mechanism is necessary to handle opening files when the application is already running. But, also, macOS has worked this way since it was called “Macintosh System” and there was no Unix and no argv underneath at all.)

    You need to start an event loop and handle the event when it arrives. tao is one library that looks like can do this, but I haven’t used it myself yet.