Various tidyups and services improvements.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@17210 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-Macdonald 2003-07-14 12:27:42 +00:00
parent 3e87f17811
commit e12ac6ef5d
6 changed files with 758 additions and 450 deletions

View file

@ -1,3 +1,14 @@
2003-07-14 Richard Frith-Macdonald <rfm@gnu.org>
* Headers/gnustep/gui/GSServicesManager.h: removed a function
* Headers/gnustep/gui/NSApplication.h: and added it here so it's
public. Also fixed some argument name missmatches.
* Source/GSServicesManager.m: Some fixes for recent changes to
DO in base library ... also make listener even safer.
* Source/NSPasteboard.m: Improve documentation and change pasteboards
to always be sent over DO bycopy.
* Tools/example.m: Set error response to nil when there is no error.
2003-07-12 18:20 Alexander Malmberg <alexander@malmberg.org>
* Headers/gnustep/gui/NSTextView.h, Source/NSTextView.m: Add

View file

@ -86,7 +86,5 @@
- (void) updateServicesMenu;
@end
id GSContactApplication(NSString *appName, NSString *port, NSDate *expire);
#endif

View file

@ -411,16 +411,21 @@ APPKIT_EXPORT NSString *NSApplicationWillUpdateNotification;
* Determine Whether an Item Is Included in Services Menus
*/
APPKIT_EXPORT int
NSSetShowsServicesMenuItem(NSString *item, BOOL showService);
NSSetShowsServicesMenuItem(NSString *name, BOOL enabled);
APPKIT_EXPORT BOOL
NSShowsServicesMenuItem(NSString *item);
NSShowsServicesMenuItem(NSString *name);
/*
* Programmatically Invoke a Service
*/
APPKIT_EXPORT BOOL
NSPerformService(NSString *item, NSPasteboard *pboard);
NSPerformService(NSString *serviceItem, NSPasteboard *pboard);
#ifndef NO_GNUSTEP
APPKIT_EXPORT id
GSContactApplication(NSString *appName, NSString *port, NSDate *expire);
#endif
/*
* Force Services Menu to Update Based on New Services

View file

@ -67,7 +67,7 @@ static GSServicesManager *manager = nil;
* messing with us. This is responsible for forwarding service
* requests and other communications with the outside world.
*/
@interface GSListener : NSObject
@interface GSListener : NSProxy
+ (id) connectionBecameInvalid: (NSNotification*)notification;
+ (GSListener*) listener;
+ (id) servicesProvider;
@ -77,13 +77,10 @@ static GSServicesManager *manager = nil;
- (void) release;
- (id) retain;
- (id) self;
- (void) performService: (NSString*)name
withPasteboard: (NSPasteboard*)pb
userData: (NSString*)ud
error: (NSString**)e;
@end
static NSConnection *listenerConnection = nil;
static NSMutableArray *listeners = nil;
static GSListener *listener = nil;
static id servicesProvider = nil;
static NSString *providerName = nil;
@ -159,10 +156,10 @@ NSRegisterServicesProvider(id provider, NSString *name)
}
/*
* The GSListener 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.
* The GSListener 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 GSListener
@ -179,15 +176,39 @@ NSRegisterServicesProvider(id provider, NSString *name)
return self;
}
+ (void) initialize
{
static BOOL beenHere = NO;
if (beenHere == NO)
{
beenHere = YES;
listeners = [NSMutableArray new];
}
}
+ (GSListener*) listener
{
if (listener == nil)
{
listener = (id)NSAllocateObject(self, 0, NSDefaultMallocZone());
[listeners addObject: listener];
}
return listener;
}
/**
* Needed to permit use of this class as a notification observer,
* since the notification system caches method implementations for speed.
*/
+ (IMP) methodForSelector: (SEL)aSelector
{
if (aSelector == 0)
[NSException raise: NSInvalidArgumentException
format: @"%@ null selector given", NSStringFromSelector(_cmd)];
return get_imp(GSObjCClass(self), aSelector);
}
+ (id) servicesProvider
{
return servicesProvider;
@ -204,6 +225,11 @@ NSRegisterServicesProvider(id provider, NSString *name)
}
}
- (id) autorelease
{
return self;
}
- (Class) class
{
return 0;
@ -213,47 +239,13 @@ NSRegisterServicesProvider(id provider, NSString *name)
{
}
/*
* Obsolete ... prefer forwardInvocation now.
/**
* Selectively forwards those messages which are known to be safe.
*/
- forward: (SEL)aSel :(arglist_t)frame
{
NSString *selName = NSStringFromSelector(aSel);
id delegate;
/*
* 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];
/*
* If the applications delegate can handle the message - forward to it.
*/
delegate = [[NSApplication sharedApplication] delegate];
if ([delegate respondsToSelector: aSel] == YES)
return [delegate performv: aSel :frame];
/*
* If the selector matches the correct form for a file operaqtion
* send the message to the manager.
*/
if ([selName hasPrefix: @"application:"] == YES
&& [manager respondsToSelector: aSel] == YES)
return [(id)manager performv: aSel :frame];
[NSException raise: NSGenericException
format: @"method %@ not implemented", selName];
return nil;
}
- (void) forwardInvocation: (NSInvocation*)anInvocation
{
SEL aSel = [anInvocation selector];
NSString *selName = NSStringFromSelector(aSel);
id delegate;
/*
* If the selector matches the correct form for a services request,
@ -261,74 +253,130 @@ NSRegisterServicesProvider(id provider, NSString *name)
*/
if ([selName hasSuffix: @":userData:error:"])
{
[anInvocation invokeWithTarget: servicesProvider];
return;
}
if ([servicesProvider respondsToSelector: aSel] == YES)
{
NSPasteboard *pb;
/*
* If the applications delegate can handle the message - forward to it.
*/
delegate = [[NSApplication sharedApplication] delegate];
if ([delegate respondsToSelector: aSel] == YES)
/*
* Create a local NSPasteboard object for this pasteboard.
* If we try to use the remote NSPasteboard object, we get
* trouble when setting property lists since the remote
* NSPasteboard fails to serialize the local property
* list objects for sending to gpbs.
*/
[anInvocation getArgument: (void*)&pb atIndex: 2];
pb = [NSPasteboard pasteboardWithName: [pb name]];
[anInvocation setArgument: (void*)&pb atIndex: 2];
[anInvocation invokeWithTarget: servicesProvider];
return;
}
}
else
{
[anInvocation invokeWithTarget: delegate];
return;
}
id delegate = [[NSApplication sharedApplication] delegate];
/*
* If the selector matches the correct form for a file operaqtion
* send the message to the manager.
*/
if ([selName hasPrefix: @"application:"] == YES
&& [manager respondsToSelector: aSel] == YES)
{
[anInvocation invokeWithTarget: manager];
return;
}
if ([selName hasPrefix: @"application:"] == YES)
{
if ([delegate respondsToSelector: aSel] == YES)
{
[anInvocation invokeWithTarget: delegate];
return;
}
else if ([manager respondsToSelector: aSel] == YES)
{
[anInvocation invokeWithTarget: manager];
return;
}
}
else
{
if ([delegate respondsToSelector: aSel] == YES)
{
NSArray *messages;
messages = [[NSUserDefaults standardUserDefaults] arrayForKey:
@"GSPermittedMessages"];
if (messages != nil)
{
if ([messages containsObject: selName] == YES)
{
[anInvocation invokeWithTarget: delegate];
}
}
else
{
[anInvocation invokeWithTarget: delegate];
}
return;
}
}
}
[NSException raise: NSGenericException
format: @"method %@ not implemented", selName];
}
- (void) performService: (NSString*)name
withPasteboard: (NSPasteboard*)pb
userData: (NSString*)ud
error: (NSString**)e
/**
* Return the appropriate method signature for aSelector, checking
* to see if it's a standard service message or standard application
* message.<br />
* If the message is non-standard, it can be checked against a list
* of messages specified by the GSPermittedMessages user default.
*/
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
{
id obj = servicesProvider;
SEL msgSel = NSSelectorFromString(name);
IMP msgImp;
NSMethodSignature *sig = nil;
NSString *selName = NSStringFromSelector(aSelector);
/*
Create a local NSPasteboard object for this pasteboard. If we try to
use the remote NSPasteboard object, we get trouble when setting property
lists since the remote NSPasteboard fails to serialize the local property
list objects for sending to gpbs.
*/
pb = [NSPasteboard pasteboardWithName: [pb name]];
if (obj != nil && [obj respondsToSelector: msgSel])
if ([selName hasSuffix: @":userData:error:"])
{
msgImp = [obj methodForSelector: msgSel];
if (msgImp != 0)
sig = [servicesProvider methodSignatureForSelector: aSelector];
}
else
{
id delegate = [[NSApplication sharedApplication] delegate];
if ([selName hasPrefix: @"application:"] == YES)
{
(*msgImp)(obj, msgSel, pb, ud, e);
return;
if ([delegate respondsToSelector: aSelector] == YES)
{
sig = [delegate methodSignatureForSelector: aSelector];
}
else
{
sig = [manager methodSignatureForSelector: aSelector];
}
}
else
{
NSArray *messages;
messages = [[NSUserDefaults standardUserDefaults] arrayForKey:
@"GSPermittedMessages"];
if (messages != nil)
{
if ([messages containsObject: selName] == YES)
{
sig = [delegate methodSignatureForSelector: aSelector];
}
}
else
{
sig = [delegate methodSignatureForSelector: aSelector];
}
}
}
return sig;
}
obj = [[NSApplication sharedApplication] delegate];
if (obj != nil && [obj respondsToSelector: msgSel])
- (BOOL) respondsToSelector: (SEL)aSelector
{
if ([self methodSignatureForSelector: aSelector] != nil)
{
msgImp = [obj methodForSelector: msgSel];
if (msgImp != 0)
{
(*msgImp)(obj, msgSel, pb, ud, e);
return;
}
return YES;
}
*e = @"No object available to provide service";
return NO;
}
- (void) release
@ -1250,14 +1298,29 @@ static NSString *disabledName = @".GNUstepDisabled";
* if necessary. Returns the proxy to the remote application, or nil
* on failure.
* </p>
* The value of expire provides a timeout in case the application cannot
* be contacted promptly.
* <p>The value of port specifies the name of the distributed objects
* service to which the connection is to be made. If this is nil
* it will be inferred from appName ... by convention, applications
* use their own name (minus any path or extension) for this.
* </p>
* <p>The value of expire provides a timeout in case the application cannot
* be contacted promptly. If it is omitted, a thirty second timeout is
* used.
* </p>
*/
id
GSContactApplication(NSString *appName, NSString *port, NSDate *expire)
{
id app;
if (port == nil)
{
port = [[appName lastPathComponent] stringByDeletingPathExtension];
}
if (expire == nil)
{
expire = [NSDate datyeWithTimeIntervalSinceNow: 30.0];
}
if (providerName != nil && [port isEqual: providerName] == YES)
{
app = [GSListener listener]; // Contect our own listener.
@ -1391,13 +1454,31 @@ NSPerformService(NSString *serviceItem, NSPasteboard *pboard)
/*
* At last, we ask for the service to be performed.
* We create an untyped selector matching the message name we have,
* Using that, we get a method signature from the provider, and
* take the type information from that to make a fully typed
* selector, with which we can create and use an invocation.
*/
NS_DURING
{
[provider performService: selName
withPasteboard: pboard
userData: userData
error: &error];
const char *name = [selName UTF8String];
SEL sel = GSSelectorFromNameAndTypes(name, 0);
NSMethodSignature *sig = [provider methodSignatureForSelector: sel];
if (sig != nil)
{
NSInvocation *inv;
NSString **errPtr = &error;
sel = GSSelectorFromNameAndTypes(name, [sig methodType]);
inv = [NSInvocation invocationWithMethodSignature: sig];
[inv setTarget: provider];
[inv setSelector: sel];
[inv setArgument: (void*)&pboard atIndex: 2];
[inv setArgument: (void*)&userData atIndex: 3];
[inv setArgument: (void*)&errPtr atIndex: 4];
[inv invoke];
}
}
NS_HANDLER
{

View file

@ -31,7 +31,12 @@
<p>
The pasteboard system is the core of OpenStep inter-application
communications. This chapter is concerned with the use of the system,
for detailed reference see the [NSPasteboard] class.
for detailed reference see the [NSPasteboard] class.<br />
For non-standard services provided by applications (ie those which
do not fit the general <em>services</em> mechanism described below),
you generally use the Distributed Objects system (see [NSConnection])
directly, and some hints about that are provided at the end of this
chapter.
</p>
<section>
<heading>Cut and Paste</heading>
@ -401,6 +406,109 @@
</deflist>
</desc>
</deflist>
<p>
Filter services are used implicitly whenever you get a pasteboard
by using one of the methods +pasteboardByFilteringData:ofType:,
+pasteboardByFilteringFile: or +pasteboardByFilteringTypesInPasteboard:
as the pasteboard system will automatically invoke any available
filter to convert the data in the pastebaord to any required
type as long as a conversion can be done using a single filter.
</p>
</section>
<section>
<heading>Distributed Objects services</heading>
<p>
While the general <em>services</em> mechanism described above
covers most eventualities, there are some circumstances where
you might want your application to offer more complex services
which require the client application to have been written to
make use of those services and where the interaction between
the two is much trickier.
</p>
<p>
In most cases, such situations are handled by server processes
rather than gui applications, thus avoiding all the overheads
of a gui application ... linking with the gui library and
using the windowing system etc. On occasion you may actually
want the services to use facilities from the gui library
(such as the [NSPasteboard] or [NSWorkspace] class).
</p>
<p>
Traditionally, NeXTstep and GNUstep applications permit you to
connect to an application using the standard [NSConnection]
mechanisms, with the name of the port you connect to being
(by convention) the name of the application. The root proxy
of the NSConnection obtained this way would be the
[NSApplication-delegate] object, and any messages sent to
this object would be handled by the application delegate.
</p>
<p>
In the interests of security, GNUstep provides a mechanism to
ensure that <em>only</em> those methods you explicitly want to
be available to remote processes are actually available.<br />
Those methods are assumed to be any of the standard application
methods, and any methods implementing the standard <em>services</em>
mechanism (ie. methods whose names begin <code>application:</code>
or end with <code>:userData:error:</code>), plus any methods
listed in the array returned by the
<code>GSPermittedMessages</code> user default.<br />
If your application wishes to make non-standard methods available,
it should use [NSUserDefaults-registerDefaults:] to set a standard
value for GSPermittedMessages. Users of the application can then
use the defaults system to override that standard setting for the
application in order to reduce or increase the list of messages
available to remote processes.
</p>
<p>
To make use of a service, you need to check to ensure that the
application providing the service is running, connect to it,
and then send messages to it. You should take care to catch
exceptions and deal with a loss of connection to the server
application.<br />
As an aid to using the services, GNUstep provides a helper function
(GSContactApplication()) which encapsulates the process of
establishing a connection and
launching the server application if necessary.
</p>
<example>
id proxy = GSContactApplication(@"pathToApp", nil, nil);
if (proxy != nil)
{
NS_EXCEPTION
{
id result = [proxy performTask: taskName withArgument: anArgument];
if (result == nil)
{
// handle error
}
else
{
// Use result
}
}
NS_HANDLER
// Handle exception
NS_ENDHANDLER
}
</example>
<p>
If we want to send repeated messages, we may store the proxy to
server application, and might want to keep track of the state of
the connection to be sure that the proxy is still valid.
</p>
<example>
ASSIGN(remote, proxy);
// We want to keep hold of the proxy for use later, so we need to know
// if the connection dies ... we ask for a notification to call our
// connectionBecameInvalid: method when the connection dies ... in that
// method we can release the proxy.
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(connectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: [remote connectionForProxy]];
</example>
</section>
</chapter>
*/
@ -419,6 +527,7 @@
#include <Foundation/NSMapTable.h>
#include <Foundation/NSNotification.h>
#include <Foundation/NSException.h>
#include <Foundation/NSInvocation.h>
#include <Foundation/NSLock.h>
#include <Foundation/NSPathUtilities.h>
#include <Foundation/NSPortNameServer.h>
@ -440,7 +549,7 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
/*
* A pasteboard class for lazily filtering data
*/
@interface FilteredPasteboard : NSPasteboard
@interface GSFiltered : NSPasteboard
{
@public
NSArray *originalTypes;
@ -450,7 +559,7 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
}
@end
@implementation FilteredPasteboard
@implementation GSFiltered
/**
* Given an array of types, produce an array of all the types we can
@ -489,11 +598,58 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
- (void) dealloc
{
DESTROY(originalTypes);
DESTROY(file);
DESTROY(data);
DESTROY(pboard);
[super dealloc];
}
/**
* GSFiltered instances are encoded differently from standard pasteboards,
* they have no names and are instead represented by whatever it is they
* are filtering.
*/
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if (data != nil)
{
[aCoder encodeObject: data];
[aCoder encodeObject: [originalTypes lastObject]];
}
else if (file != nil)
{
[aCoder encodeObject: file];
}
else
{
[aCoder encodeObject: pboard];
}
}
- (id) initWithCoder: (NSCoder*)aCoder
{
NSPasteboard *p;
id val = [aCoder decodeObject];
if ([val isKindOfClass: [NSData class]] == YES)
{
NSString *s = [aCoder decodeObject];
p = [NSPasteboard pasteboardByFilteringData: val ofType: s];
}
else if ([val isKindOfClass: [NSString class]] == YES)
{
p = [NSPasteboard pasteboardByFilteringFile: val];
}
else
{
p = [NSPasteboard pasteboardByFilteringTypesInPasteboard: val];
}
ASSIGN(self, p);
return self;
}
/**
* This method actually performs any filtering required.
*/
@ -791,10 +947,31 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
*/
NS_DURING
{
[provider performService: selName
withPasteboard: tmp
userData: userData
error: &error];
const char *cName;
SEL sel;
NSMethodSignature *sig;
cName = [selName UTF8String];
sel = GSSelectorFromNameAndTypes(cName, 0);
sig = [provider methodSignatureForSelector: sel];
if (sig != nil)
{
NSInvocation *inv;
NSString **errPtr = &error;
sel = GSSelectorFromNameAndTypes(cName, [sig methodType]);
inv = [NSInvocation invocationWithMethodSignature: sig];
[inv setTarget: provider];
[inv setSelector: sel];
[inv setArgument: (void*)&tmp atIndex: 2];
[inv setArgument: (void*)&userData atIndex: 3];
[inv setArgument: (void*)&errPtr atIndex: 4];
[inv invoke];
}
else
{
error = @"No remote object to handle filter";
}
}
NS_HANDLER
{
@ -821,6 +998,8 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
@interface NSPasteboard (Private)
+ (void) _localServer: (id<GSPasteboardSvr>)s;
+ (id) _lostServer: (NSNotification*)notification;
+ (id<GSPasteboardSvr>) _pbs;
+ (NSPasteboard*) _pasteboardWithTarget: (id<GSPasteboardObj>)aTarget
name: (NSString*)aName;
@ -864,9 +1043,15 @@ static NSMutableDictionary *pasteboards = nil;
static id<GSPasteboardSvr> the_server = nil;
static NSMapTable *mimeMap = NULL;
//
// Class methods
//
/**
* Returns the general pasteboard found by calling +pasteboardWithName:
* with NSGeneralPboard as the name.
*/
+ (NSPasteboard*) generalPasteboard
{
return [self pasteboardWithName: NSGeneralPboard];
}
+ (void) initialize
{
if (self == [NSPasteboard class])
@ -878,210 +1063,93 @@ static NSMapTable *mimeMap = NULL;
}
}
/*
* Special method to use a local server rather than connecting over DO
/**
* <p>Creates and returns a pasteboard from which the data in the named
* file can be read in all the types to which it can be converted by
* filter services.<br />
* The type of data in the file is inferred from the file extension.
* </p>
* <p>No filtering is actually performed until some object asks the
* pasteboard for the data, so calling this method is quite inexpensive.
* </p>
*/
+ (void) _localServer: (id<GSPasteboardSvr>)s
+ (NSPasteboard*) pasteboardByFilteringData: (NSData*)data
ofType: (NSString*)type
{
the_server = s;
}
GSFiltered *p;
NSArray *types;
NSArray *originalTypes;
+ (id) _lostServer: (NSNotification*)notification
{
id obj = the_server;
the_server = nil;
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSConnectionDidDieNotification
object: [notification object]];
RELEASE(obj);
return self;
}
+ (id<GSPasteboardSvr>) _pbs
{
if (the_server == nil)
{
NSString *host;
NSString *description;
host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"];
if (host == nil)
{
host = @"";
}
else
{
NSHost *h;
/*
* If we have a host specified, but it is the current host,
* we do not need to ask for a host by name (nameserver lookup
* can be faster) and the empty host name can be used to
* indicate that we may start a pasteboard server locally.
*/
h = [NSHost hostWithName: host];
if (h == nil)
{
NSLog(@"Unknown NSHost (%@) ignored", host);
host = @"";
}
else if ([h isEqual: [NSHost currentHost]] == YES)
{
host = @"";
}
else
{
host = [h name];
}
}
if ([host length] == 0)
{
description = @"local host";
}
else
{
description = host;
}
the_server = (id<GSPasteboardSvr>)[NSConnection
rootProxyForConnectionWithRegisteredName: PBSNAME host: host];
if (the_server == nil && [host length] > 0)
{
NSString *service;
service = [PBSNAME stringByAppendingFormat: @"-%@", host];
the_server = (id<GSPasteboardSvr>)[NSConnection
rootProxyForConnectionWithRegisteredName: service host: @"*"];
}
if (RETAIN((id)the_server) != nil)
{
NSConnection *conn = [(id)the_server connectionForProxy];
Protocol *p = @protocol(GSPasteboardSvr);
[(id)the_server setProtocolForProxy: p];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_lostServer:)
name: NSConnectionDidDieNotification
object: conn];
}
else
{
static BOOL recursion = NO;
static NSString *cmd = nil;
static NSArray *args = nil;
if (cmd == nil && recursion ==NO)
{
#ifdef GNUSTEP_BASE_LIBRARY
cmd = RETAIN([[NSSearchPathForDirectoriesInDomains(
GSToolsDirectory, NSSystemDomainMask, YES) objectAtIndex: 0]
stringByAppendingPathComponent: @"gpbs"]);
#else
cmd = RETAIN([[@GNUSTEP_INSTALL_PREFIX
stringByAppendingPathComponent: @"Tools"]
stringByAppendingPathComponent: @"gpbs"]);
#endif
}
if (recursion == YES || cmd == nil)
{
NSLog(@"Unable to contact pasteboard server - "
@"please ensure that gpbs is running for %@.", description);
return nil;
}
else
{
NSLog(@"\nI couldn't contact the pasteboard server for %@ -\n"
@"so I'm attempting to to start one - which will take a few seconds.\n"
@"Trying to launch gpbs from %@ or a machine/operating-system subdirectory.\n"
@"It is recommended that you start the pasteboard server (gpbs) when\n"
@"your windowing system is started up.\n", description,
[cmd stringByDeletingLastPathComponent]);
if ([host length] > 0)
{
args = [[NSArray alloc] initWithObjects:
@"-NSHost", host, nil];
}
[NSTask launchedTaskWithLaunchPath: cmd arguments: args];
[NSTimer scheduledTimerWithTimeInterval: 5.0
invocation: nil
repeats: NO];
[[NSRunLoop currentRunLoop] runUntilDate:
[NSDate dateWithTimeIntervalSinceNow: 5.0]];
recursion = YES;
[self _pbs];
recursion = NO;
}
}
}
return the_server;
}
/*
* Creating and Releasing an NSPasteboard Object
*/
+ (NSPasteboard*) _pasteboardWithTarget: (id<GSPasteboardObj>)aTarget
name: (NSString*)aName
{
NSPasteboard *p = nil;
[dictionary_lock lock];
p = [pasteboards objectForKey: aName];
if (p != nil)
{
/*
* It is conceivable that the following may have occurred -
* 1. The pasteboard was created on the server
* 2. We set up an NSPasteboard to point to it
* 3. The pasteboard on the server was destroyed by a [-releaseGlobally]
* 4. The named pasteboard was asked for again - resulting in a new
* object being created on the server.
* If this is the case, our proxy for the object on the server will be
* out of date, so we swap it for the newly created one.
*/
if (p->target != (id)aTarget)
{
AUTORELEASE(p->target);
p->target = RETAIN((id)aTarget);
}
}
else
{
/*
* For a newly created NSPasteboard object, we must make an entry
* in the dictionary so we can look it up safely.
*/
p = [self alloc];
if (p != nil)
{
p->target = RETAIN((id)aTarget);
p->name = RETAIN(aName);
[pasteboards setObject: p forKey: aName];
AUTORELEASE(p);
}
/*
* The AUTORELEASE ensures that the NSPasteboard object we are
* returning will be released once our caller has finished with it.
* This is necessary so that our RELEASE method will be called to
* remove the NSPasteboard from the 'pasteboards' array when it is not
* needed any more.
*/
}
[dictionary_lock unlock];
originalTypes = [NSArray arrayWithObject: type];
types = [GSFiltered _typesFilterableFrom: originalTypes];
p = (GSFiltered*)[GSFiltered pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
p->data = [data copy];
[p declareTypes: types owner: p];
return p;
}
/**
* Returns the general pasteboard found by calling +pasteboardWithName:
* with NSGeneralPboard as the name.
* <p>Creates and returns a pasteboard from which the data in the named
* file can be read in all the types to which it can be converted by
* filter services.<br />
* The type of data in the file is inferred from the file extension.
* </p>
*/
+ (NSPasteboard*) generalPasteboard
+ (NSPasteboard*) pasteboardByFilteringFile: (NSString*)filename
{
return [self pasteboardWithName: NSGeneralPboard];
GSFiltered *p;
NSString *ext = [filename pathExtension];
NSArray *types;
NSArray *originalTypes;
if ([ext length] > 0)
{
originalTypes = [NSArray arrayWithObjects:
NSCreateFileContentsPboardType(ext),
NSFileContentsPboardType,
nil];
}
else
{
originalTypes = [NSArray arrayWithObject: NSFileContentsPboardType];
}
types = [GSFiltered _typesFilterableFrom: originalTypes];
p = (GSFiltered*)[GSFiltered pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
p->file = [filename copy];
[p declareTypes: types owner: p];
return p;
}
/**
* <p>Creates and returns a pasteboard where the data contained in pboard
* is available for reading in as many types as it can be converted to by
* available filter services. This normally expands on the range of types
* available in pboard.
* </p>
* <p>NB. This only permits a single level of filtering ... if pboard was
* previously returned by another filtering method, it is returned instead
* of a new pasteboard.
* </p>
*/
+ (NSPasteboard*) pasteboardByFilteringTypesInPasteboard: (NSPasteboard*)pboard
{
GSFiltered *p;
NSArray *types;
NSArray *originalTypes;
if ([pboard isKindOfClass: [GSFiltered class]] == YES)
{
return pboard;
}
originalTypes = [pboard types];
types = [GSFiltered _typesFilterableFrom: originalTypes];
p = (GSFiltered*)[GSFiltered pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
p->pboard = RETAIN(pboard);
[p declareTypes: types owner: p];
return p;
}
/**
@ -1168,95 +1236,6 @@ static NSMapTable *mimeMap = NULL;
return nil;
}
/**
* <p>Creates and returns a pasteboard from which the data in the named
* file can be read in all the types to which it can be converted by
* filter services.<br />
* The type of data in the file is inferred from the file extension.
* </p>
* <p>No filtering is actually performed until some object asks the
* pasteboard for the data, so calling this method is quite inexpensive.
* </p>
*/
+ (NSPasteboard*) pasteboardByFilteringData: (NSData*)data
ofType: (NSString*)type
{
FilteredPasteboard *p;
NSArray *types;
NSArray *originalTypes;
originalTypes = [NSArray arrayWithObject: type];
types = [FilteredPasteboard _typesFilterableFrom: originalTypes];
p = (FilteredPasteboard*)[FilteredPasteboard pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
p->data = [data copy];
[p declareTypes: types owner: p];
return p;
}
/**
* <p>Creates and returns a pasteboard from which the data in the named
* file can be read in all the types to which it can be converted by
* filter services.<br />
* The type of data in the file is inferred from the file extension.
* </p>
*/
+ (NSPasteboard*) pasteboardByFilteringFile: (NSString*)filename
{
FilteredPasteboard *p;
NSString *ext = [filename pathExtension];
NSArray *types;
NSArray *originalTypes;
if ([ext length] > 0)
{
originalTypes = [NSArray arrayWithObjects:
NSCreateFileContentsPboardType(ext),
NSFileContentsPboardType,
nil];
}
else
{
originalTypes = [NSArray arrayWithObject: NSFileContentsPboardType];
}
types = [FilteredPasteboard _typesFilterableFrom: originalTypes];
p = (FilteredPasteboard*)[FilteredPasteboard pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
p->file = [filename copy];
[p declareTypes: types owner: p];
return p;
}
/**
* <p>Creates and returns a pasteboard where the data contained in pboard
* is available for reading in as many types as it can be converted to by
* available filter services. This normally expands on the range of types
* available in pboard.
* </p>
* <p>NB. This only permits a single level of filtering ... if pboard was
* previously returned by another filtering method, it is returned instead
* of a new pasteboard.
* </p>
*/
+ (NSPasteboard*) pasteboardByFilteringTypesInPasteboard: (NSPasteboard*)pboard
{
FilteredPasteboard *p;
NSArray *types;
NSArray *originalTypes;
if ([pboard isKindOfClass: [FilteredPasteboard class]] == YES)
{
return pboard;
}
originalTypes = [pboard types];
types = [FilteredPasteboard _typesFilterableFrom: originalTypes];
p = (FilteredPasteboard*)[FilteredPasteboard pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
p->pboard = RETAIN(pboard);
[p declareTypes: types owner: p];
return p;
}
/**
* Returns an array of the types from which data of the specified type
* can be produced by registered filter services.<br />
@ -1290,55 +1269,6 @@ static NSMapTable *mimeMap = NULL;
return [types allObjects];
}
/*
* Instance methods
*/
- (id) _target
{
return target;
}
/*
* Creating and Releasing an NSPasteboard Object
*/
- (void) dealloc
{
RELEASE(target);
RELEASE(name);
[super dealloc];
}
/**
* Releases the receiver in the pasteboard server so that no other application
* can use the pasteboard. This should not be called for any of the standard
* pasteboards, only for temporary ones.
*/
- (void) releaseGlobally
{
if ([name isEqualToString: NSGeneralPboard] == YES
|| [name isEqualToString: NSFontPboard] == YES
|| [name isEqualToString: NSRulerPboard] == YES
|| [name isEqualToString: NSFindPboard] == YES
|| [name isEqualToString: NSDragPboard] == YES)
{
[NSException raise: NSGenericException
format: @"Illegal attempt to globally release %@", name];
}
[target releaseGlobally];
[pasteboards removeObjectForKey: name];
}
/**
* Returns the pasteboard name (as given to +pasteboardWithName:)
* for the receiver.
*/
- (NSString*) name
{
return name;
}
/**
* <p>Adds newTypes to the pasteboard and declares newOwner to be the owner
* of the pasteboard. Use only after -declareTypes:owner: has been called
@ -1456,6 +1386,79 @@ static NSMapTable *mimeMap = NULL;
return changeCount;
}
- (void) dealloc
{
RELEASE(target);
RELEASE(name);
[super dealloc];
}
/**
* Encode for DO by using just our name.
*/
- (void) encodeWithCoder: (NSCoder*)aCoder
{
[aCoder encodeObject: name];
}
/**
* Decode from DO by creating a new pasteboard with the decoded name.
*/
- (id) initWithCoder: (NSCoder*)aCoder
{
NSString *n = [aCoder decodeObject];
NSPasteboard *p = [[self class] pasteboardWithName: n];
ASSIGN(self, p);
return self;
}
/**
* Returns the pasteboard name (as given to +pasteboardWithName:)
* for the receiver.
*/
- (NSString*) name
{
return name;
}
/**
* Releases the receiver in the pasteboard server so that no other application
* can use the pasteboard. This should not be called for any of the standard
* pasteboards, only for temporary ones.
*/
- (void) releaseGlobally
{
if ([name isEqualToString: NSGeneralPboard] == YES
|| [name isEqualToString: NSFontPboard] == YES
|| [name isEqualToString: NSRulerPboard] == YES
|| [name isEqualToString: NSFindPboard] == YES
|| [name isEqualToString: NSDragPboard] == YES)
{
[NSException raise: NSGenericException
format: @"Illegal attempt to globally release %@", name];
}
[target releaseGlobally];
[pasteboards removeObjectForKey: name];
}
/**
* Pasteboards sent over DO should always be copied so that a local
* instance is created to communicate with the pastebaord server.
*/
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
{
if ([self class] == [NSPasteboard class])
{
return self; // Always encode bycopy.
}
if ([self class] == [GSFiltered class])
{
return self; // Always encode bycopy.
}
return [super replacementObjectForPortCoder: aCoder];
}
/*
* Hack to ensure correct release of NSPasteboard objects -
* If we are released such that the only thing retaining us
@ -1853,6 +1856,212 @@ static NSMapTable *mimeMap = NULL;
@end
@implementation NSPasteboard (Private)
/*
* Special method to use a local server rather than connecting over DO
*/
+ (void) _localServer: (id<GSPasteboardSvr>)s
{
the_server = s;
}
+ (id) _lostServer: (NSNotification*)notification
{
id obj = the_server;
the_server = nil;
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSConnectionDidDieNotification
object: [notification object]];
RELEASE(obj);
return self;
}
+ (id<GSPasteboardSvr>) _pbs
{
if (the_server == nil)
{
NSString *host;
NSString *description;
host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"];
if (host == nil)
{
host = @"";
}
else
{
NSHost *h;
/*
* If we have a host specified, but it is the current host,
* we do not need to ask for a host by name (nameserver lookup
* can be faster) and the empty host name can be used to
* indicate that we may start a pasteboard server locally.
*/
h = [NSHost hostWithName: host];
if (h == nil)
{
NSLog(@"Unknown NSHost (%@) ignored", host);
host = @"";
}
else if ([h isEqual: [NSHost currentHost]] == YES)
{
host = @"";
}
else
{
host = [h name];
}
}
if ([host length] == 0)
{
description = @"local host";
}
else
{
description = host;
}
the_server = (id<GSPasteboardSvr>)[NSConnection
rootProxyForConnectionWithRegisteredName: PBSNAME host: host];
if (the_server == nil && [host length] > 0)
{
NSString *service;
service = [PBSNAME stringByAppendingFormat: @"-%@", host];
the_server = (id<GSPasteboardSvr>)[NSConnection
rootProxyForConnectionWithRegisteredName: service host: @"*"];
}
if (RETAIN((id)the_server) != nil)
{
NSConnection *conn = [(id)the_server connectionForProxy];
Protocol *p = @protocol(GSPasteboardSvr);
[(id)the_server setProtocolForProxy: p];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_lostServer:)
name: NSConnectionDidDieNotification
object: conn];
}
else
{
static BOOL recursion = NO;
static NSString *cmd = nil;
static NSArray *args = nil;
if (cmd == nil && recursion ==NO)
{
#ifdef GNUSTEP_BASE_LIBRARY
cmd = RETAIN([[NSSearchPathForDirectoriesInDomains(
GSToolsDirectory, NSSystemDomainMask, YES) objectAtIndex: 0]
stringByAppendingPathComponent: @"gpbs"]);
#else
cmd = RETAIN([[@GNUSTEP_INSTALL_PREFIX
stringByAppendingPathComponent: @"Tools"]
stringByAppendingPathComponent: @"gpbs"]);
#endif
}
if (recursion == YES || cmd == nil)
{
NSLog(@"Unable to contact pasteboard server - "
@"please ensure that gpbs is running for %@.", description);
return nil;
}
else
{
NSLog(@"\nI couldn't contact the pasteboard server for %@ -\n"
@"so I'm attempting to to start one - which will take a few seconds.\n"
@"Trying to launch gpbs from %@ or a machine/operating-system subdirectory.\n"
@"It is recommended that you start the pasteboard server (gpbs) when\n"
@"your windowing system is started up.\n", description,
[cmd stringByDeletingLastPathComponent]);
if ([host length] > 0)
{
args = [[NSArray alloc] initWithObjects:
@"-NSHost", host, nil];
}
[NSTask launchedTaskWithLaunchPath: cmd arguments: args];
[NSTimer scheduledTimerWithTimeInterval: 5.0
invocation: nil
repeats: NO];
[[NSRunLoop currentRunLoop] runUntilDate:
[NSDate dateWithTimeIntervalSinceNow: 5.0]];
recursion = YES;
[self _pbs];
recursion = NO;
}
}
}
return the_server;
}
/*
* Creating and Releasing an NSPasteboard Object
*/
+ (NSPasteboard*) _pasteboardWithTarget: (id<GSPasteboardObj>)aTarget
name: (NSString*)aName
{
NSPasteboard *p = nil;
[dictionary_lock lock];
p = [pasteboards objectForKey: aName];
if (p != nil)
{
/*
* It is conceivable that the following may have occurred -
* 1. The pasteboard was created on the server
* 2. We set up an NSPasteboard to point to it
* 3. The pasteboard on the server was destroyed by a [-releaseGlobally]
* 4. The named pasteboard was asked for again - resulting in a new
* object being created on the server.
* If this is the case, our proxy for the object on the server will be
* out of date, so we swap it for the newly created one.
*/
if (p->target != (id)aTarget)
{
AUTORELEASE(p->target);
p->target = RETAIN((id)aTarget);
}
}
else
{
/*
* For a newly created NSPasteboard object, we must make an entry
* in the dictionary so we can look it up safely.
*/
p = [self alloc];
if (p != nil)
{
p->target = RETAIN((id)aTarget);
p->name = RETAIN(aName);
[pasteboards setObject: p forKey: aName];
AUTORELEASE(p);
}
/*
* The AUTORELEASE ensures that the NSPasteboard object we are
* returning will be released once our caller has finished with it.
* This is necessary so that our RELEASE method will be called to
* remove the NSPasteboard from the 'pasteboards' array when it is not
* needed any more.
*/
}
[dictionary_lock unlock];
return p;
}
- (id) _target
{
return target;
}
@end
/**

View file

@ -74,6 +74,7 @@
NSString *val;
NSData *data;
*err = nil;
types = [pb types];
if (![types containsObject: NSStringPboardType])
{
@ -106,6 +107,7 @@
NSString *browser;
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
*err = nil;
types = [pb types];
if (![types containsObject: NSStringPboardType])
{
@ -143,6 +145,7 @@
NSString *out;
NSArray *types;
*err = nil;
types = [pb types];
if (![types containsObject: NSStringPboardType])
{
@ -171,6 +174,7 @@
NSString *out;
NSArray *types;
*err = nil;
types = [pb types];
if (![types containsObject: NSStringPboardType])
{