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:
CaS 2003-07-14 12:27:42 +00:00
parent b0e1cf1da8
commit 126ecf308c
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> 2003-07-12 18:20 Alexander Malmberg <alexander@malmberg.org>
* Headers/gnustep/gui/NSTextView.h, Source/NSTextView.m: Add * Headers/gnustep/gui/NSTextView.h, Source/NSTextView.m: Add

View file

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

View file

@ -411,16 +411,21 @@ APPKIT_EXPORT NSString *NSApplicationWillUpdateNotification;
* Determine Whether an Item Is Included in Services Menus * Determine Whether an Item Is Included in Services Menus
*/ */
APPKIT_EXPORT int APPKIT_EXPORT int
NSSetShowsServicesMenuItem(NSString *item, BOOL showService); NSSetShowsServicesMenuItem(NSString *name, BOOL enabled);
APPKIT_EXPORT BOOL APPKIT_EXPORT BOOL
NSShowsServicesMenuItem(NSString *item); NSShowsServicesMenuItem(NSString *name);
/* /*
* Programmatically Invoke a Service * Programmatically Invoke a Service
*/ */
APPKIT_EXPORT BOOL 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 * 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 * messing with us. This is responsible for forwarding service
* requests and other communications with the outside world. * requests and other communications with the outside world.
*/ */
@interface GSListener : NSObject @interface GSListener : NSProxy
+ (id) connectionBecameInvalid: (NSNotification*)notification; + (id) connectionBecameInvalid: (NSNotification*)notification;
+ (GSListener*) listener; + (GSListener*) listener;
+ (id) servicesProvider; + (id) servicesProvider;
@ -77,13 +77,10 @@ static GSServicesManager *manager = nil;
- (void) release; - (void) release;
- (id) retain; - (id) retain;
- (id) self; - (id) self;
- (void) performService: (NSString*)name
withPasteboard: (NSPasteboard*)pb
userData: (NSString*)ud
error: (NSString**)e;
@end @end
static NSConnection *listenerConnection = nil; static NSConnection *listenerConnection = nil;
static NSMutableArray *listeners = nil;
static GSListener *listener = nil; static GSListener *listener = nil;
static id servicesProvider = nil; static id servicesProvider = nil;
static NSString *providerName = 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 * The GSListener class exists as a proxy to forward messages to
* service provider objects. It implements very few methods and * service provider objects. It implements very few methods and
* those that it does implement are generally designed to defeat * those that it does implement are generally designed to defeat
* any attack by a malicious program. * any attack by a malicious program.
*/ */
@implementation GSListener @implementation GSListener
@ -179,15 +176,39 @@ NSRegisterServicesProvider(id provider, NSString *name)
return self; return self;
} }
+ (void) initialize
{
static BOOL beenHere = NO;
if (beenHere == NO)
{
beenHere = YES;
listeners = [NSMutableArray new];
}
}
+ (GSListener*) listener + (GSListener*) listener
{ {
if (listener == nil) if (listener == nil)
{ {
listener = (id)NSAllocateObject(self, 0, NSDefaultMallocZone()); listener = (id)NSAllocateObject(self, 0, NSDefaultMallocZone());
[listeners addObject: listener];
} }
return 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 + (id) servicesProvider
{ {
return servicesProvider; return servicesProvider;
@ -204,6 +225,11 @@ NSRegisterServicesProvider(id provider, NSString *name)
} }
} }
- (id) autorelease
{
return self;
}
- (Class) class - (Class) class
{ {
return 0; 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 - (void) forwardInvocation: (NSInvocation*)anInvocation
{ {
SEL aSel = [anInvocation selector]; SEL aSel = [anInvocation selector];
NSString *selName = NSStringFromSelector(aSel); NSString *selName = NSStringFromSelector(aSel);
id delegate;
/* /*
* If the selector matches the correct form for a services request, * 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:"]) if ([selName hasSuffix: @":userData:error:"])
{ {
[anInvocation invokeWithTarget: servicesProvider]; if ([servicesProvider respondsToSelector: aSel] == YES)
return; {
} NSPasteboard *pb;
/* /*
* If the applications delegate can handle the message - forward to it. * Create a local NSPasteboard object for this pasteboard.
*/ * If we try to use the remote NSPasteboard object, we get
delegate = [[NSApplication sharedApplication] delegate]; * trouble when setting property lists since the remote
if ([delegate respondsToSelector: aSel] == YES) * 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]; id delegate = [[NSApplication sharedApplication] delegate];
return;
}
/* if ([selName hasPrefix: @"application:"] == YES)
* If the selector matches the correct form for a file operaqtion {
* send the message to the manager. if ([delegate respondsToSelector: aSel] == YES)
*/ {
if ([selName hasPrefix: @"application:"] == YES [anInvocation invokeWithTarget: delegate];
&& [manager respondsToSelector: aSel] == YES) return;
{ }
[anInvocation invokeWithTarget: manager]; else if ([manager respondsToSelector: aSel] == YES)
return; {
} [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 [NSException raise: NSGenericException
format: @"method %@ not implemented", selName]; format: @"method %@ not implemented", selName];
} }
- (void) performService: (NSString*)name /**
withPasteboard: (NSPasteboard*)pb * Return the appropriate method signature for aSelector, checking
userData: (NSString*)ud * to see if it's a standard service message or standard application
error: (NSString**)e * 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; NSMethodSignature *sig = nil;
SEL msgSel = NSSelectorFromString(name); NSString *selName = NSStringFromSelector(aSelector);
IMP msgImp;
/* if ([selName hasSuffix: @":userData:error:"])
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])
{ {
msgImp = [obj methodForSelector: msgSel]; sig = [servicesProvider methodSignatureForSelector: aSelector];
if (msgImp != 0) }
else
{
id delegate = [[NSApplication sharedApplication] delegate];
if ([selName hasPrefix: @"application:"] == YES)
{ {
(*msgImp)(obj, msgSel, pb, ud, e); if ([delegate respondsToSelector: aSelector] == YES)
return; {
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]; - (BOOL) respondsToSelector: (SEL)aSelector
if (obj != nil && [obj respondsToSelector: msgSel]) {
if ([self methodSignatureForSelector: aSelector] != nil)
{ {
msgImp = [obj methodForSelector: msgSel]; return YES;
if (msgImp != 0)
{
(*msgImp)(obj, msgSel, pb, ud, e);
return;
}
} }
return NO;
*e = @"No object available to provide service";
} }
- (void) release - (void) release
@ -1250,14 +1298,29 @@ static NSString *disabledName = @".GNUstepDisabled";
* if necessary. Returns the proxy to the remote application, or nil * if necessary. Returns the proxy to the remote application, or nil
* on failure. * on failure.
* </p> * </p>
* The value of expire provides a timeout in case the application cannot * <p>The value of port specifies the name of the distributed objects
* be contacted promptly. * 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 id
GSContactApplication(NSString *appName, NSString *port, NSDate *expire) GSContactApplication(NSString *appName, NSString *port, NSDate *expire)
{ {
id app; id app;
if (port == nil)
{
port = [[appName lastPathComponent] stringByDeletingPathExtension];
}
if (expire == nil)
{
expire = [NSDate datyeWithTimeIntervalSinceNow: 30.0];
}
if (providerName != nil && [port isEqual: providerName] == YES) if (providerName != nil && [port isEqual: providerName] == YES)
{ {
app = [GSListener listener]; // Contect our own listener. 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. * 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 NS_DURING
{ {
[provider performService: selName const char *name = [selName UTF8String];
withPasteboard: pboard SEL sel = GSSelectorFromNameAndTypes(name, 0);
userData: userData NSMethodSignature *sig = [provider methodSignatureForSelector: sel];
error: &error];
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 NS_HANDLER
{ {

View file

@ -31,7 +31,12 @@
<p> <p>
The pasteboard system is the core of OpenStep inter-application The pasteboard system is the core of OpenStep inter-application
communications. This chapter is concerned with the use of the system, 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> </p>
<section> <section>
<heading>Cut and Paste</heading> <heading>Cut and Paste</heading>
@ -401,6 +406,109 @@
</deflist> </deflist>
</desc> </desc>
</deflist> </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> </section>
</chapter> </chapter>
*/ */
@ -419,6 +527,7 @@
#include <Foundation/NSMapTable.h> #include <Foundation/NSMapTable.h>
#include <Foundation/NSNotification.h> #include <Foundation/NSNotification.h>
#include <Foundation/NSException.h> #include <Foundation/NSException.h>
#include <Foundation/NSInvocation.h>
#include <Foundation/NSLock.h> #include <Foundation/NSLock.h>
#include <Foundation/NSPathUtilities.h> #include <Foundation/NSPathUtilities.h>
#include <Foundation/NSPortNameServer.h> #include <Foundation/NSPortNameServer.h>
@ -440,7 +549,7 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
/* /*
* A pasteboard class for lazily filtering data * A pasteboard class for lazily filtering data
*/ */
@interface FilteredPasteboard : NSPasteboard @interface GSFiltered : NSPasteboard
{ {
@public @public
NSArray *originalTypes; NSArray *originalTypes;
@ -450,7 +559,7 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
} }
@end @end
@implementation FilteredPasteboard @implementation GSFiltered
/** /**
* Given an array of types, produce an array of all the types we can * Given an array of types, produce an array of all the types we can
@ -489,11 +598,58 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
- (void) dealloc - (void) dealloc
{ {
DESTROY(originalTypes); DESTROY(originalTypes);
DESTROY(file);
DESTROY(data); DESTROY(data);
DESTROY(pboard); DESTROY(pboard);
[super dealloc]; [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. * This method actually performs any filtering required.
*/ */
@ -791,10 +947,31 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
*/ */
NS_DURING NS_DURING
{ {
[provider performService: selName const char *cName;
withPasteboard: tmp SEL sel;
userData: userData NSMethodSignature *sig;
error: &error];
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 NS_HANDLER
{ {
@ -821,6 +998,8 @@ static NSString *namePrefix = @"NSTypedFilenamesPboardType:";
@interface NSPasteboard (Private) @interface NSPasteboard (Private)
+ (void) _localServer: (id<GSPasteboardSvr>)s;
+ (id) _lostServer: (NSNotification*)notification;
+ (id<GSPasteboardSvr>) _pbs; + (id<GSPasteboardSvr>) _pbs;
+ (NSPasteboard*) _pasteboardWithTarget: (id<GSPasteboardObj>)aTarget + (NSPasteboard*) _pasteboardWithTarget: (id<GSPasteboardObj>)aTarget
name: (NSString*)aName; name: (NSString*)aName;
@ -864,9 +1043,15 @@ static NSMutableDictionary *pasteboards = nil;
static id<GSPasteboardSvr> the_server = nil; static id<GSPasteboardSvr> the_server = nil;
static NSMapTable *mimeMap = NULL; 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 + (void) initialize
{ {
if (self == [NSPasteboard class]) 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 originalTypes = [NSArray arrayWithObject: type];
{ types = [GSFiltered _typesFilterableFrom: originalTypes];
id obj = the_server; p = (GSFiltered*)[GSFiltered pasteboardWithUniqueName];
p->originalTypes = [originalTypes copy];
the_server = nil; p->data = [data copy];
[[NSNotificationCenter defaultCenter] [p declareTypes: types owner: p];
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; return p;
} }
/** /**
* Returns the general pasteboard found by calling +pasteboardWithName: * <p>Creates and returns a pasteboard from which the data in the named
* with NSGeneralPboard as the name. * 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; 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 * Returns an array of the types from which data of the specified type
* can be produced by registered filter services.<br /> * can be produced by registered filter services.<br />
@ -1290,55 +1269,6 @@ static NSMapTable *mimeMap = NULL;
return [types allObjects]; 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 * <p>Adds newTypes to the pasteboard and declares newOwner to be the owner
* of the pasteboard. Use only after -declareTypes:owner: has been called * of the pasteboard. Use only after -declareTypes:owner: has been called
@ -1456,6 +1386,79 @@ static NSMapTable *mimeMap = NULL;
return changeCount; 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 - * Hack to ensure correct release of NSPasteboard objects -
* If we are released such that the only thing retaining us * If we are released such that the only thing retaining us
@ -1853,6 +1856,212 @@ static NSMapTable *mimeMap = NULL;
@end @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; NSString *val;
NSData *data; NSData *data;
*err = nil;
types = [pb types]; types = [pb types];
if (![types containsObject: NSStringPboardType]) if (![types containsObject: NSStringPboardType])
{ {
@ -106,6 +107,7 @@
NSString *browser; NSString *browser;
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
*err = nil;
types = [pb types]; types = [pb types];
if (![types containsObject: NSStringPboardType]) if (![types containsObject: NSStringPboardType])
{ {
@ -143,6 +145,7 @@
NSString *out; NSString *out;
NSArray *types; NSArray *types;
*err = nil;
types = [pb types]; types = [pb types];
if (![types containsObject: NSStringPboardType]) if (![types containsObject: NSStringPboardType])
{ {
@ -171,6 +174,7 @@
NSString *out; NSString *out;
NSArray *types; NSArray *types;
*err = nil;
types = [pb types]; types = [pb types];
if (![types containsObject: NSStringPboardType]) if (![types containsObject: NSStringPboardType])
{ {