diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift
index 9f042e6d27393..6fb8c5d3ce8c1 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift
@@ -58,6 +58,40 @@ extension FileProviderExtension: NSFileProviderCustomAction {
onItemsWithIdentifiers: itemIdentifiers,
completionHandler: completionHandler
)
+ case "com.nextcloud.desktopclient.FileProviderExt.OpenInBrowserAction":
+ guard let itemIdentifier = itemIdentifiers.first else {
+ logger.error("Failed to get first item identifier for open in browser action.")
+ completionHandler(NSFileProviderError(.noSuchItem))
+ return Progress()
+ }
+
+ guard let dbManager else {
+ logger.error("Cannot open in browser due to database manager not being available.", [.item: itemIdentifier])
+ completionHandler(NSFileProviderError(.cannotSynchronize))
+ return Progress()
+ }
+
+ Task {
+ guard let userVisibleURL = try await manager?.getUserVisibleURL(for: itemIdentifier) else {
+ logger.error("Failed to get user-visible URL for item.", [.item: itemIdentifier])
+ completionHandler(NSFileProviderError(.noSuchItem))
+ return
+ }
+
+ guard let metadata = dbManager.itemMetadata(itemIdentifier) else {
+ logger.error("Failed to get metadata for item.", [.item: itemIdentifier])
+ completionHandler(NSFileProviderError(.cannotSynchronize))
+ return
+ }
+
+ let path = userVisibleURL.path
+ let domainIdentifier = domain.identifier.rawValue
+ logger.info("Telling main app to open item in browser.", [.item: path, .domain: domainIdentifier])
+ app?.openInBrowserForPath(path, remoteItemPath: metadata.path, withDomainIdentifier: domainIdentifier)
+ completionHandler(nil)
+ }
+
+ return Progress()
default:
logger.error("Unsupported action: \(actionIdentifier.rawValue)")
completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError))
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist
index ceb30173c3211..c5db5c047580d 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist
@@ -45,6 +45,14 @@
NSExtensionFileProviderActionName
File actions
+
+ NSExtensionFileProviderActionActivationRule
+ SUBQUERY ( fileproviderItems, $fileproviderItem, !($fileproviderItem.contentType.identifier UTI-CONFORMS-TO "public.folder") ).@count > 0
+ NSExtensionFileProviderActionIdentifier
+ com.nextcloud.desktopclient.FileProviderExt.OpenInBrowserAction
+ NSExtensionFileProviderActionName
+ Open in browser
+
NSExtensionFileProviderDocumentGroup
$(DEVELOPMENT_TEAM).$(OC_APPLICATION_REV_DOMAIN)
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h
index 9e8f599b12be8..bc7f68cf126e2 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h
@@ -23,6 +23,18 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)presentFileActions:(NSString *)fileId path:(NSString *)path remoteItemPath:(NSString *)remoteItemPath withDomainIdentifier:(NSString *)domainIdentifier;
+/**
+ * @brief The file provider extension can request opening an item in the browser.
+ *
+ * The main app decides whether to use direct editing (`EDIT`) or private link (`OPEN_PRIVATE_LINK`)
+ * depending on the direct editor availability for the given local file.
+ *
+ * @param path The local and absolute path for the item to open.
+ * @param remoteItemPath The server-side path of the item (reserved for future fallback logic).
+ * @param domainIdentifier The file provider domain identifier for the account that manages this file.
+ */
+- (void)openInBrowserForPath:(NSString *)path remoteItemPath:(NSString *)remoteItemPath withDomainIdentifier:(NSString *)domainIdentifier;
+
/**
* @brief The file provider extension can report its synchronization status as a string constant value to the main app through this method.
* @param status The synchronization status string.
@@ -34,4 +46,3 @@ NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
#endif /* AppProtocol_h */
-
diff --git a/src/gui/macOS/fileproviderservice.mm b/src/gui/macOS/fileproviderservice.mm
index 0b2c97e9171cd..17bdf87640719 100644
--- a/src/gui/macOS/fileproviderservice.mm
+++ b/src/gui/macOS/fileproviderservice.mm
@@ -14,6 +14,8 @@
#include
#include "accountmanager.h"
+#include "folderman.h"
+#include "socketapi/socketapi.h"
namespace OCC {
@@ -107,6 +109,37 @@ - (void)reportSyncStatus:(NSString *)status forDomainIdentifier:(NSString *)doma
Q_ARG(OCC::SyncResult::Status, syncState));
}
+- (void)openInBrowserForPath:(NSString *)path remoteItemPath:(NSString *)remoteItemPath withDomainIdentifier:(NSString *)domainIdentifier
+{
+ Q_UNUSED(remoteItemPath)
+ Q_UNUSED(domainIdentifier)
+
+ const auto localPath = QString::fromNSString(path);
+
+ qCDebug(OCC::lcMacFileProviderService) << "Received open in browser request for path:" << localPath;
+
+ if (!_service) {
+ qCWarning(OCC::lcMacFileProviderService) << "No service available to open item in browser";
+ return;
+ }
+
+ auto *const socketApi = OCC::FolderMan::instance()->socketApi();
+ if (!socketApi) {
+ qCWarning(OCC::lcMacFileProviderService) << "SocketApi is unavailable, cannot open item in browser:" << localPath;
+ return;
+ }
+
+ // Execute on the socket API object thread.
+ QMetaObject::invokeMethod(socketApi, [socketApi, localPath]() {
+ OCC::SocketListener *nullListener = nullptr;
+ if (socketApi->getDirectEditorForLocalFile(localPath)) {
+ socketApi->command_EDIT(localPath, nullListener);
+ } else {
+ socketApi->command_OPEN_PRIVATE_LINK(localPath, nullListener);
+ }
+ }, Qt::QueuedConnection);
+}
+
@end
namespace OCC {
@@ -166,4 +199,3 @@ - (void)reportSyncStatus:(NSString *)status forDomainIdentifier:(NSString *)doma
} // namespace Mac
} // namespace OCC
-