Skip to content

Commit

Permalink
macOS: Use local socket to communicate with finder extension
Browse files Browse the repository at this point in the history
  • Loading branch information
erikjv committed Nov 12, 2021
1 parent df9e6fd commit ee38e6f
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#import <Cocoa/Cocoa.h>
#import <FinderSync/FinderSync.h>

#import "FinderSyncExt-Swift.h"
#import "SyncClientProxy.h"

@interface FinderSync : FIFinderSync <SyncClientProxyDelegate>
Expand All @@ -24,6 +26,10 @@
NSString *_shareMenuTitle;
NSMutableDictionary *_strings;
NSMutableArray *_menuItems;
NSCondition *_menuIsComplete;
}

@property LineProcessorV1 *v1LineProcessor;
@property LocalSocketClient *localSocketClient;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,23 @@ - (instancetype)init
// the sandboxed App Extension needs.
// https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24
NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"];
//NSLog(@"FinderSync serverName %@", serverName);

_syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName];
NSLog(@"FinderSync serverName %@", serverName);
NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:socketApiPrefix];
NSURL *socketPath = [container URLByAppendingPathComponent:@"GUI.socket" isDirectory:false];

_syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self
serverName:serverName];
if (socketPath.path) {
self.v1LineProcessor = [[LineProcessorV1 alloc] initWithDelegate:self];
self.localSocketClient = [[LocalSocketClient alloc] initWithSocketPath:socketPath.path
lineProcessor:self.v1LineProcessor];
[self.localSocketClient start];
} else {
self.localSocketClient = nil;
}
_registeredDirectories = [[NSMutableSet alloc] init];
_strings = [[NSMutableDictionary alloc] init];
_menuIsComplete = [[NSCondition alloc] init];

[_syncClientProxy start];
return self;
Expand All @@ -76,7 +88,11 @@ - (void)requestBadgeIdentifierForURL:(NSURL *)url
}

NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping];
[_syncClientProxy askForIcon:normalizedPath isDirectory:isDir];
if (self.localSocketClient.isConnected) {
[self.localSocketClient askForIcon:normalizedPath isDirectory:isDir];
} else {
[_syncClientProxy askForIcon:normalizedPath isDirectory:isDir];
}
}

#pragma mark - Menu and toolbar item support
Expand Down Expand Up @@ -116,8 +132,17 @@ - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
}];

NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
// calling this IPC calls us back from client with several MENU_ITEM entries and then our askOnSocket returns again
[_syncClientProxy askOnSocket:paths query:@"GET_MENU_ITEMS"];
if (!self.localSocketClient.isConnected) {
// calling this IPC calls us back from client with several MENU_ITEM entries and then our askOnSocket returns again
[_syncClientProxy askOnSocket:paths query:@"GET_MENU_ITEMS"];
} else {
[self.localSocketClient askOnSocket:paths query:@"GET_MENU_ITEMS"];

// The NSConnection based communication is blocking until the answer has come in. The local socket based
// communication uses non-blocking I/O, so we have to wait here until the menu items have delivered
// asynchronously.
[self waitForMenuToArrive];
}

id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"];
if (contextMenuTitle && !onlyRootsSelected) {
Expand Down Expand Up @@ -147,11 +172,22 @@ - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
return nil;
}

- (void)waitForMenuToArrive
{
[self->_menuIsComplete lock];
[self->_menuIsComplete wait];
[self->_menuIsComplete unlock];
}

- (void)subMenuActionClicked:(id)sender {
long idx = [(NSMenuItem*)sender tag];
NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"];
NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
[_syncClientProxy askOnSocket:paths query:command];
if (self.localSocketClient.isConnected) {
[self.localSocketClient askOnSocket:paths query:command];
} else {
[_syncClientProxy askOnSocket:paths query:command];
}
}

#pragma mark - SyncClientProxyDelegate implementation
Expand Down Expand Up @@ -188,10 +224,16 @@ - (void)resetMenuItems
{
_menuItems = [[NSMutableArray alloc] init];
}

- (void)addMenuItem:(NSDictionary *)item {
[_menuItems addObject:item];
}

- (void)menuHasCompleted
{
[self->_menuIsComplete signal];
}

- (void)connectionDidDie
{
[_strings removeAllObjects];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "SyncClientProxy.h"
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// LineProcessorV1.swift
// FinderSyncExt
//
// Created by Erik Verbruggen <[email protected]> on 06-11-21.
//

import Foundation
import OSLog

class LineProcessorV1: NSObject, LineProcessor {
let delegate: SyncClientProxyDelegate

@objc init(withDelegate delegate: SyncClientProxyDelegate) {
self.delegate = delegate
}

private func log(_ str: String, type logType: OSLogType) {
// We cannot use OSLog's Logger class, because a lot of methods are only available in macOS 11.0 or higher.
os_log("%@", type: logType, str)
}

/// Processes a line, where the trailing \n has already been stripped
func process(line: String) {

self.log("Processing line '\(line)'", type: .debug)
let chunks = line.components(separatedBy: ":")
let command = chunks[0]

switch command {
case "STATUS":
let result = chunks[1]
let path = chunks.suffix(from: 2).joined(separator: ":")
DispatchQueue.main.async { self.delegate.setResultForPath(path, result: result) }
case "UPDATE_VIEW":
let path = chunks[1]
DispatchQueue.main.async { self.delegate.reFetchFileNameCache(forPath: path) }
case "REGISTER_PATH":
let path = chunks[1]
DispatchQueue.main.async { self.delegate.registerPath(path) }
case "UNREGISTER_PATH":
let path = chunks[1]
DispatchQueue.main.async { self.delegate.unregisterPath(path) }
case "GET_STRINGS":
// BEGIN and END messages, do nothing.
break
case "STRING":
DispatchQueue.main.async { self.delegate.setString(chunks[1], value: chunks[2]) }
case "GET_MENU_ITEMS":
if chunks[1] == "BEGIN" {
DispatchQueue.main.async { self.delegate.resetMenuItems() }
} else {
// Do NOT run this on the main queue! It might be blocked waiting for the menu to be completed.
delegate.menuHasCompleted()
}
case "MENU_ITEM":
let item = [
"command" : chunks[1],
"flags" : chunks[2],
"text" : chunks[3]
]
DispatchQueue.main.async { self.delegate.addMenuItem(item) }
default:
self.log("Unknown command '\(command)'", type: .error)
}
}
}
Loading

0 comments on commit ee38e6f

Please sign in to comment.