Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,22 +221,15 @@ - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
subMenuItem.submenu = subMenu;
subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"];

// There is an annoying bug in macOS (at least 10.13.3), it does not use/copy over the representedObject of a menu item
// So we have to use tag instead.
int idx = 0;

for (NSArray* item in menuItems) {
NSMenuItem *actionItem = [subMenu addItemWithTitle:[item valueForKey:@"text"]
action:@selector(subMenuActionClicked:)
keyEquivalent:@""];
// Carry the command on the item itself rather than an index into _menuItems. The
// menu outlives the array it was built from: a later GET_MENU_ITEMS round trip can
// reset and repopulate _menuItems before the user clicks, after which an index
// either selects the wrong command or is out of bounds and throws — crashing the
// extension. identifier is a plain string property from
// NSUserInterfaceItemIdentification, so unlike representedObject it survives
// whatever copying Finder does to the menu.
actionItem.identifier = [item valueForKey:@"command"];

// Finder copies the menu into its own process to display it but identifier does not survive it
// Use tag to pass the command: an index into _menuItems that subMenuActionClicked: resolves locally
[actionItem setTag:idx];
[actionItem setTarget:self];
NSString *flags = [item valueForKey:@"flags"]; // e.g. "d"

Expand All @@ -257,7 +250,14 @@ - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
}

- (void)subMenuActionClicked:(id)sender {
NSString *command = [(NSMenuItem *)sender identifier];
const NSInteger tag = [(NSMenuItem *)sender tag];
NSString *command = nil;
[self->_menuIsComplete lock];
if (tag >= 0 && tag < (NSInteger)_menuItems.count) {
command = _menuItems[tag][@"command"];
os_log_debug(_log, "command for menu: %{public}@", command);
}
[self->_menuIsComplete unlock];

if (command.length == 0) {
os_log_error(_log, "Menu item clicked with no command attached; ignoring");
Expand Down
60 changes: 22 additions & 38 deletions src/gui/macOS/findersyncservice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@

} // namespace OCC

// SocketApi handlers require a listener and write output via sendMessage()
namespace {
class ResponseCapturingListener : public OCC::SocketListener
{
public:
mutable QStringList messages;

explicit ResponseCapturingListener()
: SocketListener(nullptr)
{
}

void sendMessage(const QString &message, bool = false) const override
{
messages.append(message);
}
};
} // anonymous namespace

/**
* @brief Objective-C delegate that implements the FinderSyncAppProtocol.
*/
Expand Down Expand Up @@ -237,21 +256,19 @@ - (void)executeMenuCommand:(NSString *)command
// Build the command method name (e.g., "SHARE" -> "command_SHARE")
const QString methodName = QStringLiteral("command_%1").arg(qCommand);

// Create a null listener since we're not sending responses back via socket
// Commands will execute but won't send socket responses
OCC::SocketListener *nullListener = nullptr;

// Join all paths with record separator (same encoding as the socket protocol)
// to preserve multi-selection for commands like MAKE_AVAILABLE_LOCALLY
const QString argument = qPaths.join(QChar(0x1e));

ResponseCapturingListener captureListener;

// Try to invoke the command using Qt's meta-object system
bool invoked = QMetaObject::invokeMethod(
socketApi,
methodName.toUtf8().constData(),
Qt::DirectConnection,
Q_ARG(QString, argument),
Q_ARG(OCC::SocketListener*, nullListener)
Q_ARG(OCC::SocketListener*, &captureListener)
);

if (invoked) {
Expand Down Expand Up @@ -352,39 +369,6 @@ - (void)performHandshakeWithReply:(void(^)(void))completionHandler
return {true, statusString};
}

/**
* @brief SocketListener subclass that captures command responses in-memory.
*
* SocketApi command methods (e.g., command_GET_STRINGS, command_GET_MENU_ITEMS)
* produce their output by calling listener->sendMessage(). This design dates
* back to the UNIX-socket transport where responses were written directly to
* the socket.
*
* With the XPC transport there is no socket, yet we still need to invoke these
* commands and collect their output. ResponseCapturingListener acts as an
* in-memory adapter: it receives the sendMessage() calls and stores them in a
* QStringList that the caller can parse and convert into XPC reply values.
*
* This is production code, not a test helper.
*/
namespace {
class ResponseCapturingListener : public SocketListener
{
public:
mutable QStringList messages;

explicit ResponseCapturingListener()
: SocketListener(nullptr)
{
}

void sendMessage(const QString &message, bool = false) const override
{
messages.append(message);
}
};
} // anonymous namespace

QMap<QString, QString> FinderSyncService::getLocalizedStrings() const
{
QMap<QString, QString> result;
Expand Down
Loading