macOS/File Browser: Support external operations #107267

Open
Ankit Meel wants to merge 10 commits from ankitm/blender:ankitm/finderreveal into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
5 changed files with 236 additions and 30 deletions

View File

@ -117,10 +117,13 @@ ENUM_OPERATORS(eFileAttributes, FILE_ATTR_HARDLINK);
/** \name External File Operations
* \{ */
/**
* Not all operations may be supported on all platforms.
ankitm marked this conversation as resolved Outdated

“Not all operations may be supported on all platforms.”

“Not all operations may be supported on all platforms.”
*/
typedef enum FileExternalOperation {
FILE_EXTERNAL_OPERATION_OPEN = 1,
FILE_EXTERNAL_OPERATION_FOLDER_OPEN,
/* Following are Windows-only: */
FILE_EXTERNAL_OPERATION_FILE_REVEAL,
FILE_EXTERNAL_OPERATION_EDIT,
FILE_EXTERNAL_OPERATION_NEW,
FILE_EXTERNAL_OPERATION_FIND,
@ -133,7 +136,7 @@ typedef enum FileExternalOperation {
FILE_EXTERNAL_OPERATION_RUNAS,
FILE_EXTERNAL_OPERATION_PROPERTIES,
FILE_EXTERNAL_OPERATION_FOLDER_FIND,
FILE_EXTERNAL_OPERATION_FOLDER_CMD,
FILE_EXTERNAL_OPERATION_FOLDER_TERMINAL,
} FileExternalOperation;
bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation);
@ -357,6 +360,8 @@ void BLI_file_free_lines(struct LinkNode *lines);
* Giving a path without leading `~` is not an error.
*/
const char *BLI_expand_tilde(const char *path_with_tilde);
bool BLI_apple_external_operation_supported(const char *filepath, FileExternalOperation operation);
bool BLI_apple_external_operation_execute(const char *filepath, FileExternalOperation operation);
#endif
/* This weirdo pops up in two places. */
#if !defined(WIN32)

View File

@ -55,6 +55,8 @@ static char *windows_operation_string(FileExternalOperation operation)
return "open";
case FILE_EXTERNAL_OPERATION_FOLDER_OPEN:
return "open";
case FILE_EXTERNAL_OPERATION_FILE_REVEAL:
return NULL;
case FILE_EXTERNAL_OPERATION_EDIT:
return "edit";
case FILE_EXTERNAL_OPERATION_NEW:
@ -79,11 +81,11 @@ static char *windows_operation_string(FileExternalOperation operation)
return "properties";
case FILE_EXTERNAL_OPERATION_FOLDER_FIND:
return "find";
case FILE_EXTERNAL_OPERATION_FOLDER_CMD:
case FILE_EXTERNAL_OPERATION_FOLDER_TERMINAL:
return "cmd";
}
BLI_assert_unreachable();
return "";
return NULL;
}
#endif
@ -92,6 +94,8 @@ bool BLI_file_external_operation_supported(const char *filepath, FileExternalOpe
#ifdef WIN32
char *opstring = windows_operation_string(operation);
return BLI_windows_external_operation_supported(filepath, opstring);
#elif defined(__APPLE__)
return BLI_apple_external_operation_supported(filepath, operation);
#else
UNUSED_VARS(filepath, operation);
return false;
@ -107,6 +111,8 @@ bool BLI_file_external_operation_execute(const char *filepath, FileExternalOpera
return true;
}
return false;
#elif defined(__APPLE__)
return BLI_apple_external_operation_execute(filepath, operation);
#else
UNUSED_VARS(filepath, operation);
return false;

View File

@ -7,10 +7,16 @@
* macOS specific implementations for storage.c.
*/
#import <AppKit/AppKit.h>
#import <AppKit/NSPasteboard.h>
#import <AppKit/NSWorkspace.h>
#import <Foundation/Foundation.h>
#include <string>
#include <sys/xattr.h>
#include <utility>
#include "BLI_assert.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
@ -212,3 +218,146 @@ bool BLI_change_working_dir(const char *dir)
}
}
}
/**
*
* \param service_invocation: Taken from `/System/Library/CoreServices/pbs -dump`
* \param fileurl: The fileurl to operate on. Starts with `/`.
* \return: If the service call succeeded.
*/
bool perform_service_for_fileurl(NSString *service_invocation, NSString *fileurl)
{
@autoreleasepool {
NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName];
[pasteboard declareTypes:@[ NSPasteboardTypeString ] owner:nil];
[pasteboard setString:fileurl forType:NSPasteboardTypeString];
const bool ok = NSPerformService(service_invocation, pasteboard);
[pasteboard releaseGlobally];
return ok;
}
}
bool external_file_finder_open_default(const char *filepath)
{
@autoreleasepool {
/* `perform_service_for_fileurl(@"Finder/Open"..` shows OS confirmation popup on
* every call, so use a different method.
*/
NSURL *url = [NSURL fileURLWithFileSystemRepresentation:filepath
isDirectory:NO
relativeToURL:nil];
return [[NSWorkspace sharedWorkspace] openURL:url];
}
}
bool external_file_finder_reveal(const char *filepath)
{
@autoreleasepool {
return perform_service_for_fileurl(@"Finder/Reveal", [NSString stringWithUTF8String:filepath]);
}
}
bool external_file_get_info(const char *filepath)
{
@autoreleasepool {
return perform_service_for_fileurl(@"Finder/Show Info",
[NSString stringWithUTF8String:filepath]);
}
}
bool external_file_open_terminal(const char *filepath)
{
@autoreleasepool {
return perform_service_for_fileurl(@"New Terminal at Folder",
[NSString stringWithUTF8String:filepath]);
}
}
using ExternalOperationExecutor = bool (*)(const char *);
ExternalOperationExecutor get_external_operation_executor(const char *filepath,
FileExternalOperation operation)
{
switch (operation) {
case FILE_EXTERNAL_OPERATION_OPEN: {
return external_file_finder_open_default;
}
case FILE_EXTERNAL_OPERATION_FOLDER_OPEN: {
return external_file_finder_reveal;
}
case FILE_EXTERNAL_OPERATION_FILE_REVEAL: {
return external_file_finder_reveal;
}
case FILE_EXTERNAL_OPERATION_EDIT: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_NEW: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_FIND: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_SHOW: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_PLAY: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_BROWSE: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_PREVIEW: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_PRINT: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_INSTALL: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_RUNAS: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_PROPERTIES: {
return external_file_get_info;
}
case FILE_EXTERNAL_OPERATION_FOLDER_FIND: {
return nullptr;
}
case FILE_EXTERNAL_OPERATION_FOLDER_TERMINAL: {
return external_file_open_terminal;
}
}
BLI_assert_unreachable();
return nullptr;
}
bool BLI_apple_external_operation_execute(const char *filepath, FileExternalOperation operation)
{
@autoreleasepool {
const ExternalOperationExecutor executor = get_external_operation_executor(filepath,
operation);
if (executor == nullptr) {
return false;
}
return executor(filepath);
}
}
bool BLI_apple_external_operation_supported(const char *filepath, FileExternalOperation operation)
{
@autoreleasepool {
const ExternalOperationExecutor executor = get_external_operation_executor(filepath,
operation);
if (!executor) {
return false;
}
if (operation == FILE_EXTERNAL_OPERATION_FILE_REVEAL && !BLI_is_file(filepath)) {
return false;
}
if (operation == FILE_EXTERNAL_OPERATION_FOLDER_OPEN && !BLI_is_dir(filepath)) {
return false;
}
return true;
}
}

View File

@ -203,6 +203,9 @@ static bool BLI_windows_file_operation_is_registered(const char *extension, cons
bool BLI_windows_external_operation_supported(const char *filepath, const char *operation)
{
if (!operation) {
return false;
}
if (STREQ(operation, "open") || STREQ(operation, "properties")) {
return true;
}

View File

@ -5,11 +5,11 @@
* \ingroup spfile
*/
#include "BLI_utildefines.h"
#include "BLI_blenlib.h"
#include "BLI_fileops.h"
#include "BLI_linklist.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BKE_appdir.h"
#include "BKE_blendfile.h"
@ -1775,33 +1775,75 @@ bool file_draw_check_exists(SpaceFile *sfile)
* \{ */
static const EnumPropertyItem file_external_operation[] = {
{FILE_EXTERNAL_OPERATION_OPEN, "OPEN", 0, "Open", "Open the file"},
{FILE_EXTERNAL_OPERATION_FOLDER_OPEN, "FOLDER_OPEN", 0, "Open Folder", "Open the folder"},
{FILE_EXTERNAL_OPERATION_EDIT, "EDIT", 0, "Edit", "Edit the file"},
{FILE_EXTERNAL_OPERATION_NEW, "NEW", 0, "New", "Create a new file of this type"},
{FILE_EXTERNAL_OPERATION_FIND, "FIND", 0, "Find File", "Search for files of this type"},
{FILE_EXTERNAL_OPERATION_SHOW, "SHOW", 0, "Show", "Show this file"},
{FILE_EXTERNAL_OPERATION_PLAY, "PLAY", 0, "Play", "Play this file"},
{FILE_EXTERNAL_OPERATION_BROWSE, "BROWSE", 0, "Browse", "Browse this file"},
{FILE_EXTERNAL_OPERATION_PREVIEW, "PREVIEW", 0, "Preview", "Preview this file"},
{FILE_EXTERNAL_OPERATION_PRINT, "PRINT", 0, "Print", "Print this file"},
{FILE_EXTERNAL_OPERATION_INSTALL, "INSTALL", 0, "Install", "Install this file"},
{FILE_EXTERNAL_OPERATION_RUNAS, "RUNAS", 0, "Run As User", "Run as specific user"},
{FILE_EXTERNAL_OPERATION_OPEN,
"OPEN",
ICON_NONE,
"Open",
"Open this file in its default application"},
ankitm marked this conversation as resolved Outdated

“Open this file…” (like the other descriptions).

“Open **this** file…” (like the other descriptions).
{FILE_EXTERNAL_OPERATION_FOLDER_OPEN,

How about "Display this file in a new Finder window"?

The existing descriptions here aren't great either, noted that in #104531.

How about `"Display this file in a new Finder window"`? The existing descriptions here aren't great either, noted that in #104531.
"FOLDER_OPEN",
ICON_NONE,
#ifdef __APPLE__
"Reveal in Finder",
"Reveal this folder in a new Finder window"
#else
"Open in File Explorer",
"Open this folder in a system file browser"
ankitm marked this conversation as resolved Outdated

“in a system file browser“

“in **a** system file browser“
#endif
},
{FILE_EXTERNAL_OPERATION_FILE_REVEAL,

"Display this directory in a new Finder window"? (Is it the selected directory or the current directory?)

`"Display this directory in a new Finder window"`? (Is it the selected directory or the current directory?)

“…this file…”

“…this file…”
"FILE_REVEAL",
ICON_NONE,
#ifdef __APPLE__
"Reveal in Finder",
"Reveal this file in a new Finder window"
#else
"Open in File Explorer",
"Open this file in a system file browser"
#endif
},
{FILE_EXTERNAL_OPERATION_EDIT, "EDIT", ICON_NONE, "Edit", "Edit this file"},
{FILE_EXTERNAL_OPERATION_NEW, "NEW", ICON_NONE, "New", "Create a new file of this type"},
{FILE_EXTERNAL_OPERATION_FIND,
"FIND",
ICON_NONE,
"Find File",
"Search for files of this type"},
{FILE_EXTERNAL_OPERATION_SHOW, "SHOW", ICON_NONE, "Show", "Show this file"},
{FILE_EXTERNAL_OPERATION_PLAY, "PLAY", ICON_NONE, "Play", "Play this file"},
{FILE_EXTERNAL_OPERATION_BROWSE, "BROWSE", ICON_NONE, "Browse", "Browse this file"},
{FILE_EXTERNAL_OPERATION_PREVIEW, "PREVIEW", ICON_NONE, "Preview", "Preview this file"},
{FILE_EXTERNAL_OPERATION_PRINT, "PRINT", ICON_NONE, "Print", "Print this file"},
{FILE_EXTERNAL_OPERATION_INSTALL, "INSTALL", ICON_NONE, "Install", "Install this file"},
{FILE_EXTERNAL_OPERATION_RUNAS, "RUNAS", ICON_NONE, "Run As User", "Run as specific user"},
{FILE_EXTERNAL_OPERATION_PROPERTIES,

Use "file" instead of "item".

Use "file" instead of "item".
"PROPERTIES",
0,
ICON_NONE,
#ifdef __APPLE__
"Get Info",
"Open the Get Info window for this file"
#else
"Properties",
"Show OS Properties for this item"},
"Show OS Properties for this file"
#endif
},
{FILE_EXTERNAL_OPERATION_FOLDER_FIND,
"FOLDER_FIND",
0,
ICON_NONE,
"Find in Folder",
"Search for items in this folder"},
{FILE_EXTERNAL_OPERATION_FOLDER_CMD,
"Search for files in this folder"},
{FILE_EXTERNAL_OPERATION_FOLDER_TERMINAL,
"CMD",
0,
ICON_NONE,
#ifdef __APPLE__
"Open in Terminal",
"Open a terminal window with this folder/ file's parent folder as the current working "
"directory"
#else
"Command Prompt Here",
"Open a command prompt here"},
"Open a command prompt here"
#endif
},
{0, NULL, 0, NULL, NULL}};
static int file_external_operation_exec(bContext *C, wmOperator *op)
@ -1812,7 +1854,7 @@ static int file_external_operation_exec(bContext *C, wmOperator *op)
WM_cursor_set(CTX_wm_window(C), WM_CURSOR_WAIT);
#ifdef WIN32
#if defined(WIN32) || defined(__APPLE__)
const FileExternalOperation operation = (FileExternalOperation)RNA_enum_get(op->ptr,
"operation");
if (BLI_file_external_operation_execute(filepath, operation)) {
@ -1871,7 +1913,7 @@ void FILE_OT_external_operation(wmOperatorType *ot)
file_external_operation,
FILE_EXTERNAL_OPERATION_OPEN,
"Operation",
"Operation to perform on the file or path");
"Operation to perform on the file or folder");
}
static void file_os_operations_menu_item(uiLayout *layout,
@ -1879,7 +1921,7 @@ static void file_os_operations_menu_item(uiLayout *layout,
const char *path,
FileExternalOperation operation)
{
#ifdef WIN32
#if defined(WIN32) || defined(__APPLE__)
if (!BLI_file_external_operation_supported(path, operation)) {
return;
}
@ -1945,7 +1987,7 @@ static void file_os_operations_menu_draw(const bContext *C_const, Menu *menu)
if (fileentry->typeflag & FILE_TYPE_DIR) {
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FOLDER_OPEN);
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FOLDER_CMD);
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FOLDER_TERMINAL);
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PROPERTIES);
}
else {
@ -1961,7 +2003,8 @@ static void file_os_operations_menu_draw(const bContext *C_const, Menu *menu)
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_INSTALL);
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_RUNAS);
file_os_operations_menu_item(layout, ot, root, FILE_EXTERNAL_OPERATION_FOLDER_OPEN);
file_os_operations_menu_item(layout, ot, root, FILE_EXTERNAL_OPERATION_FOLDER_CMD);
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FILE_REVEAL);
file_os_operations_menu_item(layout, ot, root, FILE_EXTERNAL_OPERATION_FOLDER_TERMINAL);
file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PROPERTIES);
}
}