diff --git a/ChangeLog b/ChangeLog index ea66d1fc6..8c0d92d6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +Tue Dec 1 21:00:00 1998 Richard Frith-Macdonald + + * NSApplication.m: Moved services code out to GNUServiceManager.m + * NSPasteboard.m: Moved services code out to GNUServiceManager.m + * NSWorkspace.m: Moved services code out to GNUServiceManager.m + * GNUServicesManager.m: All services stuff in here - now full + implementation of services functions. + * GNUServicesManager.h: Header for services manager class. + * GNUmakefile: Added services manager stuff + * Tools/set_show_service.m: New tool to enable/disable services. + * Tools/GNUmakefile: Added set_show_service.m + * Tools/dummy.m: More dummy backend functions. + * Tools/make_services.m: Update for new directory structure. + Tue Dec 1 1998 Felipe A. Rodriguez * NSWindow.m rename windowWithNumber to _windowWithTag per backend diff --git a/Headers/gnustep/gui/GNUServicesManager.h b/Headers/gnustep/gui/GNUServicesManager.h new file mode 100644 index 000000000..0256afe4f --- /dev/null +++ b/Headers/gnustep/gui/GNUServicesManager.h @@ -0,0 +1,80 @@ +/* + GNUServicesManager.h + + Copyright (C) 1998 Free Software Foundation, Inc. + + Author: Richard Frith-Macdonald + Date: Novemeber 1998 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; see the file COPYING.LIB. + If not, write to the Free Software Foundation, + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _GNUstep_H_GNUServicesManager +#define _GNUstep_H_GNUServicesManager + +@class NSApplication; +@class NSArray; +@class NSCell; +@class NSMenu; +@class NSMutableArray; +@class NSMutableDictionary; +@class NSMutableSet; +@class NSString; +@class NSTimer; + +@interface GNUServicesManager : NSObject +{ + NSApplication *application; + NSMenu *servicesMenu; + NSMutableArray *languages; + NSMutableSet *returnInfo; + NSMutableDictionary *combinations; + NSMutableDictionary *title2info; + NSArray *menuTitles; + NSString *disabledPath; + NSString *servicesPath; + NSDate *disabledStamp; + NSDate *servicesStamp; + NSMutableSet *allDisabled; + NSMutableDictionary *allServices; + NSTimer *timer; +} ++ (GNUServicesManager*) newWithApplication: (NSApplication*)app; ++ (GNUServicesManager*) manager; +- (void) checkServices; +- (void) doService: (NSCell*)item; +- (BOOL) hasRegisteredTypes: (NSDictionary*)service; +- (NSString*) item2title: (NSCell*)item; +- (void) loadServices; +- (NSDictionary*) menuServices; +- (void) rebuildServices; +- (void) rebuildServicesMenu; +- (void) registerAsServiceProvider; +- (void) registerSendTypes: (NSArray *)sendTypes + returnTypes: (NSArray *)returnTypes; +- (NSMenu *) servicesMenu; +- (id) servicesProvider; +- (void) setServicesMenu: (NSMenu *)anObject; +- (void) setServicesProvider: (id)anObject; +- (int) setShowsServicesMenuItem: (NSString*)item to: (BOOL)enable; +- (BOOL) validateMenuItem: (NSCell*)item; +- (void) updateServicesMenu; +@end + +#endif + diff --git a/Source/GNUServicesManager.m b/Source/GNUServicesManager.m new file mode 100644 index 000000000..335dbffd3 --- /dev/null +++ b/Source/GNUServicesManager.m @@ -0,0 +1,1073 @@ +/* + GNUServicesManager.m + + Copyright (C) 1998 Free Software Foundation, Inc. + + Author: Richard Frith-Macdonald + Date: Novemeber 1998 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; see the file COPYING.LIB. + If not, write to the Free Software Foundation, + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define stringify_it(X) #X +#define prog_path(X,Y) \ + stringify_it(X) "/Tools/" GNUSTEP_TARGET_DIR "/" LIBRARY_COMBO + +/* + * The GNUListener class is for talking to other applications. + * It is a proxy with some dangerous methods implemented in a + * harmless manner to reduce the chances of a malicious app + * messing with us. + */ +@interface GNUListener : NSObject ++ (id) connectionBecameInvalid: (NSNotification*)notification; ++ (GNUListener*) listener; ++ (id) servicesProvider; ++ (void) setServicesProvider: (id)anObject; +- (Class) class; +- (void) dealloc; +- (void) release; +- (id) retain; +- (id) self; +@end + +static NSConnection *listenerConnection = nil; +static GNUListener *listener = nil; +static id servicesProvider = nil; + +void +NSUnregisterServicesProvider(NSString *name) +{ + if (listenerConnection) + { + /* + * Ensure there is no previous listener and nothing else using + * the given port name. + */ + [[NSPortNameServer defaultPortNameServer] removePortForName: name]; + [NSNotificationCenter removeObserver: [GNUListener class] + name: NSConnectionDidDieNotification + object: listenerConnection]; + [listenerConnection release]; + listenerConnection = nil; + } + ASSIGN(servicesProvider, nil); +} + +void +NSRegisterServicesProvider(id provider, NSString *name) +{ + if (listenerConnection) + { + /* + * Ensure there is no previous listener and nothing else using + * the given port name. + */ + [[NSPortNameServer defaultPortNameServer] removePortForName: name]; + [NSNotificationCenter removeObserver: [GNUListener class] + name: NSConnectionDidDieNotification + object: listenerConnection]; + [listenerConnection release]; + listenerConnection = nil; + } + if (name && provider) + { + listenerConnection = [NSConnection newRegisteringAtName: name + withRootObject: [GNUListener listener]]; + if (listenerConnection) + { + [listenerConnection retain]; + [NotificationDispatcher + addObserver: [GNUListener class] + selector: @selector(connectionBecameInvalid:) + name: NSConnectionDidDieNotification + object: listenerConnection]; + } + } + ASSIGN(servicesProvider, provider); +} + +/* + * The GNUListener class exists as a proxy to forward messages to + * service provider objects. It implements very few methods and + * those that it does implement are generally designed to defeat + * any attack by a malicious program. + */ +@implementation GNUListener + ++ (id) connectionBecameInvalid: (NSNotification*)notification +{ + NSAssert(listenerConnection==[notification object], + NSInternalInconsistencyException); + + [NSNotificationCenter removeObserver: self + name: NSConnectionDidDieNotification + object: listenerConnection]; + [listenerConnection release]; + listenerConnection = nil; + return self; +} + ++ (GNUListener*) listener +{ + if (listener == nil) + { + listener = (id)NSAllocateObject(self, 0, NSDefaultMallocZone()); + } + return listener; +} + ++ (id) servicesProvider +{ + return servicesProvider; +} + ++ (void) setServicesProvider: (id)anObject +{ + NSString *appName; + + if (servicesProvider != anObject) + { + appName = [[[NSProcessInfo processInfo] processName] lastPathComponent]; + NSRegisterServicesProvider(anObject, appName); + } +} + +- (Class) class +{ + return 0; +} + +- (void) dealloc +{ +} + +- forward: (SEL)aSel :(arglist_t)frame +{ + NSString *selName = NSStringFromSelector(aSel); + + /* + * If the selector matches the correct form for a services request, + * send the message to the services provider - otherwise raise an + * exception to say the method is not implemented. + */ + if ([selName hasSuffix: @":userData:error:"]) + return [servicesProvider performv: aSel :frame]; + + [NSException raise: NSGenericException + format: @"method %s not implemented", sel_get_name(aSel)]; + return nil; +} + +- (void) release +{ +} + +- (id) retain +{ + return self; +} + +- (id) self +{ + return self; +} +@end /* GNUListener */ + + + +@implementation GNUServicesManager + +static GNUServicesManager *manager = nil; +static NSString *servicesName = @".GNUstepServices"; +static NSString *disabledName = @".GNUstepDisabled"; + +/* + * Create a new listener for this application. + * Uses NSRegisterServicesProvider() to register itsself as a service + * provider with the applications name so we can handle incoming + * service requests. + */ ++ (GNUServicesManager*) newWithApplication: (NSApplication*)app +{ + NSDictionary *env; + NSString *str; + NSString *path; + + if (manager) + { + if (manager->application == nil) + manager->application = app; + return manager; + } + + manager = [GNUServicesManager alloc]; + + env = [[NSProcessInfo processInfo] environment]; + str = [env objectForKey: @"GNUSTEP_USER_ROOT"]; + if (str == nil) + str = [NSString stringWithFormat: @"%@/GNUstep/Services", + NSHomeDirectory()]; + path = [str stringByAppendingPathComponent: servicesName]; + manager->servicesPath = [path retain]; + path = [str stringByAppendingPathComponent: disabledName]; + manager->disabledPath = [path retain]; + /* + * Don't retain application object - that would reate a cycle. + */ + manager->application = app; + manager->returnInfo = [[NSMutableSet alloc] initWithCapacity: 16]; + manager->combinations = [[NSMutableDictionary alloc] initWithCapacity: 16]; + /* + * Check for changes to the services cache every thirty seconds. + */ + manager->timer = + [NSTimer scheduledTimerWithTimeInterval: 30.0 + target: manager + selector: @selector(loadServices) + userInfo: nil + repeats: YES]; + + [manager loadServices]; + return manager; +} + ++ (GNUServicesManager*) manager +{ + if (manager == nil) + { + [self newWithApplication: nil]; + } + return manager; +} + +- (void) checkServices +{ + system(prog_path(GNUSTEP_INSTALL_PREFIX, "/make_services")); + + [self loadServices]; +} + +- (void) dealloc +{ + NSString *appName; + + appName = [[[NSProcessInfo processInfo] processName] lastPathComponent]; + [timer invalidate]; + NSUnregisterServicesProvider(appName); + [languages release]; + [returnInfo release]; + [combinations release]; + [title2info release]; + [menuTitles release]; + [servicesMenu release]; + [disabledPath release]; + [servicesPath release]; + [disabledStamp release]; + [servicesStamp release]; + [allDisabled release]; + [allServices release]; + [super dealloc]; +} + +- (void) doService: (NSCell*)item +{ + NSString *title = [self item2title: item]; + NSDictionary *info = [title2info objectForKey: title]; + NSArray *sendTypes = [info objectForKey: @"NSSendTypes"]; + NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"]; + unsigned i, j; + unsigned es = [sendTypes count]; + unsigned er = [returnTypes count]; + NSWindow *resp = [[application keyWindow] firstResponder]; + id obj = nil; + + for (i = 0; i <= es; i++) + { + NSString *sendType; + + sendType = (i < es) ? [sendTypes objectAtIndex: i] : nil; + + for (j = 0; j <= er; j++) + { + NSString *returnType; + + returnType = (j < er) ? [returnTypes objectAtIndex: j] : nil; + + obj = [resp validRequestorForSendType: sendType + returnType: returnType]; + if (obj != nil) + { + NSPasteboard *pb; + + pb = [NSPasteboard pasteboardWithUniqueName]; + if ([obj writeSelectionToPasteboard: pb + types: sendTypes] == NO) + { + NSLog(@"object failed to write to pasteboard\n"); + } + else if (NSPerformService(title, pb) == NO) + { + NSLog(@"Failed to perform %@\n", title); + } + else if ([obj readSelectionFromPasteboard: pb] == NO) + { + NSLog(@"object failed to read from pasteboard\n"); + } + return; + } + } + } +} + +- (BOOL) hasRegisteredTypes: (NSDictionary*)service +{ + NSArray *sendTypes = [service objectForKey: @"NSSendTypes"]; + NSArray *returnTypes = [service objectForKey: @"NSReturnTypes"]; + NSString *type; + unsigned i; + + /* + * We know that both sendTypes and returnTypes can't be nil since + * make_services has validated the service entry for us. + */ + if (sendTypes == nil || [sendTypes count] == 0) + { + for (i = 0; i < [returnTypes count]; i++) + { + type = [returnTypes objectAtIndex: i]; + if ([returnInfo member: type] != nil) + { + return YES; + } + } + } + else if (returnTypes == nil || [returnTypes count] == 0) + { + for (i = 0; i < [sendTypes count]; i++) + { + type = [sendTypes objectAtIndex: i]; + if ([combinations objectForKey: type] != nil) + { + return YES; + } + } + } + else + { + for (i = 0; i < [sendTypes count]; i++) + { + NSSet *rset; + + type = [sendTypes objectAtIndex: i]; + rset = [combinations objectForKey: type]; + if (rset != nil) + { + unsigned j; + + for (j = 0; j < [returnTypes count]; j++) + { + type = [returnTypes objectAtIndex: j]; + if ([rset member: type] != nil) + { + return YES; + } + } + } + } + } + return NO; +} + +/* + * Use tag in menu cell to identify slot in menu titles array that + * contains the full title of the service. + * Return nil if this is not one of our service menu cells. + */ +- (NSString*) item2title: (NSCell*)item +{ + unsigned pos; + + if ([item target] != self) + return nil; + pos = [item tag]; + if (pos > [menuTitles count]) + return nil; + return [menuTitles objectAtIndex: pos]; +} + +- (void) loadServices +{ + NSFileManager *mgr = [NSFileManager defaultManager]; + NSDate *stamp = [NSDate date]; + BOOL changed = NO; + + if ([mgr fileExistsAtPath: disabledPath]) + { + NSDictionary *attr; + NSDate *mod; + + attr = [mgr fileAttributesAtPath: disabledPath + traverseLink: YES]; + mod = [attr objectForKey: NSFileModificationDate]; + if (disabledStamp == nil || [disabledStamp laterDate: mod] == mod) + { + NSData *data; + id plist = nil; + + data = [NSData dataWithContentsOfFile: disabledPath]; + if (data) + { + plist = [NSDeserializer deserializePropertyListFromData: data + mutableContainers: NO]; + if (plist) + { + NSMutableSet *s; + stamp = mod; + changed = YES; + s = (NSMutableSet*)[NSMutableSet setWithArray: plist]; + ASSIGN(allDisabled, s); + } + } + } + } + /* Track most recent version of file loaded or last time we checked */ + ASSIGN(disabledStamp, stamp); + + stamp = [NSDate date]; + if ([mgr fileExistsAtPath: servicesPath]) + { + NSDictionary *attr; + NSDate *mod; + + attr = [mgr fileAttributesAtPath: servicesPath + traverseLink: YES]; + mod = [attr objectForKey: NSFileModificationDate]; + if (servicesStamp == nil || [servicesStamp laterDate: mod] == mod) + { + NSData *data; + id plist = nil; + + data = [NSData dataWithContentsOfFile: servicesPath]; + if (data) + { + plist = [NSDeserializer deserializePropertyListFromData: data + mutableContainers: YES]; + if (plist) + { + stamp = mod; + ASSIGN(allServices, plist); + changed = YES; + } + } + } + } + /* Track most recent version of file loaded or last time we checked */ + ASSIGN(servicesStamp, stamp); + if (changed) + { + [self rebuildServices]; + } +} + +- (NSDictionary*) menuServices +{ + if (allServices == nil) + { + [self loadServices]; + } + return title2info; +} + +- (void) rebuildServices +{ + NSDictionary *services; + NSUserDefaults *defs; + NSMutableArray *newLang; + NSMutableSet *alreadyFound; + NSMutableDictionary *newServices; + unsigned pos; + + if (allServices == nil) + return; + + defs = [NSUserDefaults standardUserDefaults]; + newLang = [[[defs arrayForKey: @"Languages"] mutableCopy] autorelease]; + if (newLang == nil) + { + newLang = [NSMutableArray arrayWithCapacity: 1]; + } + if ([newLang containsObject: @"default"] == NO) + { + [newLang addObject: @"default"]; + } + ASSIGN(languages, newLang); + + services = [allServices objectForKey: @"ByService"]; + + newServices = [NSMutableDictionary dictionaryWithCapacity: 16]; + alreadyFound = [NSMutableSet setWithCapacity: 16]; + + /* + * Build dictionary of services we can use. + * 1. make sure we make dictionary keyed on preferred menu item language + * 2. don't include entries for services already examined. + * 3. don't include entries for menu items specifically disabled. + * 4. don't include entries for which we have no registered types. + */ + for (pos = 0; pos < [languages count]; pos++) + { + NSDictionary *byLanguage; + + byLanguage = [services objectForKey: [languages objectAtIndex: pos]]; + if (byLanguage != nil) + { + NSEnumerator *enumerator = [byLanguage keyEnumerator]; + NSString *menuItem; + + while ((menuItem = [enumerator nextObject]) != nil) + { + NSDictionary *service = [byLanguage objectForKey: menuItem]; + + if ([alreadyFound member: service] != nil) + continue; + + [alreadyFound addObject: service]; + + /* See if this service item is disabled. */ + if ([allDisabled member: menuItem] != nil) + continue; + + if ([self hasRegisteredTypes: service]) + [newServices setObject: service forKey: menuItem]; + } + } + } + if ([newServices isEqual: title2info] == NO) + { + NSArray *titles; + + ASSIGN(title2info, newServices); + titles = [title2info allKeys]; + titles = [titles sortedArrayUsingSelector: @selector(compare:)]; + ASSIGN(menuTitles, titles); + [self rebuildServicesMenu]; + } +} + +- (void) rebuildServicesMenu +{ + if (servicesMenu) + { + NSArray *itemArray; + NSMutableSet *keyEquivalents; + unsigned pos; + unsigned loc0; + unsigned loc1; + SEL sel = @selector(doService:); + NSMenu *submenu = nil; + + itemArray = [[servicesMenu itemArray] retain]; + pos = [itemArray count]; + while (pos > 0) + { + [servicesMenu removeItem: [itemArray objectAtIndex: --pos]]; + } + [itemArray release]; + + keyEquivalents = [NSMutableSet setWithCapacity: 4]; + for (loc0 = pos = 0; pos < [menuTitles count]; pos++) + { + NSString *title = [menuTitles objectAtIndex: pos]; + NSString *equiv = @""; + NSDictionary *info = [title2info objectForKey: title]; + NSDictionary *titles; + NSDictionary *equivs; + NSRange r; + unsigned lang; + id item; + + /* + * Find the key equivalent corresponding to this menu title + * in the service definition. + */ + titles = [info objectForKey: @"NSMenuItem"]; + equivs = [info objectForKey: @"NSKeyEquivalent"]; + for (lang = 0; lang < [languages count]; lang++) + { + NSString *language = [languages objectAtIndex: lang]; + NSString *t = [titles objectForKey: language]; + + if ([t isEqual: title]) + { + equiv = [equivs objectForKey: language]; + } + } + + /* + * Make a note that we are using the key equivalent, or + * set to nil if we have already used it in this menu. + */ + if (equiv) + { + if ([keyEquivalents member: equiv] == nil) + { + [keyEquivalents addObject: equiv]; + } + else + { + equiv = @""; + } + } + + r = [title rangeOfString: @"/"]; + if (r.length > 0) + { + NSString *subtitle = [title substringFromIndex: r.location+1]; + NSString *parentTitle = [title substringToIndex: r.location]; + NSMenu *menu; + + item = [servicesMenu itemWithTitle: parentTitle]; + if (item == nil) + { + loc1 = 0; + item = [servicesMenu insertItemWithTitle: parentTitle + action: 0 + keyEquivalent: @"" + atIndex: loc0++]; + menu = [[NSMenu alloc] initWithTitle: parentTitle]; + [servicesMenu setSubmenu: submenu + forItem: item]; + } + else + { + menu = (NSMenu*)[item target]; + } + if (menu != submenu) + { + [submenu sizeToFit]; + submenu = menu; + } + item = [submenu insertItemWithTitle: subtitle + action: sel + keyEquivalent: equiv + atIndex: loc1++]; + [item setTarget: self]; + [item setTag: pos]; + } + else + { + item = [servicesMenu insertItemWithTitle: title + action: sel + keyEquivalent: equiv + atIndex: loc0++]; + [item setTarget: self]; + [item setTag: pos]; + } + } + [submenu sizeToFit]; + [servicesMenu sizeToFit]; + [servicesMenu update]; + } +} + +/* + * Set up connection to listen for incoming service requests. + */ +- (void) registerAsServiceProvider +{ + NSString *appName; + + appName = [[[NSProcessInfo processInfo] processName] lastPathComponent]; + NSRegisterServicesProvider(self, appName); +} + +/* + * Register send and return types that an object can handle - we keep + * a note of all the possible combinations - + * 'returnInfo' is a set of all the return types that can be handled + * without a send. + * 'combinations' is a dictionary of all send types, with the assciated + * values being sets of possible return types. + */ +- (void) registerSendTypes: (NSArray *)sendTypes + returnTypes: (NSArray *)returnTypes +{ + BOOL didChange = NO; + unsigned i; + + for (i = 0; i < [sendTypes count]; i++) + { + NSString *sendType = [sendTypes objectAtIndex: i]; + NSMutableSet *returnSet = [combinations objectForKey: sendType]; + + if (returnSet == nil) + { + returnSet = [NSMutableSet setWithCapacity: [returnTypes count]]; + [combinations setObject: returnSet forKey: sendType]; + [returnSet addObjectsFromArray: returnTypes]; + didChange = YES; + } + else + { + unsigned count = [returnSet count]; + + [returnSet addObjectsFromArray: returnTypes]; + if ([returnSet count] != count) + { + didChange = YES; + } + } + } + + i = [returnInfo count]; + [returnInfo addObjectsFromArray: returnTypes]; + if ([returnInfo count] != i) + { + didChange = YES; + } + + if (didChange) + { + [self rebuildServices]; + } +} + +- (NSMenu*) servicesMenu +{ + return servicesMenu; +} + +- (id) servicesProvider +{ + return [GNUListener servicesProvider]; +} + +- (void) setServicesMenu: (NSMenu*)aMenu +{ + ASSIGN(servicesMenu, aMenu); + [self rebuildServicesMenu]; +} + +- (void) setServicesProvider: (id)anObject +{ + [GNUListener setServicesProvider: anObject]; +} + +- (int) setShowsServicesMenuItem: (NSString*)item to: (BOOL)enable +{ + NSData *d; + + [self loadServices]; + if (allDisabled == nil) + allDisabled = [[NSMutableSet setWithCapacity: 1] retain]; + if (enable) + [allDisabled removeObject: item]; + else + [allDisabled addObject: item]; + d = [NSSerializer serializePropertyList: [allDisabled allObjects]]; + if ([d writeToFile: disabledPath atomically: YES] == YES) + return 0; + return -1; +} + +- (BOOL) validateMenuItem: (NSCell*)item +{ + NSString *title = [self item2title: item]; + NSDictionary *info = [title2info objectForKey: title]; + NSArray *sendTypes = [info objectForKey: @"NSSendTypes"]; + NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"]; + unsigned i, j; + unsigned es = [sendTypes count]; + unsigned er = [returnTypes count]; + NSWindow *resp = [[application keyWindow] firstResponder]; + + /* + * If the menu item is not in our map, it must be the cell containing + * a sub-menu - so we see if any cell in the submenu is valid. + */ + if (title == nil) + { + NSMenu *sub = [item target]; + + if (sub && [sub isKindOfClass: [NSMenu class]]) + { + NSArray *a = [sub itemArray]; + + for (i = 0; i < [a count]; i++) + { + if ([self validateMenuItem: [a objectAtIndex: i]] == YES) + { + return YES; + } + } + } + return NO; + } + + /* + * The cell corresponds to one of our services - so we check to see if + * there is anything that can deal with it. + */ + if (es == 0) + { + if (er == 0) + { + if ([resp validRequestorForSendType: nil + returnType: nil] != nil) + return YES; + } + else + { + for (j = 0; j < er; j++) + { + NSString *returnType; + + returnType = [returnTypes objectAtIndex: j]; + if ([resp validRequestorForSendType: nil + returnType: returnType] != nil) + return YES; + } + } + } + else + { + for (i = 0; i < es; i++) + { + NSString *sendType; + + sendType = [sendTypes objectAtIndex: i]; + + if (er == 0) + { + if ([resp validRequestorForSendType: sendType + returnType: nil] != nil) + return YES; + } + else + { + for (j = 0; j < er; j++) + { + NSString *returnType; + + returnType = [returnTypes objectAtIndex: j]; + if ([resp validRequestorForSendType: sendType + returnType: returnType] != nil) + return YES; + } + } + } + } + return NO; +} + +- (void) updateServicesMenu +{ + if (servicesMenu && [[application mainMenu] autoenablesItems]) + { + NSMenuMatrix *menuCells; + NSArray *a; + unsigned i; + NSMenu *mainMenu = [application mainMenu]; + BOOL found = NO; + + a = [mainMenu itemArray]; + for (i = 0; i < [a count]; i++) + if ([[a objectAtIndex: i] target] == servicesMenu) + found = YES; + if (found == NO) + { + NSLog(@"Services menu not in main menu!\n"); + return; + } + + menuCells = [servicesMenu menuCells]; + a = [menuCells itemArray]; + + for (i = 0; i < [a count]; i++) + { + NSCell *cell = [a objectAtIndex: i]; + BOOL wasEnabled = [cell isEnabled]; + BOOL shouldBeEnabled = [self validateMenuItem: cell]; + + if (wasEnabled != shouldBeEnabled) + { + [cell setEnabled: shouldBeEnabled]; + [menuCells setNeedsDisplayInRect: [menuCells cellFrameAtRow: i]]; + } + } + /* FIXME - only doing this here 'cos auto-display doesn't work */ + if ([menuCells needsDisplay]) + [menuCells display]; + } +} + +@end /* GNUServicesManager */ + + +BOOL +NSPerformService(NSString *serviceItem, NSPasteboard *pboard) +{ + NSDictionary *service; + NSString *port; + NSString *timeout; + double seconds; + NSDate *finishBy; + NSString *appPath; + id provider; + NSConnection *connection; + NSString *message; + NSString *selName; + SEL msgSel; + NSString *userData; + IMP msgImp; + NSString *error = nil; + + service = [[manager menuServices] objectForKey: serviceItem]; + if (service == nil) + return NO; /* No matching service. */ + + port = [service objectForKey: @"NSPortName"]; + timeout = [service objectForKey: @"NSTimeout"]; + if (timeout && [timeout floatValue] > 100) + { + seconds = [timeout floatValue] / 1000.0; + } + else + { + seconds = 30.0; + } + finishBy = [NSDate dateWithTimeIntervalSinceNow: seconds]; + appPath = [service objectForKey: @"ServicePath"]; + userData = [service objectForKey: @"NSUserData"]; + message = [service objectForKey: @"NSMessage"]; + selName = [message stringByAppendingString: @":userData:error:"]; + msgSel = NSSelectorFromString(selName); + + /* + * If there is no selector - we need to generate one with the + * appropriate types. + */ + if (msgSel == 0) + { + NSMethodSignature *sig; + const char *name; + const char *type; + + sig = [NSMethodSignature signatureWithObjCTypes: "v@:@@^@"]; + type = [sig methodType]; + name = [selName cString]; + msgSel = sel_register_typed_name(name, type); + } + + provider = [NSConnection rootProxyForConnectionWithRegisteredName: port + host: @""]; + if (provider == nil) + { + if ([[NSWorkspace sharedWorkspace] launchApplication: appPath] == NO) + { + return NO; /* Unable to launch. */ + } + + provider = [NSConnection rootProxyForConnectionWithRegisteredName: port + host: @""]; + while (provider == nil && [finishBy timeIntervalSinceNow] > 1.0) + { + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + NSDate *next; + + [NSTimer scheduledTimerWithTimeInterval: 1.0 + invocation: nil + repeats: NO]; + next = [NSDate dateWithTimeIntervalSinceNow: 5.0]; + [loop runUntilDate: next]; + provider = [NSConnection + rootProxyForConnectionWithRegisteredName: port + host: @""]; + } + } + + if (provider == nil) + { + return NO; /* Unable to contact. */ + } + connection = [(NSDistantObject*)provider connectionForProxy]; + seconds = [finishBy timeIntervalSinceNow]; + [connection setRequestTimeout: seconds]; + [connection setReplyTimeout: seconds]; + + msgImp = get_imp(fastClass(provider), msgSel); + NS_DURING + { + (*msgImp)(provider, msgSel, pboard, userData, &error); + } + NS_HANDLER + { + [NSException raise: NSPasteboardCommunicationException + format: @"%s", [[localException reason] cString]]; + } + NS_ENDHANDLER + + if (error != nil) + { + NSLog(error); + return NO; + } + + return YES; +} + +int +NSSetShowsServicesMenuItem(NSString *name, BOOL enabled) +{ + + return [[GNUServicesManager manager] setShowsServicesMenuItem: name + to: enabled]; +} + diff --git a/Source/GNUmakefile b/Source/GNUmakefile index ddd079c91..ec476830d 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -106,6 +106,7 @@ NSView.m \ NSWindow.m \ NSWorkspace.m \ TrackingRectangle.m \ +GNUServicesManager.m \ PSMatrix.m \ tiff.m \ externs.m @@ -199,6 +200,7 @@ AppKit/NSWindow.h \ AppKit/NSWorkspace.h \ AppKit/TrackingRectangle.h \ AppKit/PSMatrix.h \ +AppKit/GNUServiceManager.h \ AppKit/nsimage-tiff.h \ -include GNUmakefile.preamble diff --git a/Source/NSApplication.m b/Source/NSApplication.m index 129529019..2c2d46922 100644 --- a/Source/NSApplication.m +++ b/Source/NSApplication.m @@ -64,641 +64,7 @@ [a release]; \ a = b; - -//***************************************************************************** -// -// ApplicationListener -// -//***************************************************************************** - -extern NSDictionary *GSAllServicesDictionary(); - -/* - * Local class for handling incoming service requests etc. - */ -@interface ApplicationListener : NSObject -{ - id servicesProvider; - NSApplication *application; - NSMenu *servicesMenu; - NSMutableArray *languages; - NSMutableSet *returnInfo; - NSMutableDictionary *combinations; - NSMutableDictionary *title2info; - NSArray *menuTitles; - BOOL isRegistered; -} -+ (ApplicationListener*) newWithApplication: (NSApplication*)app; -- (void) doService: (NSCell*)item; -- (BOOL) hasRegisteredTypes: (NSDictionary*)service; -- (NSString*) item2title: (NSCell*)item; -- (void) rebuildServices; -- (void) rebuildServicesMenu; -- (void) registerAsServiceProvider; -- (void) registerSendTypes: (NSArray *)sendTypes - returnTypes: (NSArray *)returnTypes; -- (NSMenu *) servicesMenu; -- (id) servicesProvider; -- (void) setServicesMenu: (NSMenu *)anObject; -- (void) setServicesProvider: (id)anObject; -- (BOOL) validateMenuItem: (NSCell*)item; -- (void) updateServicesMenu; -@end - -@implementation ApplicationListener -/* - * Create a new listener for this application. - * Uses NSRegisterServicesProvider() to register itsself as a service - * provider with the applications name so we can handle incoming - * service requests. - */ -+ (ApplicationListener*) newWithApplication: (NSApplication*)app -{ - ApplicationListener *listener = [ApplicationListener alloc]; - - /* - * Don't retain application object - that would reate a cycle. - */ - listener->application = app; - listener->returnInfo = [[NSMutableSet alloc] initWithCapacity: 16]; - listener->combinations = [[NSMutableDictionary alloc] initWithCapacity: 16]; - return listener; -} - -- (void) doService: (NSCell*)item -{ - NSString *title = [self item2title: item]; - NSDictionary *info = [title2info objectForKey: title]; - NSArray *sendTypes = [info objectForKey: @"NSSendTypes"]; - NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"]; - unsigned i, j; - unsigned es = [sendTypes count]; - unsigned er = [returnTypes count]; - NSWindow *resp = [[application keyWindow] firstResponder]; - id obj = nil; - - for (i = 0; i <= es; i++) - { - NSString *sendType; - - sendType = (i < es) ? [sendTypes objectAtIndex: i] : nil; - - for (j = 0; j <= er; j++) - { - NSString *returnType; - - returnType = (j < er) ? [returnTypes objectAtIndex: j] : nil; - - obj = [resp validRequestorForSendType: sendType - returnType: returnType]; - if (obj != nil) - { - NSPasteboard *pb; - - pb = [NSPasteboard pasteboardWithUniqueName]; - if ([obj writeSelectionToPasteboard: pb - types: sendTypes] == NO) - { - NSLog(@"object failed to write to pasteboard\n"); - } - else if (NSPerformService(title, pb) == NO) - { - NSLog(@"Failed to perform %@\n", title); - } - else if ([obj readSelectionFromPasteboard: pb] == NO) - { - NSLog(@"object failed to read from pasteboard\n"); - } - return; - } - } - } -} - -- (BOOL) hasRegisteredTypes: (NSDictionary*)service -{ - NSArray *sendTypes = [service objectForKey: @"NSSendTypes"]; - NSArray *returnTypes = [service objectForKey: @"NSReturnTypes"]; - NSString *type; - unsigned i; - - /* - * We know that both sendTypes and returnTypes can't be nil since - * make_services has validated the service entry for us. - */ - if (sendTypes == nil || [sendTypes count] == 0) - { - for (i = 0; i < [returnTypes count]; i++) - { - type = [returnTypes objectAtIndex: i]; - if ([returnInfo member: type] != nil) - { - return YES; - } - } - } - else if (returnTypes == nil || [returnTypes count] == 0) - { - for (i = 0; i < [sendTypes count]; i++) - { - type = [sendTypes objectAtIndex: i]; - if ([combinations objectForKey: type] != nil) - { - return YES; - } - } - } - else - { - for (i = 0; i < [sendTypes count]; i++) - { - NSSet *rset; - - type = [sendTypes objectAtIndex: i]; - rset = [combinations objectForKey: type]; - if (rset != nil) - { - unsigned j; - - for (j = 0; j < [returnTypes count]; j++) - { - type = [returnTypes objectAtIndex: j]; - if ([rset member: type] != nil) - { - return YES; - } - } - } - } - } - return NO; -} - -/* - * Use tag in menu cell to identify slot in menu titles array that - * contains the full title of the service. - * Return nil if this is not one of our service menu cells. - */ -- (NSString*) item2title: (NSCell*)item -{ - unsigned pos; - - if ([item target] != self) - return nil; - pos = [item tag]; - if (pos > [menuTitles count]) - return nil; - return [menuTitles objectAtIndex: pos]; -} - - -- (void) dealloc -{ - NSRegisterServicesProvider(nil, nil); - [languages release]; - [servicesProvider release]; - [returnInfo release]; - [combinations release]; - [title2info release]; - [menuTitles release]; - [servicesMenu release]; - [super dealloc]; -} - -- forward: (SEL)aSel :(arglist_t)frame -{ - NSString *selName = NSStringFromSelector(aSel); - - /* - * If the selector matches the correct form for a services request, - * send the message to the services provider - otherwise raise an - * exception to say the method is not implemented. - */ - if ([selName hasSuffix: @":userData:error:"]) - return [servicesProvider performv: aSel :frame]; - else - return [self notImplemented: aSel]; -} - -- (void) rebuildServices -{ - NSDictionary *services; - NSUserDefaults *defs; - NSMutableArray *newLang; - NSSet *disabled; - NSMutableSet *alreadyFound; - NSMutableDictionary *newServices; - unsigned pos; - - /* - * If the application has not yet started up fully and registered us, - * we defer the rebuild intil we are registered. This avoids loads - * of successive rebuilds as responder classes register the types they - * can handle on startup. - */ - if (isRegistered == NO) - return; - - defs = [NSUserDefaults standardUserDefaults]; - newLang = [[[defs arrayForKey: @"Languages"] mutableCopy] autorelease]; - if (newLang == nil) - { - newLang = [NSMutableArray arrayWithCapacity: 1]; - } - if ([newLang containsObject: @"default"] == NO) - { - [newLang addObject: @"default"]; - } - ASSIGN(languages, newLang); - - disabled = [NSSet setWithArray: [defs arrayForKey: @"DisabledServices"]]; - services = [GSAllServicesDictionary() objectForKey: @"ByService"]; - - newServices = [NSMutableDictionary dictionaryWithCapacity: 16]; - alreadyFound = [NSMutableSet setWithCapacity: 16]; - - /* - * Build dictionary of services we can use. - * 1. make sure we make dictionary keyed on preferred menu item language - * 2. don't include entries for services already examined. - * 3. don't include entries for menu items specifically disabled. - * 4. don't include entries for which we have no registered types. - */ - for (pos = 0; pos < [languages count]; pos++) - { - NSDictionary *byLanguage; - - byLanguage = [services objectForKey: [languages objectAtIndex: pos]]; - if (byLanguage != nil) - { - NSEnumerator *enumerator = [byLanguage keyEnumerator]; - NSString *menuItem; - - while ((menuItem = [enumerator nextObject]) != nil) - { - NSDictionary *service = [byLanguage objectForKey: menuItem]; - - if ([alreadyFound member: service] != nil) - continue; - - [alreadyFound addObject: service]; - - if ([disabled member: menuItem] != nil) - continue; - - if ([self hasRegisteredTypes: service]) - [newServices setObject: service forKey: menuItem]; - } - } - } - if ([newServices isEqual: title2info] == NO) - { - NSArray *titles; - - ASSIGN(title2info, newServices); - titles = [title2info allKeys]; - titles = [titles sortedArrayUsingSelector: @selector(compare:)]; - ASSIGN(menuTitles, titles); - [self rebuildServicesMenu]; - } -} - -- (void) rebuildServicesMenu -{ - if (isRegistered && servicesMenu) - { - NSArray *itemArray; - NSMutableSet *keyEquivalents; - unsigned pos; - unsigned loc0; - unsigned loc1; - SEL sel = @selector(doService:); - NSMenu *submenu = nil; - - itemArray = [[servicesMenu itemArray] retain]; - pos = [itemArray count]; - while (pos > 0) - { - [servicesMenu removeItem: [itemArray objectAtIndex: --pos]]; - } - [itemArray release]; - - keyEquivalents = [NSMutableSet setWithCapacity: 4]; - for (loc0 = pos = 0; pos < [menuTitles count]; pos++) - { - NSString *title = [menuTitles objectAtIndex: pos]; - NSString *equiv = @""; - NSDictionary *info = [title2info objectForKey: title]; - NSDictionary *titles; - NSDictionary *equivs; - NSRange r; - unsigned lang; - id item; - - /* - * Find the key equivalent corresponding to this menu title - * in the service definition. - */ - titles = [info objectForKey: @"NSMenuItem"]; - equivs = [info objectForKey: @"NSKeyEquivalent"]; - for (lang = 0; lang < [languages count]; lang++) - { - NSString *language = [languages objectAtIndex: lang]; - NSString *t = [titles objectForKey: language]; - - if ([t isEqual: title]) - { - equiv = [equivs objectForKey: language]; - } - } - - /* - * Make a note that we are using the key equivalent, or - * set to nil if we have already used it in this menu. - */ - if (equiv) - { - if ([keyEquivalents member: equiv] == nil) - { - [keyEquivalents addObject: equiv]; - } - else - { - equiv = @""; - } - } - - r = [title rangeOfString: @"/"]; - if (r.length > 0) - { - NSString *subtitle = [title substringFromIndex: r.location+1]; - NSString *parentTitle = [title substringToIndex: r.location]; - NSMenu *menu; - - item = [servicesMenu itemWithTitle: parentTitle]; - if (item == nil) - { - loc1 = 0; - item = [servicesMenu insertItemWithTitle: parentTitle - action: 0 - keyEquivalent: @"" - atIndex: loc0++]; - menu = [[NSMenu alloc] initWithTitle: parentTitle]; - [servicesMenu setSubmenu: submenu - forItem: item]; - } - else - { - menu = (NSMenu*)[item target]; - } - if (menu != submenu) - { - [submenu sizeToFit]; - submenu = menu; - } - item = [submenu insertItemWithTitle: subtitle - action: sel - keyEquivalent: equiv - atIndex: loc1++]; - [item setTarget: self]; - [item setTag: pos]; - } - else - { - item = [servicesMenu insertItemWithTitle: title - action: sel - keyEquivalent: equiv - atIndex: loc0++]; - [item setTarget: self]; - [item setTag: pos]; - } - } - [submenu sizeToFit]; - [servicesMenu sizeToFit]; - [servicesMenu update]; - } -} - -/* - * Set up connection to listen for incoming service requests. - */ -- (void) registerAsServiceProvider -{ - if (isRegistered == NO) - { - NSString *appName; - - isRegistered = YES; - [self rebuildServices]; - appName = [[[NSProcessInfo processInfo] processName] lastPathComponent]; - NSRegisterServicesProvider(self, appName); - } -} - -/* - * Register send and return types that an object can handle - we keep - * a note of all the possible combinations - - * 'returnInfo' is a set of all the return types that can be handled - * without a send. - * 'combinations' is a dictionary of all send types, with the assciated - * values being sets of possible return types. - */ -- (void) registerSendTypes: (NSArray *)sendTypes - returnTypes: (NSArray *)returnTypes -{ - BOOL didChange = NO; - unsigned i; - - for (i = 0; i < [sendTypes count]; i++) - { - NSString *sendType = [sendTypes objectAtIndex: i]; - NSMutableSet *returnSet = [combinations objectForKey: sendType]; - - if (returnSet == nil) - { - returnSet = [NSMutableSet setWithCapacity: [returnTypes count]]; - [combinations setObject: returnSet forKey: sendType]; - [returnSet addObjectsFromArray: returnTypes]; - didChange = YES; - } - else - { - unsigned count = [returnSet count]; - - [returnSet addObjectsFromArray: returnTypes]; - if ([returnSet count] != count) - { - didChange = YES; - } - } - } - - i = [returnInfo count]; - [returnInfo addObjectsFromArray: returnTypes]; - if ([returnInfo count] != i) - { - didChange = YES; - } - - if (didChange) - { - [self rebuildServices]; - } -} - -- (NSMenu*) servicesMenu -{ - return servicesMenu; -} - -- (id) servicesProvider -{ - return servicesProvider; -} - -- (void) setServicesMenu: (NSMenu*)aMenu -{ - ASSIGN(servicesMenu, aMenu); - [self rebuildServicesMenu]; -} - -- (void) setServicesProvider: (id)anObject -{ - ASSIGN(servicesProvider, anObject); -} - -- (BOOL) validateMenuItem: (NSCell*)item -{ - NSString *title = [self item2title: item]; - NSDictionary *info = [title2info objectForKey: title]; - NSArray *sendTypes = [info objectForKey: @"NSSendTypes"]; - NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"]; - unsigned i, j; - unsigned es = [sendTypes count]; - unsigned er = [returnTypes count]; - NSWindow *resp = [[application keyWindow] firstResponder]; - - /* - * If the menu item is not in our map, it must be the cell containing - * a sub-menu - so we see if any cell in the submenu is valid. - */ - if (title == nil) - { - NSMenu *sub = [item target]; - - if (sub && [sub isKindOfClass: [NSMenu class]]) - { - NSArray *a = [sub itemArray]; - - for (i = 0; i < [a count]; i++) - { - if ([self validateMenuItem: [a objectAtIndex: i]] == YES) - { - return YES; - } - } - } - return NO; - } - - /* - * The cell corresponds to one of our services - so we check to see if - * there is anything that can deal with it. - */ - if (es == 0) - { - if (er == 0) - { - if ([resp validRequestorForSendType: nil - returnType: nil] != nil) - return YES; - } - else - { - for (j = 0; j < er; j++) - { - NSString *returnType; - - returnType = [returnTypes objectAtIndex: j]; - if ([resp validRequestorForSendType: nil - returnType: returnType] != nil) - return YES; - } - } - } - else - { - for (i = 0; i < es; i++) - { - NSString *sendType; - - sendType = [sendTypes objectAtIndex: i]; - - if (er == 0) - { - if ([resp validRequestorForSendType: sendType - returnType: nil] != nil) - return YES; - } - else - { - for (j = 0; j < er; j++) - { - NSString *returnType; - - returnType = [returnTypes objectAtIndex: j]; - if ([resp validRequestorForSendType: sendType - returnType: returnType] != nil) - return YES; - } - } - } - } - return NO; -} - -- (void) updateServicesMenu -{ - if (servicesMenu && [[application mainMenu] autoenablesItems]) - { - NSMenuMatrix *menuCells; - NSArray *a; - unsigned i; - NSMenu *mainMenu = [application mainMenu]; - BOOL found = NO; - - a = [mainMenu itemArray]; - for (i = 0; i < [a count]; i++) - if ([[a objectAtIndex: i] target] == servicesMenu) - found = YES; - if (found == NO) - { - NSLog(@"Services menu not in main menu!\n"); - return; - } - - menuCells = [servicesMenu menuCells]; - a = [menuCells itemArray]; - - for (i = 0; i < [a count]; i++) - { - NSCell *cell = [a objectAtIndex: i]; - BOOL wasEnabled = [cell isEnabled]; - BOOL shouldBeEnabled = [self validateMenuItem: cell]; - - if (wasEnabled != shouldBeEnabled) - { - [cell setEnabled: shouldBeEnabled]; - [menuCells setNeedsDisplayInRect: [menuCells cellFrameAtRow: i]]; - } - } - /* FIXME - only doing this here 'cos auto-display doesn't work */ - if ([menuCells needsDisplay]) - [menuCells display]; - } -} - -@end /* ApplicationListener */ +#include "GNUServicesManager.h" //***************************************************************************** // @@ -754,7 +120,7 @@ static id NSApp; NSDebugLog(@"Begin of NSApplication -init\n"); - listener = [ApplicationListener newWithApplication: self]; + listener = [GNUServicesManager newWithApplication: self]; window_list = [NSMutableArray new]; // allocate window list window_count = 1; @@ -1609,7 +975,7 @@ int i; returnTypes: (NSArray *)returnTypes { [listener registerSendTypes: sendTypes - returnTypes: returnTypes]; + returnTypes: returnTypes]; } - (NSMenu *) servicesMenu diff --git a/Source/NSPasteboard.m b/Source/NSPasteboard.m index 558b9f72c..36994dba0 100644 --- a/Source/NSPasteboard.m +++ b/Source/NSPasteboard.m @@ -738,177 +738,3 @@ NSGetFileTypes(NSArray *pboardTypes) return nil; } - -extern NSDictionary* GSAllServicesDictionary(); -extern NSDictionary* GSApplicationsDictionary(); - -static NSConnection *listener = nil; - -void -NSRegisterServicesProvider(id provider, NSString *name) -{ - if (listener) - { - /* - * Ensure there is no previous listener and nothing else using - * the given port name. - */ - [[NSPortNameServer defaultPortNameServer] removePortForName: name]; - [listener release]; - } - listener = [NSConnection newRegisteringAtName: name - withRootObject: provider]; - [listener retain]; -} - -BOOL -NSPerformService(NSString *serviceItem, NSPasteboard *pboard) -{ - NSUserDefaults *defs; - NSArray *languages; - NSDictionary *services; - NSDictionary *byLanguage; - NSDictionary *service; - NSString *port; - unsigned end; - unsigned pos; - NSString *timeout; - double seconds; - NSDate *finishBy; - NSString *appPath; - id provider; - NSConnection *connection; - NSString *message; - NSString *selName; - SEL msgSel; - NSString *userData; - IMP msgImp; - NSString *error = nil; - NSDictionary *allServices; - - /* - * Get language preference array. - */ - defs = [NSUserDefaults standardUserDefaults]; - languages = [defs arrayForKey: @"Languages"]; - - /* - * Get dictionary of menu services from workspace manager. - */ - allServices = GSAllServicesDictionary(); - - services = [allServices objectForKey: @"ByService"]; - - /* - * Find service information for a service matching the given menu item - * Search in language preference order. - */ - if (languages) - end = [languages count]; - else - end = 0; - byLanguage = nil; - for (pos = 0; pos < end; pos++) - { - NSString *language = [languages objectAtIndex: pos]; - - byLanguage = [services objectForKey: language]; - if (byLanguage != nil) - break; - } - if (byLanguage == nil) - byLanguage = [services objectForKey: @"default"]; - service = [byLanguage objectForKey: serviceItem]; - - if (service == nil) - return NO; /* No matching service. */ - - port = [service objectForKey: @"NSPortName"]; - timeout = [service objectForKey: @"NSTimeout"]; - if (timeout && [timeout floatValue] > 100) - { - seconds = [timeout floatValue] / 1000.0; - } - else - { - seconds = 30.0; - } - finishBy = [NSDate dateWithTimeIntervalSinceNow: seconds]; - appPath = [service objectForKey: @"ServicePath"]; - userData = [service objectForKey: @"NSUserData"]; - message = [service objectForKey: @"NSMessage"]; - selName = [message stringByAppendingString: @":userData:error:"]; - msgSel = NSSelectorFromString(selName); - - /* - * If there is no selector - we need to generate one with the - * appropriate types. - */ - if (msgSel == 0) - { - NSMethodSignature *sig; - const char *name; - const char *type; - - sig = [NSMethodSignature signatureWithObjCTypes: "v@:@@^@"]; - type = [sig methodType]; - name = [selName cString]; - msgSel = sel_register_typed_name(name, type); - } - - provider = [NSConnection rootProxyForConnectionWithRegisteredName: port - host: @""]; - if (provider == nil) - { - if ([[NSWorkspace sharedWorkspace] launchApplication: appPath] == NO) - { - return NO; /* Unable to launch. */ - } - - provider = [NSConnection rootProxyForConnectionWithRegisteredName: port - host: @""]; - while (provider == nil && [finishBy timeIntervalSinceNow] > 1.0) - { - NSRunLoop *loop = [NSRunLoop currentRunLoop]; - NSDate *next; - - [NSTimer scheduledTimerWithTimeInterval: 1.0 - invocation: nil - repeats: NO]; - next = [NSDate dateWithTimeIntervalSinceNow: 5.0]; - [loop runUntilDate: next]; - provider = [NSConnection - rootProxyForConnectionWithRegisteredName: port - host: @""]; - } - } - - if (provider == nil) - { - return NO; /* Unable to contact. */ - } - connection = [(NSDistantObject*)provider connectionForProxy]; - seconds = [finishBy timeIntervalSinceNow]; - [connection setRequestTimeout: seconds]; - [connection setReplyTimeout: seconds]; - - msgImp = get_imp(fastClass(provider), msgSel); - NS_DURING - { - (*msgImp)(provider, msgSel, pboard, userData, &error); - } - NS_HANDLER - { - [NSException raise: NSPasteboardCommunicationException - format: @"%s", [[localException reason] cString]]; - } - NS_ENDHANDLER - - if (error != nil) - { - NSLog(error); - return NO; - } - - return YES; -} diff --git a/Source/NSWorkspace.m b/Source/NSWorkspace.m index d9c820303..0576a94cf 100644 --- a/Source/NSWorkspace.m +++ b/Source/NSWorkspace.m @@ -39,18 +39,10 @@ #define stringify_it(X) #X #define prog_path(X,Y) \ - stringify_it(X) "/Tools/" GNUSTEP_TARGET_DIR "/" LIBRARY_COMBO Y + stringify_it(X) "/Tools/" GNUSTEP_TARGET_DIR "/" LIBRARY_COMBO -static NSDictionary *allServices = nil; static NSDictionary *applications = nil; -NSDictionary* -GSAllServicesDictionary() -{ - if (allServices == nil) - [[NSWorkspace sharedWorkspace] findApplications]; - return allServices; -} NSDictionary* GSApplicationsDictionary() @@ -60,20 +52,14 @@ GSApplicationsDictionary() return applications; } -void -NSUpdateDynamicServices() -{ - [[NSWorkspace sharedWorkspace] findApplications]; -} - @implementation NSWorkspace static NSWorkspace *sharedWorkspace = nil; static NSNotificationCenter *workspaceCenter = nil; static BOOL userDefaultsChanged = NO; -static NSString *cacheName = @".GNUstepServices"; -static NSString *servicesPath = nil; +static NSString *appListName = @".GNUstepAppList"; +static NSString *appListPath = nil; static NSString* gnustep_target_dir = #ifdef GNUSTEP_TARGET_DIR @@ -130,9 +116,10 @@ static NSString* library_combo = str = [env objectForKey: @"GNUSTEP_USER_ROOT"]; if (str == nil) - str = [NSString stringWithFormat: @"%@/GNUstep", NSHomeDirectory()]; - str = [str stringByAppendingPathComponent: cacheName]; - servicesPath = [str retain]; + str = [NSString stringWithFormat: @"%@/GNUstep/Services", + NSHomeDirectory()]; + str = [str stringByAppendingPathComponent: appListName]; + appListPath = [str retain]; if ((str = [env objectForKey: @"GNUSTEP_TARGET_DIR"]) != nil) gnustep_target_dir = [str retain]; @@ -258,7 +245,7 @@ inFileViewerRootedAtPath:(NSString *)rootFullpath NSString *last = [appName lastPathComponent]; if (applications == nil) - NSUpdateDynamicServices(); + [self findApplications]; if ([appName isEqual: last]) { @@ -322,21 +309,18 @@ inFileViewerRootedAtPath:(NSString *)rootFullpath - (void)findApplications { NSData *data; - NSDictionary *newServices; - NSDictionary *dict; + NSDictionary *newApps; system(prog_path(GNUSTEP_INSTALL_PREFIX, "/make_services")); - data = [NSData dataWithContentsOfFile: servicesPath]; + data = [NSData dataWithContentsOfFile: appListPath]; if (data) - newServices = [NSDeserializer deserializePropertyListFromData: data - mutableContainers: NO]; + newApps = [NSDeserializer deserializePropertyListFromData: data + mutableContainers: NO]; else - newServices = [NSDictionary dictionary]; + newApps = [NSDictionary dictionary]; - ASSIGN(allServices, newServices); - dict = [newServices objectForKey: @"Applications"]; - ASSIGN(applications, dict); + ASSIGN(applications, newApps); } // diff --git a/Tools/GNUmakefile b/Tools/GNUmakefile index a2476e91c..6447b3374 100644 --- a/Tools/GNUmakefile +++ b/Tools/GNUmakefile @@ -32,12 +32,13 @@ GNUSTEP_MAKEFILES = $(GNUSTEP_SYSTEM_ROOT)/Makefiles include $(GNUSTEP_MAKEFILES)/common.make # The applications to be compiled -TOOL_NAME = gpbs make_services +TOOL_NAME = gpbs make_services set_show_service SERVICE_NAME = example # The source files to be compiled gpbs_OBJC_FILES = gpbs.m dummy.m make_services_OBJC_FILES = make_services.m dummy.m +set_show_service_OBJC_FILES = set_show_service.m dummy.m example_OBJC_FILES = example.m dummy.m diff --git a/Tools/dummy.m b/Tools/dummy.m index bc2dd3f1e..d966351f7 100644 --- a/Tools/dummy.m +++ b/Tools/dummy.m @@ -54,6 +54,22 @@ NSBeep(void) { } +void GSfill() {} +void GSsetgray() {} +void GSnewpath() {} +void GSgrestore() {} +void GSrectfill() {} +void GSsetlinewidth() {} +void GSclosepath() {} +void GSshow() {} +void GStranslate() {} +void GSmoveto() {} +void GSgsave() {} +void GSlineto() {} +void GSstroke() {} +void GSrlineto() {} +void GSrectclip() {} + void NSFrameRect(NSRect aRect) { } @@ -79,3 +95,15 @@ void NSDrawGroove(NSRect aRect, NSRect clipRect) @implementation GMUnarchiver @end +@interface NSWindowView : NSObject +@end + +@implementation NSWindowView +@end + +@interface GPSDrawContext : NSObject +@end + +@implementation GPSDrawContext +@end + diff --git a/Tools/make_services.m b/Tools/make_services.m index d75c010af..c15d61f59 100644 --- a/Tools/make_services.m +++ b/Tools/make_services.m @@ -31,13 +31,16 @@ #include #include #include +#include #include #include static void scanDirectory(NSMutableDictionary *services, NSString *path); +static void scanDynamic(NSMutableDictionary *services, NSString *path); static NSMutableArray *validateEntry(id svcs, NSString* path); static NSMutableDictionary *validateService(NSDictionary *service, NSString* path, unsigned i); +static NSString *appsName = @".GNUstepAppList"; static NSString *cacheName = @".GNUstepServices"; static NSString *infoLoc = @"Resources/Info-gnustep.plist"; @@ -65,6 +68,7 @@ main(int argc, char** argv) unsigned index; BOOL isDir; NSMutableDictionary *fullMap; + NSDictionary *oldMap; pool = [NSAutoreleasePool new]; @@ -132,18 +136,43 @@ main(int argc, char** argv) } roots = [NSMutableArray arrayWithCapacity: 3]; + services = [NSMutableDictionary dictionaryWithCapacity: 200]; /* * Build a list of 'root' directories to search for applications. * Order is important - later duplicates of services are ignored. + * + * Make sure that the users 'GNUstep/Services' directory exists. */ str = [env objectForKey: @"GNUSTEP_USER_ROOT"]; if (str != nil) usrRoot = str; else usrRoot = [NSString stringWithFormat: @"%@/GNUstep", NSHomeDirectory()]; + + mgr = [NSFileManager defaultManager]; + if (([mgr fileExistsAtPath: usrRoot isDirectory: &isDir] && isDir) == 0) + { + if ([mgr createDirectoryAtPath: usrRoot attributes: nil] == NO) + { + NSLog(@"couldn't create %@\n", usrRoot); + [pool release]; + exit(1); + } + } [roots addObject: usrRoot]; + usrRoot = [usrRoot stringByAppendingPathComponent: @"Services"]; + if (([mgr fileExistsAtPath: usrRoot isDirectory: &isDir] && isDir) == 0) + { + if ([mgr createDirectoryAtPath: usrRoot attributes: nil] == NO) + { + NSLog(@"couldn't create %@\n", usrRoot); + [pool release]; + exit(1); + } + } + str = [env objectForKey: @"GNUSTEP_LOCAL_ROOT"]; if (str != nil) [roots addObject: str]; @@ -156,14 +185,19 @@ main(int argc, char** argv) else [roots addObject: @"/usr/GNUstep"]; + /* + * Before doing the main scan, we examine the 'Services' directory to + * see if any application has registered dynamic services - these take + * precedence over any listed in an applications Info_gnustep.plist. + */ + scanDynamic(services, usrRoot); + /* * List of directory names to search within each root directory * when looking for applications providing services. */ locations = [NSArray arrayWithObjects: @"Apps", @"Library/Services", nil]; - services = [NSMutableDictionary dictionaryWithCapacity: 200]; - for (index = 0; index < [roots count]; index++) { NSString *root = [roots objectAtIndex: index]; @@ -178,33 +212,57 @@ main(int argc, char** argv) } } - mgr = [NSFileManager defaultManager]; - if (([mgr fileExistsAtPath: usrRoot isDirectory: &isDir] && isDir) == 0) - { - if ([mgr createDirectoryAtPath: usrRoot attributes: nil] == NO) - { - NSLog(@"couldn't create %@\n", usrRoot); - [pool release]; - exit(1); - } - } - fullMap = [NSMutableDictionary dictionaryWithCapacity: 5]; [fullMap setObject: services forKey: @"ByPath"]; [fullMap setObject: serviceMap forKey: @"ByService"]; [fullMap setObject: filterMap forKey: @"ByFilter"]; [fullMap setObject: printMap forKey: @"ByPrint"]; [fullMap setObject: spellMap forKey: @"BySpell"]; - [fullMap setObject: applicationMap forKey: @"Applications"]; str = [usrRoot stringByAppendingPathComponent: cacheName]; - data = [NSSerializer serializePropertyList: fullMap]; - if ([data writeToFile: str atomically: YES] == NO) + if ([mgr fileExistsAtPath: str]) { - NSLog(@"couldn't write %@\n", str); - [pool release]; - exit(1); + data = [NSData dataWithContentsOfFile: str]; + oldMap = [NSDeserializer deserializePropertyListFromData: data + mutableContainers: NO]; } + else + { + oldMap = nil; + } + if ([fullMap isEqual: oldMap] == NO) + { + data = [NSSerializer serializePropertyList: fullMap]; + if ([data writeToFile: str atomically: YES] == NO) + { + NSLog(@"couldn't write %@\n", str); + [pool release]; + exit(1); + } + } + + str = [usrRoot stringByAppendingPathComponent: appsName]; + if ([mgr fileExistsAtPath: str]) + { + data = [NSData dataWithContentsOfFile: str]; + oldMap = [NSDeserializer deserializePropertyListFromData: data + mutableContainers: NO]; + } + else + { + oldMap = nil; + } + if ([applicationMap isEqual: oldMap] == NO) + { + data = [NSSerializer serializePropertyList: applicationMap]; + if ([data writeToFile: str atomically: YES] == NO) + { + NSLog(@"couldn't write %@\n", str); + [pool release]; + exit(1); + } + } + [pool release]; exit(0); } @@ -331,6 +389,54 @@ scanDirectory(NSMutableDictionary *services, NSString *path) [arp release]; } +static void +scanDynamic(NSMutableDictionary *services, NSString *path) +{ + NSFileManager *mgr = [NSFileManager defaultManager]; + NSAutoreleasePool *arp = [NSAutoreleasePool new]; + NSArray *contents = [mgr directoryContentsAtPath: path]; + unsigned index; + + for (index = 0; index < [contents count]; index++) + { + NSString *name = [contents objectAtIndex: index]; + NSString *infPath; + NSDictionary *info; + + /* + * Ignore anything with a leading dot. + */ + if ([name hasPrefix: @"."]) + { + continue; + } + + infPath = [path stringByAppendingPathComponent: name]; + + info = [NSDictionary dictionaryWithContentsOfFile: infPath]; + if (info) + { + id svcs = [info objectForKey: @"NSServices"]; + + if (svcs) + { + NSMutableArray *entry; + + entry = validateEntry(svcs, infPath); + if (entry) + { + [services setObject: entry forKey: infPath]; + } + } + } + else + { + NSLog(@"bad app info - %@\n", infPath); + } + } + [arp release]; +} + static NSMutableArray* validateEntry(id svcs, NSString *path) { diff --git a/Tools/set_show_service.m b/Tools/set_show_service.m new file mode 100644 index 000000000..0f32f0bcb --- /dev/null +++ b/Tools/set_show_service.m @@ -0,0 +1,93 @@ +/* + set_show_servicaes.m + + GNUstep utility to enable or disable a service for the current user. + + Copyright (C) 1998 Free Software Foundation, Inc. + + Author: Richard Frith-Macdonald + Date: November 1998 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; see the file COPYING.LIB. + If not, write to the Free Software Foundation, + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include +#include + + +int +main(int argc, char** argv) +{ + NSAutoreleasePool *pool; + NSProcessInfo *proc; + NSArray *args; + unsigned index; + + // [NSObject enableDoubleReleaseCheck: YES]; + + pool = [NSAutoreleasePool new]; + + proc = [NSProcessInfo processInfo]; + if (proc == nil) + { + NSLog(@"unable to get process information!\n"); + [pool release]; + exit(0); + } + + args = [proc arguments]; + + for (index = 0; index < [args count]; index++) + { + if ([[args objectAtIndex: index] isEqual: @"--help"]) + { + printf( +"set_show_service enables or disables the display of a specified service\n" +"item. It's should be in the form 'set_show_services --enable name' or \n" +"'set_show_service --disable name' where 'name' is a service name.\n"); + exit(0); + } + if ([[args objectAtIndex: index] isEqual: @"--enable"]) + { + if (index >= [args count] - 1) + { + NSLog(@"No name specified for enable.\n"); + exit(1); + } + NSSetShowsServicesMenuItem([args objectAtIndex: ++index], YES); + exit(0); + } + if ([[args objectAtIndex: index] isEqual: @"--disable"]) + { + if (index >= [args count] - 1) + { + NSLog(@"No name specified for disable.\n"); + exit(1); + } + NSSetShowsServicesMenuItem([args objectAtIndex: ++index], NO); + exit(0); + } + } + + NSLog(@"Nothing to do.\n"); + return(1); +} +