diff --git a/scripts/startup/bl_ui/space_filebrowser.py b/scripts/startup/bl_ui/space_filebrowser.py index 1a8063da420..e52d380f67e 100644 --- a/scripts/startup/bl_ui/space_filebrowser.py +++ b/scripts/startup/bl_ui/space_filebrowser.py @@ -538,6 +538,7 @@ class FILEBROWSER_MT_context_menu(FileBrowserMenu, Menu): layout.operator("file.next", text="Forward") layout.operator("file.parent", text="Go to Parent") layout.operator("file.refresh", text="Refresh") + layout.menu("FILEBROWSER_MT_operations_menu") layout.separator() diff --git a/source/blender/blenlib/BLI_fileops.h b/source/blender/blenlib/BLI_fileops.h index 87d0cd2b92d..eade4b63dcd 100644 --- a/source/blender/blenlib/BLI_fileops.h +++ b/source/blender/blenlib/BLI_fileops.h @@ -113,6 +113,33 @@ ENUM_OPERATORS(eFileAttributes, FILE_ATTR_HARDLINK); /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name External File Operations + * \{ */ + +typedef enum FileExternalOperation { + FILE_EXTERNAL_OPERATION_OPEN = 0, + FILE_EXTERNAL_OPERATION_FOLDER_OPEN, + /* Following are Windows-only: */ + FILE_EXTERNAL_OPERATION_EDIT, + FILE_EXTERNAL_OPERATION_NEW, + FILE_EXTERNAL_OPERATION_FIND, + FILE_EXTERNAL_OPERATION_SHOW, + FILE_EXTERNAL_OPERATION_PLAY, + FILE_EXTERNAL_OPERATION_BROWSE, + FILE_EXTERNAL_OPERATION_PREVIEW, + FILE_EXTERNAL_OPERATION_PRINT, + FILE_EXTERNAL_OPERATION_INSTALL, + FILE_EXTERNAL_OPERATION_RUNAS, + FILE_EXTERNAL_OPERATION_PROPERTIES, + FILE_EXTERNAL_OPERATION_FOLDER_FIND, + FILE_EXTERNAL_OPERATION_FOLDER_CMD, +} FileExternalOperation; + +bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation); +bool BLI_file_external_operation_execute(const char *filepath, FileExternalOperation operation); + /* -------------------------------------------------------------------- */ /** \name Directories * \{ */ diff --git a/source/blender/blenlib/BLI_winstuff.h b/source/blender/blenlib/BLI_winstuff.h index 34f1b1a68f1..5ed64128a26 100644 --- a/source/blender/blenlib/BLI_winstuff.h +++ b/source/blender/blenlib/BLI_winstuff.h @@ -89,6 +89,11 @@ bool BLI_windows_register_blend_extension(bool background); void BLI_windows_get_default_root_dir(char root_dir[4]); int BLI_windows_get_executable_dir(char *str); +/* ShellExecute Helpers. */ + +bool BLI_windows_external_operation_supported(const char *filepath, const char *operation); +bool BLI_windows_external_operation_execute(const char *filepath, const char *operation); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenlib/intern/fileops.c b/source/blender/blenlib/intern/fileops.c index 35aaeb3ac16..49ca3986a59 100644 --- a/source/blender/blenlib/intern/fileops.c +++ b/source/blender/blenlib/intern/fileops.c @@ -46,6 +46,71 @@ #include "BLI_sys_types.h" /* for intptr_t support */ #include "BLI_utildefines.h" +#ifdef WIN32 +/* Text string used as the "verb" for Windows shell operations. */ +static char *windows_operation_string(FileExternalOperation operation) +{ + switch (operation) { + case FILE_EXTERNAL_OPERATION_OPEN: + return "open"; + case FILE_EXTERNAL_OPERATION_FOLDER_OPEN: + return "open"; + case FILE_EXTERNAL_OPERATION_EDIT: + return "edit"; + case FILE_EXTERNAL_OPERATION_NEW: + return "new"; + case FILE_EXTERNAL_OPERATION_FIND: + return "find"; + case FILE_EXTERNAL_OPERATION_SHOW: + return "show"; + case FILE_EXTERNAL_OPERATION_PLAY: + return "play"; + case FILE_EXTERNAL_OPERATION_BROWSE: + return "browse"; + case FILE_EXTERNAL_OPERATION_PREVIEW: + return "preview"; + case FILE_EXTERNAL_OPERATION_PRINT: + return "print"; + case FILE_EXTERNAL_OPERATION_INSTALL: + return "install"; + case FILE_EXTERNAL_OPERATION_RUNAS: + return "runas"; + case FILE_EXTERNAL_OPERATION_PROPERTIES: + return "properties"; + case FILE_EXTERNAL_OPERATION_FOLDER_FIND: + return "find"; + case FILE_EXTERNAL_OPERATION_FOLDER_CMD: + return "cmd"; + } + BLI_assert_unreachable(); + return ""; +} +#endif + +bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation) +{ +#ifdef WIN32 + char *opstring = windows_operation_string(operation); + return BLI_windows_external_operation_supported(filepath, opstring); +#else + return false; +#endif +} + +bool BLI_file_external_operation_execute(const char *filepath, FileExternalOperation operation) +{ +#ifdef WIN32 + char *opstring = windows_operation_string(operation); + if (BLI_windows_external_operation_supported(filepath, opstring) && + BLI_windows_external_operation_execute(filepath, opstring)) { + return true; + } + return false; +#else + return false; +#endif +} + size_t BLI_file_zstd_from_mem_at_pos( void *buf, size_t len, FILE *file, size_t file_offset, int compression_level) { diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c index 3a574b60ae2..f8c9cc49371 100644 --- a/source/blender/blenlib/intern/winstuff.c +++ b/source/blender/blenlib/intern/winstuff.c @@ -11,10 +11,12 @@ # include # include # include +# include # include "MEM_guardedalloc.h" # define WIN32_SKIP_HKEY_PROTECTION /* Need to use HKEY. */ +# include "BLI_fileops.h" # include "BLI_path_util.h" # include "BLI_string.h" # include "BLI_utildefines.h" @@ -178,6 +180,62 @@ bool BLI_windows_register_blend_extension(const bool background) return true; } +/* Check the registry to see if there is an operation association to a file + * extension. Extension *should almost always contain a dot like ".txt", + * but this does allow querying non - extensions *like "Directory", "Drive", + * "AllProtocols", etc - anything in Classes with a "shell" branch. + */ +static bool BLI_windows_file_operation_is_registered(const char *extension, const char *operation) +{ + HKEY hKey; + HRESULT hr = AssocQueryKey(ASSOCF_INIT_IGNOREUNKNOWN, + ASSOCKEY_SHELLEXECCLASS, + (LPCTSTR)extension, + (LPCTSTR)operation, + &hKey); + if (SUCCEEDED(hr)) { + RegCloseKey(hKey); + return true; + } + return false; +} + +bool BLI_windows_external_operation_supported(const char *filepath, const char *operation) +{ + if (STREQ(operation, "open") || STREQ(operation, "properties")) { + return true; + } + + if (BLI_is_dir(filepath)) { + return BLI_windows_file_operation_is_registered("Directory", operation); + } + + const char *extension = BLI_path_extension(filepath); + return BLI_windows_file_operation_is_registered(extension, operation); +} + +bool BLI_windows_external_operation_execute(const char *filepath, char *operation) +{ + WCHAR wpath[FILE_MAX]; + if (conv_utf_8_to_16(filepath, wpath, ARRAY_SIZE(wpath)) != 0) { + return false; + } + + WCHAR woperation[FILE_MAX]; + if (conv_utf_8_to_16(operation, woperation, ARRAY_SIZE(woperation)) != 0) { + return false; + } + + SHELLEXECUTEINFOW shellinfo = {0}; + shellinfo.cbSize = sizeof(SHELLEXECUTEINFO); + shellinfo.fMask = SEE_MASK_INVOKEIDLIST; + shellinfo.lpVerb = woperation; + shellinfo.lpFile = wpath; + shellinfo.nShow = SW_SHOW; + + return ShellExecuteExW(&shellinfo); +} + void BLI_windows_get_default_root_dir(char root[4]) { char str[MAX_PATH + 1]; diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 6fcc804443f..e11244d0214 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -66,6 +66,10 @@ void FILE_OT_bookmark_move(struct wmOperatorType *ot); void FILE_OT_reset_recent(wmOperatorType *ot); void FILE_OT_hidedot(struct wmOperatorType *ot); void FILE_OT_execute(struct wmOperatorType *ot); + +void FILE_OT_external_operation(struct wmOperatorType *ot); +void file_external_operations_menu_register(void); + /** * Variation of #FILE_OT_execute that accounts for some mouse specific handling. * Otherwise calls the same logic. diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 16c6eba01e5..d22f654210b 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -19,6 +19,8 @@ #include "BKE_report.h" #include "BKE_screen.h" +#include "BLT_translation.h" + #ifdef WIN32 # include "BLI_winstuff.h" #endif @@ -1768,6 +1770,258 @@ bool file_draw_check_exists(SpaceFile *sfile) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name External operations that can performed on files. + * \{ */ + +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_PROPERTIES, + "PROPERTIES", + 0, + "Properties", + "Show OS Properties for this item"}, + {FILE_EXTERNAL_OPERATION_FOLDER_FIND, + "FOLDER_FIND", + 0, + "Find in Folder", + "Search for items in this folder"}, + {FILE_EXTERNAL_OPERATION_FOLDER_CMD, + "CMD", + 0, + "Command Prompt Here", + "Open a command prompt here"}, + {0, NULL, 0, NULL, NULL}}; + +static int file_external_operation_exec(bContext *C, wmOperator *op) +{ + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath"); + char filepath[FILE_MAX]; + RNA_property_string_get(op->ptr, prop, filepath); + + const FileExternalOperation operation = (FileExternalOperation)RNA_enum_get(op->ptr, + "operation"); + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_WAIT); + +#ifdef WIN32 + if (BLI_file_external_operation_execute(filepath, operation)) { + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + return OPERATOR_FINISHED; + } +#else + wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true); + PointerRNA op_props; + WM_operator_properties_create_ptr(&op_props, ot); + RNA_string_set(&op_props, "filepath", filepath); + if (WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props, NULL) == + OPERATOR_FINISHED) { + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + return OPERATOR_FINISHED; + } +#endif + + BKE_reportf( + op->reports, RPT_ERROR, "Failure to perform exernal file operation on \"%s\"", filepath); + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + return OPERATOR_CANCELLED; +} + +static char *file_external_operation_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(ot), + PointerRNA *ptr) +{ + const char *description = ""; + RNA_enum_description(file_external_operation, RNA_enum_get(ptr, "operation"), &description); + return BLI_strdup(description); +} + +void FILE_OT_external_operation(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "External File Operation"; + ot->idname = "FILE_OT_external_operation"; + ot->description = "Perform external operation on a file or folder"; + + /* api callbacks */ + ot->exec = file_external_operation_exec; + ot->get_description = file_external_operation_description; + + /* flags */ + ot->flag = OPTYPE_REGISTER; /* No undo! */ + + /* properties */ + prop = RNA_def_string(ot->srna, "filepath", NULL, FILE_MAX, "File or folder path", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + RNA_def_enum(ot->srna, + "operation", + file_external_operation, + 0, + "Operation", + "Operation to perform on the file or path"); +} + +static void file_os_operations_menu_item(uiLayout *layout, + wmOperatorType *ot, + const char *path, + FileExternalOperation operation) +{ +#ifdef WIN32 + if (!BLI_file_external_operation_supported(path, operation)) { + return; + } +#else + if (!ELEM(operation, FILE_EXTERNAL_OPERATION_OPEN, FILE_EXTERNAL_OPERATION_FOLDER_OPEN)) { + return; + } +#endif + + const char *title = ""; + RNA_enum_name(file_external_operation, operation, &title); + + PointerRNA props_ptr; + uiItemFullO_ptr(layout, ot, title, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &props_ptr); + RNA_string_set(&props_ptr, "filepath", path); + if (operation) { + RNA_enum_set(&props_ptr, "operation", operation); + } +} + +static void file_os_operations_menu_draw(const bContext *C_const, Menu *menu) +{ + bContext *C = (bContext *)C_const; + + /* File browsing only operator (not asset browsing). */ + if (!ED_operator_file_browsing_active(C)) { + return; + } + + SpaceFile *sfile = CTX_wm_space_file(C); + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + if (!sfile || !params) { + return; + } + + char dir[FILE_MAX_LIBEXTRA]; + if (filelist_islibrary(sfile->files, dir, NULL)) { + return; + } + + int numfiles = filelist_files_ensure(sfile->files); + FileDirEntry *fileentry = NULL; + int num_selected = 0; + + for (int i = 0; i < numfiles; i++) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { + fileentry = filelist_file(sfile->files, i); + num_selected++; + } + } + + if (!fileentry || num_selected > 1) { + return; + } + + char path[FILE_MAX_LIBEXTRA]; + filelist_file_get_full_path(sfile->files, fileentry, path); + const char *root = filelist_dir(sfile->files); + + uiLayout *layout = menu->layout; + uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); + wmOperatorType *ot = WM_operatortype_find("FILE_OT_external_operation", true); + + 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_PROPERTIES); + } + else { + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_OPEN); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_EDIT); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_NEW); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FIND); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_SHOW); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PLAY); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_BROWSE); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PREVIEW); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PRINT); + 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_PROPERTIES); + } +} + +static bool file_os_operations_menu_poll(const bContext *C_const, MenuType *UNUSED(mt)) +{ + bContext *C = (bContext *)C_const; + + /* File browsing only operator (not asset browsing). */ + if (!ED_operator_file_browsing_active(C)) { + return false; + } + + SpaceFile *sfile = CTX_wm_space_file(C); + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + + if (sfile && params) { + char dir[FILE_MAX_LIBEXTRA]; + if (filelist_islibrary(sfile->files, dir, NULL)) { + return false; + } + + int numfiles = filelist_files_ensure(sfile->files); + int num_selected = 0; + for (int i = 0; i < numfiles; i++) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { + num_selected++; + } + } + + if (num_selected > 1) { + CTX_wm_operator_poll_msg_set(C, "More than one item is selected"); + } + else if (num_selected < 1) { + CTX_wm_operator_poll_msg_set(C, "No items are selected"); + } + else { + return true; + } + } + + return false; +} + + +void file_external_operations_menu_register(void) +{ + MenuType *mt; + + mt = MEM_callocN(sizeof(MenuType), "spacetype file menu file operations"); + strcpy(mt->idname, "FILEBROWSER_MT_operations_menu"); + strcpy(mt->label, N_("External")); + strcpy(mt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + mt->draw = file_os_operations_menu_draw; + mt->poll = file_os_operations_menu_poll; + WM_menutype_add(mt); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Execute File Window Operator * \{ */ diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index c58a9c67fc6..fe7179f8b8a 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -613,6 +613,7 @@ static void file_operatortypes(void) WM_operatortype_append(FILE_OT_start_filter); WM_operatortype_append(FILE_OT_edit_directory_path); WM_operatortype_append(FILE_OT_view_selected); + WM_operatortype_append(FILE_OT_external_operation); } /* NOTE: do not add .blend file reading on this level */ @@ -1063,6 +1064,7 @@ void ED_spacetype_file(void) art->draw = file_tools_region_draw; BLI_addhead(&st->regiontypes, art); file_tool_props_region_panels_register(art); + file_external_operations_menu_register(); BKE_spacetype_register(st); }