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;