diff --git a/Headers/gnustep/gui/GSServicesManager.h b/Headers/gnustep/gui/GSServicesManager.h index ff7f3a6f8..a07ebcd24 100644 --- a/Headers/gnustep/gui/GSServicesManager.h +++ b/Headers/gnustep/gui/GSServicesManager.h @@ -66,7 +66,7 @@ - (BOOL) application: (NSApplication*)theApp printFile: (NSString*)file; - (void) doService: (NSMenuItem*)item; -- (NSDictionary*) filters; +- (NSArray*) filters; - (BOOL) hasRegisteredTypes: (NSDictionary*)service; - (NSString*) item2title: (NSMenuItem*)item; - (void) loadServices; diff --git a/Source/GSServicesManager.m b/Source/GSServicesManager.m index f7424c434..19e6dfd3b 100644 --- a/Source/GSServicesManager.m +++ b/Source/GSServicesManager.m @@ -548,7 +548,7 @@ static NSString *disabledName = @".GNUstepDisabled"; /** * Return a dictionary of information about registered filter services. */ -- (NSDictionary*) filters +- (NSArray*) filters { return [_allServices objectForKey: @"ByFilter"]; } @@ -1312,8 +1312,16 @@ GSContactApplication(NSString *appName, NSString *port, NSDate *expire) return app; } +/** + *

Given the name of a serviceItem, and some data in a pasteboard + * this function sends the data to the service provider (launching + * another application if necessary) and retrieves the result of + * the service in the pastebaord. + *

+ * Returns YES on success, NO otherwise. + */ BOOL -GSPerformService(NSString *serviceItem, NSPasteboard *pboard, BOOL isFilter) +NSPerformService(NSString *serviceItem, NSPasteboard *pboard) { NSDictionary *service; NSString *port; @@ -1327,26 +1335,14 @@ GSPerformService(NSString *serviceItem, NSPasteboard *pboard, BOOL isFilter) NSString *userData; NSString *error = nil; - if (isFilter == YES) + service = [[manager menuServices] objectForKey: serviceItem]; + if (service == nil) { - service = [[manager filters] objectForKey: serviceItem]; - if (service == nil) - { - NSLog(@"No service matching '%@'", serviceItem); - return NO; /* No matching service. */ - } - } - else - { - service = [[manager menuServices] objectForKey: serviceItem]; - if (service == nil) - { - NSRunAlertPanel(nil, - @"No service matching '%@'", - @"Continue", nil, nil, - serviceItem); - return NO; /* No matching service. */ - } + NSRunAlertPanel(nil, + @"No service matching '%@'", + @"Continue", nil, nil, + serviceItem); + return NO; /* No matching service. */ } port = [service objectForKey: @"NSPortName"]; @@ -1362,14 +1358,7 @@ GSPerformService(NSString *serviceItem, NSPasteboard *pboard, BOOL isFilter) finishBy = [NSDate dateWithTimeIntervalSinceNow: seconds]; appPath = [service objectForKey: @"ServicePath"]; userData = [service objectForKey: @"NSUserData"]; - if (isFilter == YES) - { - message = [service objectForKey: @"NSFilter"]; - } - else - { - message = [service objectForKey: @"NSMessage"]; - } + message = [service objectForKey: @"NSMessage"]; selName = [message stringByAppendingString: @":userData:error:"]; /* @@ -1379,17 +1368,10 @@ GSPerformService(NSString *serviceItem, NSPasteboard *pboard, BOOL isFilter) provider = GSContactApplication(appPath, port, finishBy); if (provider == nil) { - if (isFilter == YES) - { - NSLog(@"Failed to contact service provider for '%@'", serviceItem); - } - else - { - NSRunAlertPanel(nil, - @"Failed to contact service provider for '%@'", - @"Continue", nil, nil, - serviceItem); - } + NSRunAlertPanel(nil, + @"Failed to contact service provider for '%@'", + @"Continue", nil, nil, + serviceItem); return NO; } @@ -1425,38 +1407,16 @@ GSPerformService(NSString *serviceItem, NSPasteboard *pboard, BOOL isFilter) if (error != nil) { - if (isFilter == YES) - { - NSLog(@"Failed to contact service provider for '%@': %@", - serviceItem, error); - } - else - { - NSRunAlertPanel(nil, - @"Failed to contact service provider for '%@': %@", - @"Continue", nil, nil, - serviceItem, error); - } + NSRunAlertPanel(nil, + @"Failed to contact service provider for '%@': %@", + @"Continue", nil, nil, + serviceItem, error); return NO; } return YES; } -/** - *

Given the name of a serviceItem, and some data in a pasteboard - * this function sends the data to the service provider (launching - * another application if necessary) and retrieves the result of - * the service in the pastebaord. - *

- * Returns YES on success, NO otherwise. - */ -BOOL -NSPerformService(NSString *serviceItem, NSPasteboard *pboard) -{ - return GSPerformService(serviceItem, pboard, NO); -} - /** *

Controls whether the item name should be included in the services menu. *

diff --git a/Source/NSPasteboard.m b/Source/NSPasteboard.m index 44747cd45..adbfaf178 100644 --- a/Source/NSPasteboard.m +++ b/Source/NSPasteboard.m @@ -352,6 +352,55 @@ data of a particular type, but the pasteboard only contains data of some other type.

+

+ A filter service definition in the Info.plist file differs from that + of a standard service in that the NSMessage entry is replaced + by an NSFilter entry, the NSMenuItem and + NSKeyEquivalent entries are omitted, and a few other entries + may be added - +

+ + NSFilter + + This is the first part of the message name for the method + which actually implements the filter service ... just like + the NSMessage entry in a normal service. + + NSInputMechanism + + This (optional) entry is a string value specifying an alternative + mechanism for performing the filer service (instead of sending a + message to an application to ask it to do it). + Possible values are - + + NSIdentity + + The data to be filtered is simply placed upon the pasteboard + without any transformation. + + NSMapFile + + The data to be filtered is the name of a file, which is + loaded into memory and placed on the pasteboard without + any transformation.
+ If the data to be filtered contains multiple file names, + only the first is used. +
+ NSUnixStdio + + The data to be filtered is the name of a file, which is + passed as the argument to a unix command-line program, + and the standard output of that program is captured and + placed on the pasteboard. The program is run each time + data is requested, so this is inefficient in comparison + to a filter implemented using the standard method (of + sending a message to a running application).
+ If the data to be filtered contains multiple file names, + only the first is used. +
+
+
+
*/ @@ -402,36 +451,6 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; @end @implementation FilteredPasteboard -/** - * Find a filter specification to use. - */ -+ (NSString*) _filterForType: (NSString*)type fromTypes: (NSArray*)types -{ - NSDictionary *filters = [[GSServicesManager manager] filters]; - NSEnumerator *enumerator = [filters keyEnumerator]; - NSString *key; - - while ((key = [enumerator nextObject]) != nil) - { - NSDictionary *info = [filters objectForKey: key]; - NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"]; - - if ([returnTypes containsObject: type] == YES) - { - NSArray *sendTypes = [info objectForKey: @"NSSendTypes"]; - unsigned i; - - for (i = 0; i < [types count]; i++) - { - if ([sendTypes containsObject: [types objectAtIndex: i]] == YES) - { - return key; - } - } - } - } - return nil; -} /** * Given an array of types, produce an array of all the types we can @@ -440,18 +459,20 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; + (NSArray*) _typesFilterableFrom: (NSArray*)from { NSMutableSet *types = [NSMutableSet setWithCapacity: 8]; - NSDictionary *info = [[GSServicesManager manager] filters]; + NSArray *filters = [[GSServicesManager manager] filters]; + unsigned c = [filters count]; unsigned i; for (i = 0; i < [from count]; i++) { - NSString *type = [from objectAtIndex: i]; - NSEnumerator *enumerator = [info objectEnumerator]; + NSString *type = [from objectAtIndex: i]; + unsigned j; [types addObject: type]; // Always include original type - while ((info = [enumerator nextObject]) != nil) + for (j = 0; j < c; j++) { + NSDictionary *info = [filters objectAtIndex: j]; NSArray *sendTypes = [info objectForKey: @"NSSendTypes"]; if ([sendTypes containsObject: type] == YES) @@ -480,7 +501,6 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; provideDataForType: (NSString*)type { NSDictionary *info; - NSString *filterName = nil; NSString *fromType = nil; NSString *mechanism; @@ -495,24 +515,24 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; info = [NSDictionary dictionaryWithObjectsAndKeys: @"NSIdentity", @"NSInputMechanism", nil]; - filterName = nil; } else { - NSDictionary *filters; - NSEnumerator *enumerator; + NSArray *filters; + unsigned count; + unsigned filterNumber = 0; /* * Locate the filter information needed, including the type we are * converting from and the name of the filter to use. */ filters = [[GSServicesManager manager] filters]; - enumerator = [filters keyEnumerator]; - while (fromType == nil && (filterName = [enumerator nextObject]) != nil) + count = [filters count]; + while (fromType == nil && filterNumber < count) { NSArray *returnTypes; - info = [filters objectForKey: filterName]; + info = [filters objectAtIndex: filterNumber++]; returnTypes = [info objectForKey: @"NSReturnTypes"]; if ([returnTypes containsObject: type] == YES) @@ -548,7 +568,8 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; /* * The data for an NSUnixStdio filter must be one or more filenames */ - if ([fromType isEqualToString: NSFilenamesPboardType] == NO + if ([fromType isEqualToString: NSStringPboardType] == NO + && [fromType isEqualToString: NSFilenamesPboardType] == NO && [fromType hasPrefix: namePrefix] == NO) { [sender setData: [NSData data] forType: type]; @@ -616,8 +637,43 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; */ [sender setData: m forType: type]; } - else if ([mechanism isEqualToString: @"NSIdentity"] == YES - || [mechanism isEqualToString: @"NSMapFile"] == YES) + else if ([mechanism isEqualToString: @"NSMapFile"] == YES) + { + NSString *filename; + NSData *d; + id o; + + if ([fromType isEqualToString: NSStringPboardType] == NO + && [fromType isEqualToString: NSFilenamesPboardType] == NO + && [fromType hasPrefix: namePrefix] == NO) + { + [sender setData: [NSData data] forType: type]; + return; // Not the name of a file to filter. + } + + o = [NSDeserializer deserializePropertyListFromData: d + mutableContainers: NO]; + if ([o isKindOfClass: [NSString class]] == YES) + { + filename = o; + } + else if ([o isKindOfClass: [NSArray class]] == YES + && [o count] > 0 + && [[o objectAtIndex: 0] isKindOfClass: [NSString class]] == YES) + { + filename = [o objectAtIndex: 0]; + } + else + { + [sender setData: [NSData data] forType: type]; + return; // Not the name of a file to filter. + } + + d = [NSData dataWithContentsOfFile: filename]; + + [sender setData: d forType: type]; + } + else if ([mechanism isEqualToString: @"NSIdentity"] == YES) { /* * An 'identity' filter simply places the required data on the @@ -640,8 +696,17 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; } else { - extern BOOL GSPerformService(NSString*, NSPasteboard*, BOOL); NSPasteboard *tmp; + NSString *port; + NSString *timeout; + double seconds; + NSDate *finishBy; + NSString *appPath; + id provider; + NSString *message; + NSString *selName; + NSString *userData; + NSString *error = nil; /* * Put data onto a pasteboard that can be used by the service provider. @@ -663,10 +728,86 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:"; tmp = pboard; // Already in a pasteboard. } + port = [info objectForKey: @"NSPortName"]; + timeout = [info objectForKey: @"NSTimeout"]; + if (timeout && [timeout floatValue] > 100) + { + seconds = [timeout floatValue] / 1000.0; + } + else + { + seconds = 30.0; + } + finishBy = [NSDate dateWithTimeIntervalSinceNow: seconds]; + appPath = [info objectForKey: @"NSExecutable"]; + if ([appPath length] > 0) + { + /* + * A relative path for NSExecutable is relative to the bundle. + */ + if ([appPath isAbsolutePath] == NO) + { + NSString *bundlePath = [info objectForKey: @"ServicePath"]; + + appPath = [bundlePath stringByAppendingPathComponent: appPath]; + } + } + else + { + appPath = [info objectForKey: @"ServicePath"]; + } + userData = [info objectForKey: @"NSUserData"]; + message = [info objectForKey: @"NSFilter"]; + selName = [message stringByAppendingString: @":userData:error:"]; + /* - * Now get the service provider to do the job. + * Locate the service provider ... this will be a proxy to the remote + * object, or a local object (if we provide the service ourself) */ - GSPerformService(filterName, tmp, YES); + provider = GSContactApplication(appPath, port, finishBy); + if (provider == nil) + { + NSLog(@"Failed to contact service provider at '%@' '%@'", + appPath, port); + return; + } + + /* + * If the service provider is a remote object, we can set timeouts on + * the NSConnection so we don't hang waiting for it to reply. + */ + if ([provider isProxy] == YES) + { + NSConnection *connection; + + connection = [(NSDistantObject*)provider connectionForProxy]; + seconds = [finishBy timeIntervalSinceNow]; + [connection setRequestTimeout: seconds]; + [connection setReplyTimeout: seconds]; + } + + /* + * At last, we ask for the service to be performed. + */ + NS_DURING + { + [provider performService: selName + withPasteboard: tmp + userData: userData + error: &error]; + } + NS_HANDLER + { + error = [NSString stringWithFormat: @"%@", [localException reason]]; + } + NS_ENDHANDLER + + if (error != nil) + { + NSLog(@"Failed to contact service provider for '%@': %@", + appPath, error); + return; + } /* * Finally, make it available. @@ -1117,8 +1258,9 @@ static NSMapTable *mimeMap = NULL; + (NSArray*) typesFilterableTo: (NSString*)type { NSMutableSet *types = [NSMutableSet setWithCapacity: 8]; - NSDictionary *info = [[GSServicesManager manager] filters]; - NSEnumerator *enumerator = [info objectEnumerator]; + NSArray *filters = [[GSServicesManager manager] filters]; + NSEnumerator *enumerator = [filters objectEnumerator]; + NSDictionary *info; [types addObject: type]; // Always include original type diff --git a/Tools/make_services.m b/Tools/make_services.m index 94b25cb9e..18554ec47 100644 --- a/Tools/make_services.m +++ b/Tools/make_services.m @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,8 @@ static NSString *cacheName = @".GNUstepServices"; static BOOL verbose = NO; static NSMutableDictionary *serviceMap; -static NSMutableDictionary *filterMap; +static NSMutableArray *filterList; +static NSMutableSet *filterSet; static NSMutableDictionary *printMap; static NSMutableDictionary *spellMap; static NSMutableDictionary *applicationMap; @@ -94,7 +96,8 @@ main(int argc, char** argv, char **env_c) [NSSerializer shouldBeCompact: YES]; serviceMap = [NSMutableDictionary dictionaryWithCapacity: 64]; - filterMap = [NSMutableDictionary dictionaryWithCapacity: 66]; + filterList = [NSMutableArray arrayWithCapacity: 16]; + filterSet = [NSMutableSet setWithCapacity: 64]; printMap = [NSMutableDictionary dictionaryWithCapacity: 8]; spellMap = [NSMutableDictionary dictionaryWithCapacity: 8]; applicationMap = [NSMutableDictionary dictionaryWithCapacity: 64]; @@ -286,7 +289,7 @@ main(int argc, char** argv, char **env_c) fullMap = [NSMutableDictionary dictionaryWithCapacity: 5]; [fullMap setObject: services forKey: @"ByPath"]; [fullMap setObject: serviceMap forKey: @"ByService"]; - [fullMap setObject: filterMap forKey: @"ByFilter"]; + [fullMap setObject: filterList forKey: @"ByFilter"]; [fullMap setObject: printMap forKey: @"ByPrint"]; [fullMap setObject: spellMap forKey: @"BySpell"]; @@ -815,13 +818,13 @@ validateService(NSDictionary *service, NSString *path, unsigned pos) */ if ((obj = [result objectForKey: @"NSFilter"]) != nil) { - NSDictionary *inf; NSString *str; NSArray *snd; NSArray *ret; + BOOL notPresent = NO; str = [result objectForKey: @"NSInputMechanism"]; - if (str) + if (str != nil) { if ([str isEqualToString: @"NSUnixStdio"] == NO && [str isEqualToString: @"NSMapFile"] == NO @@ -831,25 +834,45 @@ validateService(NSDictionary *service, NSString *path, unsigned pos) return nil; } } - else + else if ([result objectForKey: @"NSPortName"] == nil) { - if ([result objectForKey: @"NSPortName"] == nil) - { - NSLog(@"NSServices entry %u NSPortName missing - %@", pos, path); - return nil; - } + NSLog(@"NSServices entry %u NSPortName missing - %@", pos, path); + return nil; } snd = [result objectForKey: @"NSSendTypes"]; ret = [result objectForKey: @"NSReturnTypes"]; - if (snd == nil || ret == nil) + if ([snd count] == 0 || [ret count] == 0) { - NSLog(@"NSServices entry %u types missing - %@", pos, path); + NSLog(@"NSServices entry %u types empty or missing - %@", pos, path); return nil; } + else + { + unsigned i = [snd count]; - inf = [filterMap objectForKey: obj]; - if (inf != nil) + /* + * See if this filter handles any send/return combination + * which is not alreadly present. + */ + while (notPresent == NO && i-- > 0) + { + unsigned j = [ret count]; + + while (notPresent == NO && j-- > 0) + { + str = [NSString stringWithFormat: @"%@==>%@", + [snd objectAtIndex: i], [ret objectAtIndex: j]]; + if ([filterSet member: str] == nil) + { + notPresent = YES; + [filterSet addObject: str]; + [filterList addObject: result]; + } + } + } + } + if (notPresent == NO) { if (verbose) { @@ -857,7 +880,6 @@ validateService(NSDictionary *service, NSString *path, unsigned pos) } return nil; } - [filterMap setObject: result forKey: obj]; } else if ((obj = [result objectForKey: @"NSMessage"]) != nil) { @@ -871,8 +893,8 @@ validateService(NSDictionary *service, NSString *path, unsigned pos) NSLog(@"NSServices entry %u NSPortName missing - %@", pos, path); return nil; } - if ([result objectForKey: @"NSSendTypes"] == nil && - [result objectForKey: @"NSReturnTypes"] == nil) + if ([result objectForKey: @"NSSendTypes"] == nil + && [result objectForKey: @"NSReturnTypes"] == nil) { NSLog(@"NSServices entry %u types missing - %@", pos, path); return nil;