/* gpbs.m GNUstep pasteboard server Copyright (C) 1997 Free Software Foundation, Inc. Author: Richard Frith-Macdonald Date: August 1997 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 @class PasteboardServer; int debug = 0; int verbose = 0; #define MAXHIST 100 PasteboardServer *server = nil; NSConnection *conn = nil; NSLock *dictionary_lock = nil; NSMutableDictionary* pasteboards = nil; @interface PasteboardData: NSObject { NSData* data; NSString* type; id owner; id pboard; BOOL wantsChangedOwner; BOOL hasGNUDataForType; BOOL hasStdDataForType; } + (PasteboardData*) newWithType: (NSString*)aType owner: (id)anObject pboard: (id)anotherObject wantsChangedOwner: (BOOL)wants hasStdDataForType: (BOOL)StdData hasGNUDataForType: (BOOL)GNUData; - (void) checkConnection: (NSConnection*)c; - (NSData*) data; - (NSData*) newDataWithVersion: (int)version; - (id) owner; - (id) pboard; - (void) setData: (NSData*)d; - (NSString*) type; - (BOOL) wantsChangedOwner; @end @implementation PasteboardData + (PasteboardData*) newWithType: (NSString*)aType owner: (id)anObject pboard: (id)anotherObject wantsChangedOwner: (BOOL)wants hasStdDataForType: (BOOL)StdData hasGNUDataForType: (BOOL)GNUData; { PasteboardData* d = [PasteboardData alloc]; if (d) { d->type = [aType retain]; d->owner = [anObject retain]; d->pboard = [anotherObject retain]; d->wantsChangedOwner = wants; d->hasStdDataForType = StdData; d->hasGNUDataForType = GNUData; } return d; } - (void) checkConnection: (NSConnection*)c { id o; if (owner && [owner isProxy] && [owner connectionForProxy] == c) { o = owner; owner = nil; [o release]; o = pboard; pboard = nil; [o release]; } if (pboard && [pboard isProxy] && [pboard connectionForProxy] == c) { o = owner; owner = nil; [o release]; o = pboard; pboard = nil; [o release]; } } - (void) dealloc { [type release]; [data release]; [owner release]; [pboard release]; [super dealloc]; } - (NSData*) data { if (verbose) { printf("get data for %x\n", (unsigned)self); } return data; } - (NSData*) newDataWithVersion: (int)version { if (data == nil && (owner && pboard)) { if (hasGNUDataForType) { [pboard askOwner:owner toProvideDataForType: type andVersion: version]; } else if (hasStdDataForType) { [pboard askOwner:owner toProvideDataForType: type]; } } return [self data]; } - (id) owner { return owner; } - (id) pboard { return pboard; } - (void) setData: (NSData*)d { if (verbose) { printf("set data for %x\n", (unsigned)self); } [d retain]; [data release]; data = d; } - (id) type { return type; } - (BOOL) wantsChangedOwner { return wantsChangedOwner; } @end @interface PasteboardEntry: NSObject { int refNum; BOOL hasBeenFiltered; id owner; id pboard; NSMutableArray *items; BOOL wantsChangedOwner; BOOL hasGNUDataForType; BOOL hasStdDataForType; } + (PasteboardEntry*) newWithTypes: (NSString*)someTypes owner: (id)anOwner pboard: (id)aPboard ref: (int)count; - (void) addTypes: (NSArray*)types owner: (id)owner pasteboard: (id)pb; - connectionBecameInvalid: notification; - (BOOL) hasBeenFiltered; - (PasteboardData*) itemForType: (NSString*)type; - (void) lostOwnership; - (int) refNum; - (NSArray*) types; @end @implementation PasteboardEntry + (PasteboardEntry*) newWithTypes: (NSString*)someTypes owner: (id)anOwner pboard: (id)aPboard ref: (int)count { PasteboardEntry* e = [PasteboardEntry alloc]; if (e) { int i; e->owner = [anOwner retain]; e->pboard = [aPboard retain]; if (anOwner && [anOwner respondsToSelector: @selector(pasteboardChangedOwner:)]) { e->wantsChangedOwner = YES; } if (anOwner && [anOwner respondsToSelector: @selector(pasteboard:provideDataForType:)]) { e->hasStdDataForType = YES; } if (anOwner && [anOwner respondsToSelector: @selector(pasteboard:provideDataForType:andVersion:)]) { e->hasGNUDataForType = YES; } e->items = [[NSMutableArray alloc] initWithCapacity:[someTypes count]]; for (i = 0; i < [someTypes count]; i++) { NSString* type = [someTypes objectAtIndex:i]; PasteboardData* d; d = [PasteboardData newWithType: type owner: anOwner pboard: aPboard wantsChangedOwner: e->wantsChangedOwner hasStdDataForType: e->hasStdDataForType hasGNUDataForType: e->hasGNUDataForType]; [e->items addObject:d]; [d release]; } e->refNum = count; } return e; } - (void) addTypes: newTypes owner: owner pasteboard: pb { int i; BOOL wants = NO; BOOL StdData = NO; BOOL GNUData = NO; if (owner && [owner respondsToSelector: @selector(pasteboardChangedOwner:)]) { wants = YES; } if (owner && [owner respondsToSelector: @selector(pasteboard:provideDataForType:)]) { StdData = YES; } if (owner && [owner respondsToSelector: @selector(pasteboard:provideDataForType:andVersion:)]) { GNUData = YES; } for (i = 0; i < [newTypes count]; i++) { NSString* type = (NSString*)[newTypes objectAtIndex:i]; if ([self itemForType:type] == nil) { PasteboardData* d; d = [PasteboardData newWithType: type owner: owner pboard: pb wantsChangedOwner: wants hasStdDataForType: StdData hasGNUDataForType: GNUData]; [items addObject:d]; [d release]; } } } - (void) checkConnection: (NSConnection*)c { unsigned int i; id o; if (owner && [owner isProxy] && [owner connectionForProxy] == c) { o = owner; owner = nil; [o release]; o = pboard; pboard = nil; [o release]; } if (pboard && [pboard isProxy] && [pboard connectionForProxy] == c) { o = owner; owner = nil; [o release]; o = pboard; pboard = nil; [o release]; } for (i = [items count]; i > 0; i--) { PasteboardData* d = [items objectAtIndex: i-1]; [d checkConnection: c]; if ([d data] == nil && [d owner] == nil) { [items removeObjectAtIndex:i-1]; } } } - (void) dealloc { [owner release]; [pboard release]; [items release]; [super dealloc]; } - (BOOL) hasBeenFiltered { return hasBeenFiltered; } - (PasteboardData*) itemForType: (NSString*)type { unsigned i; for (i = 0; i < [items count]; i++) { PasteboardData* d = [items objectAtIndex:i]; if ([[d type] isEqual: type]) { return d; } } return nil; } - (void) lostOwnership { NSMutableArray* a = [NSMutableArray arrayWithCapacity:4]; unsigned i; NS_DURING { if (wantsChangedOwner) { [a addObject: owner]; } for (i = 0; i < [items count]; i++) { PasteboardData* d = [items objectAtIndex:i]; if ([d wantsChangedOwner] && [a containsObject: [d owner]] == NO) { [a addObject: [d owner]]; } } if (wantsChangedOwner) { [a removeObject: owner]; [owner pasteboardChangedOwner: pboard]; } for (i = 0; i < [items count] && [a count] > 0; i++) { PasteboardData* d = [items objectAtIndex:i]; id o = [d owner]; if ([a containsObject: o]) { [o pasteboardChangedOwner: [d pboard]]; [a removeObject: o]; } } } NS_HANDLER { NSLog(@"Error informing objects of ownership change - %@\n", [localException reason]); } NS_ENDHANDLER } - (int) refNum { return refNum; } - (NSArray*) types { NSMutableArray* t = [NSMutableArray arrayWithCapacity: [items count]]; unsigned int i; for (i = 0; i < [items count]; i++) { PasteboardData* d = [items objectAtIndex:i]; [t addObject: [d type]]; } return t; } @end @interface PasteboardObject: NSObject { NSString *name; int nextCount; unsigned histLength; NSMutableArray *history; PasteboardEntry *current; } + (PasteboardObject*) pasteboardWithName: (NSString*)name; - (int) addTypes: (NSArray*)types owner: (id)owner pasteboard: (id)pboard oldCount: (int)count; - (NSString*) availableTypeFromArray: (NSArray*)types changeCount: (int*)count; - (int) changeCount; - (void) checkConnection: (NSConnection*)c; - (NSData*) dataForType: (NSString*)type oldCount: (int)count; - (int) declareTypes: (NSArray*)types owner: (id)owner pasteboard: (id)pboard; - (PasteboardEntry*) entryByCount: (int)count; - (NSString*) name; - (void) releaseGlobally; - (BOOL) setData: (NSData*)data forType: (NSString*)type isFile: (BOOL)flag oldCount: (int)count; - (void) setHistory: (unsigned)length; - (NSArray*) typesAndChangeCount: (int*)count; @end @implementation PasteboardObject + (void) initialize { pasteboards = [[NSMutableDictionary alloc] initWithCapacity:8]; dictionary_lock = [[NSLock alloc] init]; } + (PasteboardObject*) pasteboardWithName: (NSString*)aName { static int number = 0; PasteboardObject* pb; [dictionary_lock lock]; while (aName == nil) { aName = [NSString stringWithFormat: @"%dlocalName", number++]; if ([pasteboards objectForKey:aName] == nil) { break; // This name is unique. } else { aName = nil; // Name already in use - try another. } } pb = [pasteboards objectForKey: aName]; if (pb == nil) { pb = [PasteboardObject alloc]; pb->name = [aName retain]; pb->nextCount = 1; pb->histLength = 1; pb->history = [[NSMutableArray alloc] initWithCapacity:2]; pb->current = nil; [pasteboards setObject: pb forKey: aName]; [pb autorelease]; } [dictionary_lock unlock]; return pb; } - (int) addTypes: (NSArray*)types owner: (id)owner pasteboard: (NSPasteboard*)pb oldCount: (int)count { PasteboardEntry *e = [self entryByCount:count]; if (e) { [e addTypes: types owner: owner pasteboard: pb]; return count; } return 0; } - (NSString*) availableTypeFromArray: (NSArray*)types changeCount: (int*)count { PasteboardEntry *e = nil; if (*count <= 0) { e = current; } else { e = [self entryByCount:*count]; } if (e) { unsigned i; *count = [e refNum]; for (i = 0; i < [types count]; i++) { NSString* key = [types objectAtIndex:i]; if ([e itemForType: key] != nil) { return key; } } } return nil; } - (int) changeCount { if (current) { return [current refNum]; } return 0; } - (void) checkConnection: (NSConnection*)c { unsigned int i; for (i = 0; i < [history count]; i++) { [[history objectAtIndex: i] checkConnection: c]; } } - (NSData*) dataForType: (NSString*)type oldCount: (int)count mustBeCurrent: (BOOL)flag { PasteboardEntry *e = nil; if (flag) { e = current; } else { e = [self entryByCount:count]; } if (verbose) { printf("get data for type '%s' version %d\n", [type cStringNoCopy], e ? [e refNum] : -1); } if (e) { PasteboardData* d = [e itemForType: type]; if (d) { return [d newDataWithVersion: [e refNum]]; } } return nil; } - (void) dealloc { [name release]; [history release]; [super dealloc]; } - (int) declareTypes: (bycopy NSArray*)types owner: (id)owner pasteboard: (NSPasteboard*)pb { PasteboardEntry* old = [current retain]; current = [PasteboardEntry newWithTypes:types owner:owner pboard:pb ref:nextCount++]; [history addObject:current]; [current release]; if ([history count] > histLength) { [history removeObjectAtIndex:0]; } [old lostOwnership]; [old release]; return [current refNum]; } - (PasteboardEntry*) entryByCount: (int)count { if (current == nil) { return nil; } else if ([current refNum] == count) { return current; } else { int i; for (i = 0; i < [history count]; i++) { if ([[history objectAtIndex:i] refNum] == count) { return (PasteboardEntry*)[history objectAtIndex:i]; } } return nil; } } - (NSString*) name { return name; } - (void) releaseGlobally { if ([name isEqual: NSDragPboard]) return; if ([name isEqual: NSFindPboard]) return; if ([name isEqual: NSFontPboard]) return; if ([name isEqual: NSGeneralPboard]) return; if ([name isEqual: NSRulerPboard]) return; [pasteboards removeObjectForKey: name]; } - (BOOL) setData: (NSData*)data forType: (NSString*)type isFile: (BOOL)flag oldCount: (int)count { PasteboardEntry* e; if (verbose) { printf("set data for type '%s' version %d\n", [type cStringNoCopy], count); } e = [self entryByCount: count]; if (e) { PasteboardData* d; if (flag) { d = [e itemForType: NSFileContentsPboardType]; if (d) { [d setData: data]; } else { return NO; } if (type && [type isEqual: NSFileContentsPboardType] == NO) { d = [e itemForType: type]; if (d) { [d setData: data]; } else { return NO; } } return YES; } else if (type) { d = [e itemForType: type]; if (d) { [d setData: data]; return YES; } else { return NO; } } else { return NO; } } else { return NO; } } - (void) setHistory: (unsigned)length { if (length < 1) length = 1; if (length > MAXHIST) length = MAXHIST; histLength = length; if (length < histLength) { while ([history count] > histLength) { [history removeObjectAtIndex:0]; } } } - (NSArray*) typesAndChangeCount: (int*)count { PasteboardEntry *e = nil; if (*count <= 0) { e = current; } else { e = [self entryByCount:*count]; } if (e) { *count = [e refNum]; return [e types]; } return nil; } @end @interface PasteboardServer : NSObject { NSMutableArray* permenant; } - (NSConnection*) connection: ancestor didConnect: newConn; - connectionBecameInvalid: notification; - (id) pasteboardByFilteringData: (NSData*)data ofType: (NSString*)type isFile: (BOOL)flag; - (id) pasteboardByFilteringTypesInPasteboard: pb; - (id) pasteboardWithName: (NSString*)name; - (id) pasteboardWithUniqueName; - (NSArray*) typesFilterableTo: (NSString*)type; @end @implementation PasteboardServer - (NSConnection*) connection: ancestor didConnect: newConn { [NotificationDispatcher addObserver: self selector: @selector(connectionBecameInvalid:) name: NSConnectionDidDieNotification object: newConn]; [newConn setDelegate: self]; return newConn; } - connectionBecameInvalid: notification { id connection = [notification object]; if (connection == conn) { NSLog(@"Help - pasteboard server connection has died!\n"); exit(1); } if ([connection isKindOf: [NSConnection class]]) { NSEnumerator *e = [pasteboards objectEnumerator]; PasteboardObject *o; while ((o = [e nextObject]) != nil) { [o checkConnection: connection]; } } return self; } - (void)dealloc { [permenant release]; [super dealloc]; } - init { self = [super init]; if (self) { permenant = [[NSMutableArray alloc] initWithCapacity:5]; /* * Create all the pasteboards which must persist forever and add them * to a local array. */ [permenant addObject: [self pasteboardWithName: NSGeneralPboard]]; [permenant addObject: [self pasteboardWithName: NSFontPboard]]; [permenant addObject: [self pasteboardWithName: NSRulerPboard]]; [permenant addObject: [self pasteboardWithName: NSFindPboard]]; [permenant addObject: [self pasteboardWithName: NSDragPboard]]; } return self; } - (id) pasteboardByFilteringData: (NSData*)data ofType: (NSString*)type isFile: (BOOL)flag { [self notImplemented: _cmd]; return nil; } - (id) pasteboardByFilteringTypesInPasteboard: pb { [self notImplemented: _cmd]; return nil; } - (id) pasteboardWithName: (NSString*)name { return [PasteboardObject pasteboardWithName: name]; } - (id) pasteboardWithUniqueName { return [PasteboardObject pasteboardWithName: nil]; } - (NSArray*) typesFilterableTo: (NSString*)type { [self notImplemented: _cmd]; return nil; } @end static int ihandler(int sig) { abort(); } static void init(int argc, char** argv) { const char *options = "Hdv"; int sym; while ((sym = getopt(argc, argv, options)) != -1) { switch(sym) { case 'H': printf("%s -[%s]\n", argv[0], options); printf("GNU Pasteboard server\n"); printf("-H for help\n"); printf("-d avoid fork() to make debugging easy\n"); printf("-v More verbose debug output\n"); exit(0); case 'd': debug++; break; case 'v': verbose++; break; default: printf("%s - GNU Pasteboard server\n", argv[0]); printf("-H for help\n"); exit(0); } } for (sym = 0; sym < 32; sym++) { signal(sym, ihandler); } signal(SIGPIPE, SIG_IGN); signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGTERM, ihandler); if (debug == 0) { /* * Now fork off child process to run in background. */ switch (fork()) { case -1: NSLog(@"gpbs - fork failed - bye.\n"); exit(1); case 0: /* * Try to run in background. */ #ifdef NeXT setpgrp(0, getpid()); #else setsid(); #endif break; default: if (verbose) { NSLog(@"Process backgrounded (running as daemon)\r\n"); } exit(0); } } } int main(int argc, char** argv) { init(argc, argv); [NSObject enableDoubleReleaseCheck: YES]; server = [[PasteboardServer alloc] init]; if (server == nil) { NSLog(@"Unable to create server object.\n"); exit(1); } /* Register a connection that provides the server object to the network */ conn = [NSConnection newRegisteringAtName:PBSNAME withRootObject:server]; if (conn == nil) { NSLog(@"Unable to register with name server.\n"); exit(1); } [conn setDelegate:server]; [NotificationDispatcher addObserver: server selector: @selector(connectionBecameInvalid:) name: NSConnectionDidDieNotification object: conn]; if (verbose) { NSLog(@"GNU pasteboard server startup.\n"); } [[NSRunLoop currentRunLoop] run]; exit(0); }