From dd714bfce853591de0d10464679b4b37f0bb98d3 Mon Sep 17 00:00:00 2001 From: Stefan Schimanski Date: Sun, 3 Feb 2008 10:41:23 +0000 Subject: [PATCH] * LinkBack support files, now in pure Objective-C (without any Objective-C++ which is not supported by automake) git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@22761 a592a061-630c-0410-9148-cb99ea01b6c8 --- configure.ac | 4 + development/cmake/src/support/CMakeLists.txt | 23 +- src/support/Makefile.am | 10 + src/support/linkback/LinkBack.h | 165 +++++++ src/support/linkback/LinkBack.m | 445 +++++++++++++++++++ src/support/linkback/LinkBackProxy.h | 32 ++ src/support/linkback/LinkBackProxy.m | 231 ++++++++++ src/support/linkback/LinkBackServer.h | 67 +++ src/support/linkback/LinkBackServer.m | 281 ++++++++++++ 9 files changed, 1252 insertions(+), 6 deletions(-) create mode 100644 src/support/linkback/LinkBack.h create mode 100644 src/support/linkback/LinkBack.m create mode 100644 src/support/linkback/LinkBackProxy.h create mode 100644 src/support/linkback/LinkBackProxy.m create mode 100644 src/support/linkback/LinkBackServer.h create mode 100644 src/support/linkback/LinkBackServer.m diff --git a/configure.ac b/configure.ac index 1f176e78b5..97bf6bcfe6 100644 --- a/configure.ac +++ b/configure.ac @@ -88,6 +88,10 @@ dnl LYX_CXX_RTTI dnl AC_CHECK_HEADERS(ostream istream sstream locale limits ios) dnl LYX_CXX_STL_MODERN_STREAMS +### Objective-C compiler +AC_PROG_OBJC +_AM_DEPENDENCIES([OBJC]) + ### and now some special lyx flags. AC_ARG_ENABLE(assertions, AC_HELP_STRING([--enable-assertions],[add runtime sanity checks in the program]),, diff --git a/development/cmake/src/support/CMakeLists.txt b/development/cmake/src/support/CMakeLists.txt index 0500e4026c..46ca63eecb 100644 --- a/development/cmake/src/support/CMakeLists.txt +++ b/development/cmake/src/support/CMakeLists.txt @@ -13,6 +13,9 @@ file(GLOB support_minizip_sources ${TOP_SRC_DIR}/src/support/minizip/*.c) file(GLOB support_minizip_cpp_sources ${TOP_SRC_DIR}/src/support/minizip/*.cpp) file(GLOB support_minizip_headers ${TOP_SRC_DIR}/src/support/minizip/*.h) +file(GLOB support_linkback_sources ${TOP_SRC_DIR}/src/support/linkback/*.m*) +file(GLOB support_linkback_headers ${TOP_SRC_DIR}/src/support/linkback/*.h) + list(REMOVE_ITEM support_sources ${TOP_SRC_DIR}/src/support/os_win32.cpp ${TOP_SRC_DIR}/src/support/os_unix.cpp @@ -24,6 +27,13 @@ list(REMOVE_ITEM support_sources ${TOP_SRC_DIR}/src/support/minizip/iowin32.c ${TOP_SRC_DIR}/src/support/gettext.cpp) +if(APPLE) + message(STATUS "Mac LinkBack support") +else() + set(support_linkback_sources "") + set(support_linkback_headers "") +endif() + # needed to compile tex2lyx in merged mode set(dont_merge ${TOP_SRC_DIR}/src/support/gettext.cpp) @@ -40,8 +50,8 @@ include_directories(${TOP_SRC_DIR}/src/support if(NOT MERGE_FILES) - set(support_sources ${support_sources} ${support_minizip_sources} ${support_minizip_cpp_sources}) - set(support_headers ${support_headers} ${support_minizip_headers}) + set(support_sources ${support_sources} ${support_minizip_sources} ${support_minizip_cpp_sources} ${support_linkback_sources}) + set(support_headers ${support_headers} ${support_minizip_headers} ${support_linkback_headers}) add_library(support ${library_type} ${support_sources} ${support_headers} ${dont_merge}) else() lyx_const_touched_files(_allinone support_sources) @@ -51,13 +61,14 @@ else() set_source_files_properties(_allinone_touched.C PROPERTIES OBJECT_DEPENDS "${depends_moc}") add_library(support ${library_type} ${_allinone_files} - ${support_minizip_sources} ${support_minizip_cpp_sources} ${support_headers} ${dont_merge}) + ${support_minizip_sources} ${support_minizip_cpp_sources} ${support_linkback_sources} ${support_headers} ${dont_merge}) endif() +target_link_libraries(support boost_signals ${QT_QTCORE_LIBRARY} ${ZLIB_LIBRARY}) -target_link_libraries(support boost_signals ${QT_QTCORE_LIBRARY} ${ZLIB_LIBRARY} ) - -if(WIN32) +if(APPLE) + target_link_libraries(support "objc" "-framework Appkit" "-framework CoreFoundation") +elseif(WIN32) target_link_libraries(support shlwapi) endif() diff --git a/src/support/Makefile.am b/src/support/Makefile.am index 9db3a24fb6..5744f815fa 100644 --- a/src/support/Makefile.am +++ b/src/support/Makefile.am @@ -111,6 +111,16 @@ liblyxsupport_la_SOURCES = \ minizip/zip.h \ minizip/zipunzip.cpp +if INSTALL_MACOSX +liblyxsupport_la_SOURCES += \ + linkback/LinkBack.h \ + linkback/LinkBack.m \ + linkback/LinkBackProxy.h \ + linkback/LinkBackProxy.m \ + linkback/LinkBackServer.h \ + linkback/LinkBackServer.m +endif + ############################## Tests ################################## EXTRA_DIST += \ diff --git a/src/support/linkback/LinkBack.h b/src/support/linkback/LinkBack.h new file mode 100644 index 0000000000..ccf3274bfa --- /dev/null +++ b/src/support/linkback/LinkBack.h @@ -0,0 +1,165 @@ +// +// LinkBack.h +// LinkBack Project +// +// Created by Charles Jolley on Tue Jun 15 2004. +// Copyright (c) 2004, Nisus Software, Inc. +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// Neither the name of the Nisus Software, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + + +#import + +// Use this pasteboard type to put LinkBack data to the pasteboard. Use MakeLinkBackData() to create the data. +extern NSString* LinkBackPboardType ; + +// Default Action Names. These will be localized for you automatically. +extern NSString* LinkBackEditActionName ; +extern NSString* LinkBackRefreshActionName ; + +// +// Support Functions +// +NSString* LinkBackUniqueItemKey() ; +NSString* LinkBackEditMultipleMenuTitle() ; +NSString* LinkBackEditNoneMenuTitle() ; + +// +// Deprecated Support Functions -- use LinkBack Data Category instead +// +id MakeLinkBackData(NSString* serverName, id appData) ; +id LinkBackGetAppData(id linkBackData) ; +BOOL LinkBackDataBelongsToActiveApplication(id data) ; + +// +// LinkBack Data Category +// + +// Use these methods to create and access linkback data objects. You can also use the helper functions above. + +@interface NSDictionary (LinkBackData) + ++ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData ; + ++ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData suggestedRefreshRate:(NSTimeInterval)rate ; + ++ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData actionName:(NSString*)action suggestedRefreshRate:(NSTimeInterval)rate ; + +- (BOOL)linkBackDataBelongsToActiveApplication ; + +- (id)linkBackAppData ; +- (NSString*)linkBackSourceApplicationName ; +- (NSString*)linkBackActionName ; +- (NSString*)linkBackVersion ; +- (NSURL*)linkBackApplicationURL ; + +- (NSTimeInterval)linkBackSuggestedRefreshRate ; + +- (NSString*)linkBackEditMenuTitle ; + +@end + +// +// Delegate Protocols +// + +@class LinkBack ; + +@protocol LinkBackServerDelegate +- (void)linkBackDidClose:(LinkBack*)link ; +- (void)linkBackClientDidRequestEdit:(LinkBack*)link ; +@end + +@protocol LinkBackClientDelegate +- (void)linkBackDidClose:(LinkBack*)link ; +- (void)linkBackServerDidSendEdit:(LinkBack*)link ; +@end + +// used for cross app communications +@protocol LinkBack +- (oneway void)remoteCloseLink ; +- (void)requestEditWithPasteboardName:(bycopy NSString*)pboardName ; // from client +- (void)refreshEditWithPasteboardName:(bycopy NSString*)pboardName ; // from server +@end + +@interface LinkBack : NSObject { + LinkBack* peer ; // the client or server on the other side. + BOOL isServer ; + id delegate ; + NSPasteboard* pboard ; + id repobj ; + NSString* sourceName ; + NSString* sourceApplicationName ; + NSString* key ; +} + ++ (LinkBack*)activeLinkBackForItemKey:(id)key ; +// works for both the client and server side. Valid only while a link is connected. + +// ........................................................................... +// General Use methods +// +- (NSPasteboard*)pasteboard ; +- (void)closeLink ; + +- (id)representedObject ; +- (void)setRepresentedObject:(id)obj ; +// Applications can use this represented object to attach some meaning to the live link. For example, a client application may set this to the object to be modified when the edit is refreshed. This retains its value. + +- (NSString*)sourceName ; +- (NSString*)sourceApplicationName ; +- (NSString*)itemKey ; // maybe this matters only on the client side. + +// ........................................................................... +// Server-side methods +// ++ (BOOL)publishServerWithName:(NSString*)name delegate:(id)del ; + ++ (void)retractServerWithName:(NSString*)name ; + +- (void)sendEdit ; + +// ........................................................................... +// Client-Side Methods +// ++ (LinkBack*)editLinkBackData:(id)data sourceName:(NSString*)aName delegate:(id)del itemKey:(NSString*)aKey ; + +@end + +@interface LinkBack (InternalUseOnly) + +- (id)initServerWithClient: (LinkBack*)aLinkBack delegate: (id)aDel ; + +- (id)initClientWithSourceName:(NSString*)aName delegate:(id)aDel itemKey:(NSString*)aKey ; + +- (BOOL)connectToServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier fallbackURL:(NSURL*)url appName:(NSString*)appName ; + +- (void)requestEdit ; + +@end diff --git a/src/support/linkback/LinkBack.m b/src/support/linkback/LinkBack.m new file mode 100644 index 0000000000..063f61a489 --- /dev/null +++ b/src/support/linkback/LinkBack.m @@ -0,0 +1,445 @@ +// +// LinkBack.m +// LinkBack +// +// Created by Charles Jolley on Tue Jun 15 2004. +// Copyright (c) 2004, Nisus Software, Inc. +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// Neither the name of the Nisus Software, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "LinkBack.h" +#import "LinkBackServer.h" + +NSString* LinkBackPboardType = @"LinkBackData" ; + +// LinkBack data keys. These are used in a LinkBack object, which is currently a dictionary. Do not depend on these values. They are public for testing purposes only. +NSString* LinkBackServerActionKey = @"serverActionKey" ; +NSString* LinkBackServerApplicationNameKey = @"serverAppName" ; +NSString* LinkBackServerNameKey = @"serverName" ; +NSString* LinkBackServerBundleIdentifierKey = @"bundleId" ; +NSString* LinkBackVersionKey = @"version" ; +NSString* LinkBackApplicationDataKey = @"appData" ; +NSString* LinkBackSuggestedRefreshKey = @"refresh" ; +NSString* LinkBackApplicationURLKey = @"ApplicationURL" ; + +NSString* LinkBackEditActionName = @"_Edit" ; +NSString* LinkBackRefreshActionName = @"_Refresh" ; + +// ........................................................................... +// Support Functions +// + +id MakeLinkBackData(NSString* serverName, id appData) +{ + return [NSDictionary linkBackDataWithServerName: serverName appData: appData] ; +} + +id LinkBackGetAppData(id LinkBackData) +{ + return [LinkBackData linkBackAppData] ; +} + +NSString* LinkBackUniqueItemKey() +{ + static int counter = 0 ; + + NSString* base = [[NSBundle mainBundle] bundleIdentifier] ; + unsigned long time = [NSDate timeIntervalSinceReferenceDate] ; + return [NSString stringWithFormat: @"%@%.8x.%.4x",base,time,counter++] ; +} + +BOOL LinkBackDataBelongsToActiveApplication(id data) +{ + return [data linkBackDataBelongsToActiveApplication] ; +} + +NSString* LinkBackEditMultipleMenuTitle() +{ + NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ; + NSString* ret = [bundle localizedStringForKey: @"_EditMultiple" value: @"Edit LinkBack Items" table: @"Localized"] ; + return ret ; +} + +NSString* LinkBackEditNoneMenuTitle() +{ + NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ; + NSString* ret = [bundle localizedStringForKey: @"_EditNone" value: @"Edit LinkBack Item" table: @"Localized"] ; + return ret ; +} + +// ........................................................................... +// LinkBack Data Category +// + +// Use these methods to create and access linkback data objects. You can also use the helper functions above. + +@implementation NSDictionary (LinkBackData) + ++ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData +{ + return [self linkBackDataWithServerName: serverName appData: appData actionName: nil suggestedRefreshRate: 0]; +} + ++ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData suggestedRefreshRate:(NSTimeInterval)rate +{ + return [self linkBackDataWithServerName: serverName appData: appData actionName: LinkBackRefreshActionName suggestedRefreshRate: rate] ; +} + ++ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData actionName:(NSString*)action suggestedRefreshRate:(NSTimeInterval)rate ; +{ + NSDictionary* appInfo = [[NSBundle mainBundle] infoDictionary] ; + + NSMutableDictionary* ret = [[NSMutableDictionary alloc] init] ; + NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ; + NSString* url = [appInfo objectForKey: @"LinkBackApplicationURL"] ; + NSString* appName = [[NSProcessInfo processInfo] processName] ; + id version = @"A" ; + + if (nil==serverName) [NSException raise: NSInvalidArgumentException format: @"LinkBack Data cannot be created without a server name."] ; + + // callback information + [ret setObject: bundleId forKey: LinkBackServerBundleIdentifierKey]; + [ret setObject: serverName forKey: LinkBackServerNameKey] ; + [ret setObject: version forKey: LinkBackVersionKey] ; + + // additional information + if (appName) [ret setObject: appName forKey: LinkBackServerApplicationNameKey] ; + if (action) [ret setObject: action forKey: LinkBackServerActionKey] ; + if (appData) [ret setObject: appData forKey: LinkBackApplicationDataKey] ; + if (url) [ret setObject: url forKey: LinkBackApplicationURLKey] ; + [ret setObject: [NSNumber numberWithFloat: rate] forKey: LinkBackSuggestedRefreshKey] ; + + return [ret autorelease] ; +} + +- (BOOL)linkBackDataBelongsToActiveApplication +{ + NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ; + NSString* dataId = [self objectForKey: LinkBackServerBundleIdentifierKey] ; + return (dataId && [dataId isEqualToString: bundleId]) ; +} + +- (id)linkBackAppData +{ + return [self objectForKey: LinkBackApplicationDataKey] ; +} + +- (NSString*)linkBackSourceApplicationName +{ + return [self objectForKey: LinkBackServerApplicationNameKey] ; +} + +- (NSString*)linkBackActionName +{ + NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ; + NSString* ret = [self objectForKey: LinkBackServerActionKey] ; + if (nil==ret) ret = LinkBackEditActionName ; + + ret = [bundle localizedStringForKey: ret value: ret table: @"Localized"] ; + return ret ; +} + +- (NSString*)linkBackEditMenuTitle +{ + NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ; + NSString* appName = [self linkBackSourceApplicationName] ; + NSString* action = [self linkBackActionName] ; + NSString* ret = [bundle localizedStringForKey: @"_EditPattern" value: @"%@ in %@" table: @"Localized"] ; + ret = [NSString stringWithFormat: ret, action, appName] ; + return ret ; +} + +- (NSString*)linkBackVersion +{ + return [self objectForKey: LinkBackVersionKey] ; +} + +- (NSTimeInterval)linkBackSuggestedRefreshRate +{ + id obj = [self objectForKey: LinkBackSuggestedRefreshKey] ; + return (obj) ? [obj floatValue] : 0 ; +} + +- (NSURL*)linkBackApplicationURL +{ + id obj = [self objectForKey: LinkBackApplicationURLKey] ; + if (obj) obj = [NSURL URLWithString: obj] ; + return obj ; +} + +@end + +// ........................................................................... +// LinkBackServer +// +// one of these exists for each registered server name. This is the receiver of server requests. + +// ........................................................................... +// LinkBack Class +// + +NSMutableDictionary* keyedLinkBacks = nil ; + +@implementation LinkBack + ++ (void)initialize +{ + static BOOL inited = NO ; + if (inited) return ; + inited=YES; [super initialize] ; + keyedLinkBacks = [[NSMutableDictionary alloc] init] ; +} + ++ (LinkBack*)activeLinkBackForItemKey:(id)aKey +{ + return [keyedLinkBacks objectForKey: aKey] ; +} + +- (id)initServerWithClient: (LinkBack*)aLinkBack delegate: (id)aDel +{ + if (self = [super init]) { + peer = [aLinkBack retain] ; + sourceName = [[peer sourceName] copy] ; + sourceApplicationName = [[peer sourceApplicationName] copy] ; + key = [[peer itemKey] copy] ; + isServer = YES ; + delegate = aDel ; + [keyedLinkBacks setObject: self forKey: key] ; + } + + return self ; +} + +- (id)initClientWithSourceName:(NSString*)aName delegate:(id)aDel itemKey:(NSString*)aKey ; +{ + if (self = [super init]) { + isServer = NO ; + delegate = aDel ; + sourceName = [aName copy] ; + sourceApplicationName = [[NSProcessInfo processInfo] processName] ; + pboard = [[NSPasteboard pasteboardWithUniqueName] retain] ; + key = [aKey copy] ; + } + + return self ; +} + +- (void)dealloc +{ + [repobj release] ; + [sourceName release] ; + + if (peer) [self closeLink] ; + [peer release] ; + + if (!isServer) [pboard releaseGlobally] ; // client owns the pboard. + [pboard release] ; + + [super dealloc] ; +} + +// ........................................................................... +// General Use methods + +- (NSPasteboard*)pasteboard +{ + return pboard ; +} + +- (id)representedObject +{ + return repobj ; +} + +- (void)setRepresentedObject:(id)obj +{ + [obj retain] ; + [repobj release] ; + repobj = obj ; +} + +- (NSString*)sourceName +{ + return sourceName ; +} + +- (NSString*)sourceApplicationName +{ + return sourceApplicationName ; +} + +- (NSString*)itemKey +{ + return key ; +} + +// this method is called to initial a link closure from this side. +- (void)closeLink +{ + // inform peer of closure + if (peer) { + [peer remoteCloseLink] ; + [peer release] ; + peer = nil ; + [self release] ; + [keyedLinkBacks removeObjectForKey: [self itemKey]]; + } +} + +// this method is called whenever the link is about to be or has been closed by the other side. +- (void)remoteCloseLink +{ + if (peer) { + [peer release] ; + peer = nil ; + [self release] ; + [keyedLinkBacks removeObjectForKey: [self itemKey]]; + } + + if (delegate) [delegate linkBackDidClose: self] ; +} + +// ........................................................................... +// Server-side methods +// ++ (BOOL)publishServerWithName:(NSString*)name delegate:(id)del +{ + return [LinkBackServer publishServerWithName: name delegate: del] ; +} + ++ (void)retractServerWithName:(NSString*)name +{ + LinkBackServer* server = [LinkBackServer LinkBackServerWithName: name] ; + if (server) [server retract] ; +} + +- (void)sendEdit +{ + if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ; + [peer refreshEditWithPasteboardName: [pboard name]] ; +} + +// FROM CLIENT LinkBack +- (void)requestEditWithPasteboardName:(bycopy NSString*)pboardName +{ + // get the new pasteboard, if needed + if ((!pboard) || ![pboardName isEqualToString: [pboard name]]) pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ; + + // pass onto delegate + [delegate performSelectorOnMainThread: @selector(linkBackClientDidRequestEdit:) withObject: self waitUntilDone: NO] ; +} + +// ........................................................................... +// Client-Side Methods +// ++ (LinkBack*)editLinkBackData:(id)data sourceName:(NSString*)aName delegate:(id)del itemKey:(NSString*)aKey +{ + // if an active live link already exists, use that. Otherwise, create a new one. + LinkBack* ret = [keyedLinkBacks objectForKey: aKey] ; + + if(nil==ret) { + BOOL ok ; + NSString* serverName ; + NSString* serverId ; + NSString* appName ; + NSURL* url ; + + // collect server contact information from data. + ok = [data isKindOfClass: [NSDictionary class]] ; + if (ok) { + serverName = [data objectForKey: LinkBackServerNameKey] ; + serverId = [data objectForKey: LinkBackServerBundleIdentifierKey]; + appName = [data linkBackSourceApplicationName] ; + url = [data linkBackApplicationURL] ; + } + + if (!ok || !serverName || !serverId) [NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ; + + // create the live link object and try to connect to the server. + ret = [[LinkBack alloc] initClientWithSourceName: aName delegate: del itemKey: aKey] ; + + if (![ret connectToServerWithName: serverName inApplication: serverId fallbackURL: url appName: appName]) { + [ret release] ; + ret = nil ; + } + } + + // now with a live link in hand, request an edit + if (ret) { + // if connected to server, publish data and inform server. + NSPasteboard* my_pboard = [ret pasteboard] ; + [my_pboard declareTypes: [NSArray arrayWithObject: LinkBackPboardType] owner: ret] ; + [my_pboard setPropertyList: data forType: LinkBackPboardType] ; + + [ret requestEdit] ; + + // if connection to server failed, return nil. + } else { + [ret release] ; + ret = nil ; + } + + return ret ; +} + +- (BOOL)connectToServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier fallbackURL:(NSURL*)url appName:(NSString*)appName +{ + // get the LinkBackServer. + LinkBackServer* server = [LinkBackServer LinkBackServerWithName: aName inApplication: bundleIdentifier launchIfNeeded: YES fallbackURL: url appName: appName] ; + if (!server) return NO ; // failed to get server + + peer = [[server initiateLinkBackFromClient: self] retain] ; + if (!peer) return NO ; // failed to initiate session + + // if we connected, then add to the list of active keys + [keyedLinkBacks setObject: self forKey: [self itemKey]] ; + + return YES ; +} + +- (void)requestEdit +{ + if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ; + [peer requestEditWithPasteboardName: [pboard name]] ; +} + +// RECEIVED FROM SERVER +- (void)refreshEditWithPasteboardName:(bycopy NSString*)pboardName +{ + // if pboard has changes, change to new pboard. + if (![pboardName isEqualToString: [pboard name]]) { + [pboard release] ; + pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ; + } + + // inform delegate + [delegate performSelectorOnMainThread: @selector(linkBackServerDidSendEdit:) withObject: self waitUntilDone: NO] ; +} + +@end diff --git a/src/support/linkback/LinkBackProxy.h b/src/support/linkback/LinkBackProxy.h new file mode 100644 index 0000000000..a535aa4ff2 --- /dev/null +++ b/src/support/linkback/LinkBackProxy.h @@ -0,0 +1,32 @@ +// -*- C++ -*- +/** + * \file LinkBackProxy.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Stefan Schimanski + * + * Full author contact details are available in file CREDITS. + */ + +#ifndef LINKBACKPROXY_H +#define LINKBACKPROXY_H + +#ifdef __cplusplus +extern "C" { +#endif + +/// +int isLinkBackDataInPasteboard(); +/// +int editLinkBackFile(char const * filename); +/// +void getLinkBackData(void const ** buf, unsigned * len); +/// +void closeAllLinkBackLinks(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LINKBACKPROXY_H diff --git a/src/support/linkback/LinkBackProxy.m b/src/support/linkback/LinkBackProxy.m new file mode 100644 index 0000000000..c86649f02b --- /dev/null +++ b/src/support/linkback/LinkBackProxy.m @@ -0,0 +1,231 @@ +/** + * \file LinkBackProxy.mm + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Stefan Schimanski + * + * Full author contact details are available in file CREDITS. + */ + +#include + +#include "support/linkback/LinkBackProxy.h" + +#include "support/linkback/LinkBack.h" + +/////////////////////////////////////////////////////////////////////// + +static NSAutoreleasePool * pool = nil; + +@interface LyXLinkBackClient : NSObject { + NSMutableSet * keys; +} + ++ (void)load; +- (LyXLinkBackClient *)init; +- (BOOL)edit:(NSString *)fileName; +@end + +@implementation LyXLinkBackClient + ++ (void)load +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +- (LyXLinkBackClient *)init +{ + self = [super init]; + if (self != nil) + keys = [[NSMutableSet alloc] init]; + return self; +} + +- (void)dealloc { + // close all links + NSArray * allKeys = [keys allObjects]; + unsigned i; + for (i = 0; i < [allKeys count]; ++i) { + LinkBack * link + = [LinkBack activeLinkBackForItemKey:[allKeys objectAtIndex:i]]; + [link closeLink]; + } + [keys release]; + + [super dealloc]; +} + +- (BOOL)edit:(NSString *)fileName +{ + if ([LinkBack activeLinkBackForItemKey:fileName]) + return YES; + + @try { + // get put data, i.e. PDF + LinkBack + 4 bytes PDF-length + NSData * data = [NSData dataWithContentsOfFile:fileName]; + if (data == nil) { + NSLog(@"Cannot read file %@", fileName); + return NO; + } + + // Get linkback data which comes behind the pdf data. + // The pdf data length are the last 4 bytes. + UInt32 pdfLen = 0; + pdfLen = *(UInt32 const *)(((UInt8 const *)[data bytes]) + [data length] - 4); + pdfLen = NSSwapBigLongToHost(pdfLen); // make it big endian + if (pdfLen >= [data length] - 4) { + NSLog(@"Invalid file %@ for LinkBack", fileName); + return NO; + } + + NSData * linkBackData + = [data subdataWithRange:NSMakeRange(pdfLen, [data length] - pdfLen - 4)]; + if (linkBackData == nil) { + NSLog(@"Invalid file %@ for LinkBack", fileName); + return NO; + } + + NSMutableDictionary * linkBackDataDict + = [NSUnarchiver unarchiveObjectWithData:linkBackData]; + if (linkBackDataDict == nil) { + NSLog(@"LinkBack data in %@ corrupted"); + return NO; + } + + // create the link to the LinkBack server + LinkBack * link = [LinkBack editLinkBackData:linkBackDataDict + sourceName:fileName delegate:self itemKey:fileName]; + if (link == nil) { + NSLog(@"Failed to create LinkBack link for %@", fileName); + return NO; + } + [keys addObject:fileName]; + } + @catch (NSException * exception) { + NSLog(@"LinkBack exception: %@", exception); + return NO; + } + + return YES; +} + +- (void)linkBackDidClose:(LinkBack*)link +{ + NSString * fileName = [link itemKey]; + if (fileName) { + [keys removeObject:fileName]; + NSLog(@"LinkBack link for %@ closed", fileName); + } +} + +- (void)linkBackServerDidSendEdit:(LinkBack*)link +{ + @try { + // get pasteboard and check that linkback and pdf data is available + NSPasteboard * pboard = [link pasteboard]; + NSArray * linkBackType = [NSArray arrayWithObjects: LinkBackPboardType, nil]; + NSArray * pdfType = [NSArray arrayWithObjects: NSPDFPboardType, nil]; + if ([pboard availableTypeFromArray:linkBackType] == nil + || [pboard availableTypeFromArray:pdfType] == nil) { + NSLog(@"No PDF or LinkBack data in pasteboard"); + return; + } + + // get new linkback data + id linkBackDataDict = [pboard propertyListForType:LinkBackPboardType]; + if (linkBackDataDict == nil) { + NSLog(@"Cannot get LinkBack data from pasteboard"); + return; + } + NSData * linkBackData = [NSArchiver archivedDataWithRootObject:linkBackDataDict]; + if (linkBackData == nil) { + NSLog(@"Cannot archive LinkBack data"); + return; + } + + // get pdf + NSData * pdfData = [pboard dataForType:NSPDFPboardType]; + if (pdfData == nil) { + NSLog(@"Cannot get PDF data from pasteboard"); + return; + } + + // update the file + NSString * fileName = [link itemKey]; + NSFileHandle * file = [NSFileHandle fileHandleForUpdatingAtPath:fileName]; + if (file == nil) { + NSLog(@"LinkBack file %@ disappeared.", fileName); + return; + } + [file truncateFileAtOffset:0]; + [file writeData:pdfData]; + [file writeData:linkBackData]; + + UInt32 pdfLen = NSSwapHostLongToBig([pdfData length]); // big endian + NSData * lenData = [NSData dataWithBytes:&pdfLen length:4]; + [file writeData:lenData]; + [file closeFile]; + } + @catch (NSException * exception) { + NSLog(@"LinkBack exception in linkBackServerDidSendEdit: %@", exception); + } +} + +@end + + +/////////////////////////////////////////////////////////////////////// + +static LyXLinkBackClient * linkBackClient = nil; + +int isLinkBackDataInPasteboard() +{ + NSArray * linkBackType = [NSArray arrayWithObjects: LinkBackPboardType, nil]; + NSString * ret = [[NSPasteboard generalPasteboard] availableTypeFromArray:linkBackType]; + return ret != nil; +} + + +void getLinkBackData(void const * * buf, unsigned * len) +{ + // get linkback data from pasteboard + NSPasteboard * pboard = [NSPasteboard generalPasteboard]; + id linkBackData = [pboard propertyListForType:LinkBackPboardType]; + + NSData * nsdata + = [NSArchiver archivedDataWithRootObject:linkBackData]; + if (nsdata == nil) { + *buf = 0; + *len = 0; + return; + } + + *buf = [nsdata bytes]; + *len = [nsdata length]; +} + + +int editLinkBackFile(char const * docName) +{ + // setup Obj-C and our client + if (linkBackClient == nil) + linkBackClient = [[LyXLinkBackClient alloc] init]; + if (pool == nil) + pool = [[NSAutoreleasePool alloc] init]; + + // FIXME: really UTF8 here? + NSString * nsDocName = [NSString stringWithUTF8String:docName]; + return [linkBackClient edit:nsDocName] == YES; +} + + +void closeAllLinkBackLinks() +{ + [linkBackClient release]; + linkBackClient = nil; + + [pool release]; + pool = nil; +} + diff --git a/src/support/linkback/LinkBackServer.h b/src/support/linkback/LinkBackServer.h new file mode 100644 index 0000000000..b8b5c20bef --- /dev/null +++ b/src/support/linkback/LinkBackServer.h @@ -0,0 +1,67 @@ +// +// LinkBackServer.h +// LinkBack +// +// Created by Charles Jolley on Tue Jun 15 2004. +// Copyright (c) 2004, Nisus Software, Inc. +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// Neither the name of the Nisus Software, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import + +@class LinkBack ; +@protocol LinkBackServerDelegate, LinkBackClientDelegate ; + +@protocol LinkBackServer +- (LinkBack*)initiateLinkBackFromClient:(LinkBack*)clientLinkBack ; +@end + +// This method is used as the standard way of constructing the actual server name a live link connection is posted under. It is constructed from the name and identifier. +NSString* MakeLinkBackServerName(NSString* bundleIdentifier, NSString* name) ; + +// a LinkBack server is created for each published server. This simply responds to connection requests to create new live links. +@interface LinkBackServer : NSObject { + NSConnection* listener ; + NSString* name ; + id delegate ; +} + ++ (LinkBackServer*)LinkBackServerWithName:(NSString*)name ; ++ (BOOL)publishServerWithName:(NSString*)name delegate:(id)del ; + ++ (LinkBackServer*)LinkBackServerWithName:(NSString*)name inApplication:(NSString*)bundleIdentifier launchIfNeeded:(BOOL)flag fallbackURL:(NSURL*)url appName:(NSString*)appName ; + +// This method is used by clients to connect + +- (id)initWithName:(NSString*)name delegate:(id)aDel; + +- (BOOL)publish ; // creates the connection and adds to the list. +- (void)retract ; + +@end diff --git a/src/support/linkback/LinkBackServer.m b/src/support/linkback/LinkBackServer.m new file mode 100644 index 0000000000..f4fff31aed --- /dev/null +++ b/src/support/linkback/LinkBackServer.m @@ -0,0 +1,281 @@ +// +// LinkBackServer.m +// LinkBack +// +// Created by Charles Jolley on Tue Jun 15 2004. +// Copyright (c) 2004, Nisus Software, Inc. +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// Neither the name of the Nisus Software, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "LinkBackServer.h" +#import "LinkBack.h" + +NSString* MakeLinkBackServerName(NSString* bundleIdentifier, NSString* name) +{ + return [bundleIdentifier stringByAppendingFormat: @":%@",name] ; +} + +NSMutableDictionary* LinkBackServers = nil ; + +@implementation LinkBackServer + ++ (void)initialize +{ + static BOOL inited = NO ; + if (inited) return ; + + [super initialize] ; + inited = YES ; + + if (!LinkBackServers) LinkBackServers = [[NSMutableDictionary alloc] init]; +} + ++ (LinkBackServer*)LinkBackServerWithName:(NSString*)aName +{ + return [LinkBackServers objectForKey: aName] ; +} + ++ (BOOL)publishServerWithName:(NSString*)aName delegate:(id)del +{ + LinkBackServer* serv = [[LinkBackServer alloc] initWithName: aName delegate: del] ; + BOOL ret = [serv publish] ; // retains if successful + [serv release] ; + return ret ; +} + +BOOL LinkBackServerIsSupported(NSString* name, id supportedServers) +{ + BOOL ret = NO ; + int idx ; + NSString* curServer = supportedServers ; + + // NOTE: supportedServers may be nil, an NSArray, or NSString. + if (supportedServers) { + if ([supportedServers isKindOfClass: [NSArray class]]) { + idx = [supportedServers count] ; + while((NO==ret) && (--idx >= 0)) { + curServer = [supportedServers objectAtIndex: idx] ; + ret = [curServer isEqualToString: name] ; + } + } else ret = [curServer isEqualToString: name] ; + } + + return ret ; +} + +NSString* FindLinkBackServer(NSString* bundleIdentifier, NSString* serverName, NSString* dir, int level) +{ + NSString* ret = nil ; + + NSFileManager* fm = [NSFileManager defaultManager] ; + NSArray* contents = [fm directoryContentsAtPath: dir] ; + int idx ; + + NSLog(@"searching for %@ in folder: %@", serverName, dir) ; + + // working info + NSString* cpath ; + NSBundle* cbundle ; + NSString* cbundleIdentifier ; + id supportedServers ; + + // resolve any symlinks, expand tildes. + dir = [dir stringByStandardizingPath] ; + + // find all .app bundles in the directory and test them. + idx = (contents) ? [contents count] : 0 ; + while((nil==ret) && (--idx >= 0)) { + cpath = [contents objectAtIndex: idx] ; + + if ([[cpath pathExtension] isEqualToString: @"app"]) { + cpath = [dir stringByAppendingPathComponent: cpath] ; + cbundle = [NSBundle bundleWithPath: cpath] ; + cbundleIdentifier = [cbundle bundleIdentifier] ; + + if ([cbundleIdentifier isEqualToString: bundleIdentifier]) { + supportedServers = [[cbundle infoDictionary] objectForKey: @"LinkBackServer"] ; + ret= (LinkBackServerIsSupported(serverName, supportedServers)) ? cpath : nil ; + } + } + } + + // if the app was not found, descend into non-app dirs. only descend 4 levels to avoid taking forever. + if ((nil==ret) && (level<4)) { + idx = (contents) ? [contents count] : 0 ; + while((nil==ret) && (--idx >= 0)) { + BOOL isdir ; + + cpath = [contents objectAtIndex: idx] ; + [fm fileExistsAtPath: cpath isDirectory: &isdir] ; + if (isdir && (![[cpath pathExtension] isEqualToString: @"app"])) { + cpath = [dir stringByAppendingPathComponent: cpath] ; + ret = FindLinkBackServer(bundleIdentifier, serverName, cpath, level+1) ; + } + } + } + + return ret ; +} + +void LinkBackRunAppNotFoundPanel(NSString* appName, NSURL* url) +{ + int result ; + + // strings for panel + NSBundle* b = [NSBundle bundleForClass: [LinkBack class]] ; + NSString* title ; + NSString* msg ; + NSString* ok ; + NSString* urlstr ; + + title = NSLocalizedStringFromTableInBundle(@"_AppNotFoundTitle", @"Localized", b, @"app not found title") ; + ok = NSLocalizedStringFromTableInBundle(@"_OK", @"Localized", b, @"ok") ; + + msg = (url) ? NSLocalizedStringFromTableInBundle(@"_AppNotFoundMessageWithURL", @"Localized", b, @"app not found msg") : NSLocalizedStringFromTableInBundle(@"_AppNotFoundMessageNoURL", @"Localized", b, @"app not found msg") ; + + urlstr = (url) ? NSLocalizedStringFromTableInBundle(@"_GetApplication", @"Localized", b, @"Get application") : nil ; + + title = [NSString stringWithFormat: title, appName] ; + + result = NSRunCriticalAlertPanel(title, msg, ok, urlstr, nil) ; + if (NSAlertAlternateReturn == result) { + [[NSWorkspace sharedWorkspace] openURL: url] ; + } +} + ++ (LinkBackServer*)LinkBackServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier launchIfNeeded:(BOOL)flag fallbackURL:(NSURL*)url appName:(NSString*)appName ; +{ + BOOL connect = YES ; + NSString* serverName = MakeLinkBackServerName(bundleIdentifier, aName) ; + id ret = nil ; + NSTimeInterval tryMark ; + + // Try to connect + ret = [NSConnection rootProxyForConnectionWithRegisteredName: serverName host: nil] ; + + // if launchIfNeeded, and the connection was not available, try to launch. + if((!ret) && (flag)) { + NSString* appPath ; + id linkBackServers ; + + // first, try to find the app with the bundle identifier + appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier: bundleIdentifier] ; + linkBackServers = [[[NSBundle bundleWithPath: appPath] infoDictionary] objectForKey: @"LinkBackServer"] ; + appPath = (LinkBackServerIsSupported(aName, linkBackServers)) ? appPath : nil ; + + // if the found app is not supported, we will need to search for the app ourselves. + if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"/Applications",0); + + if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"~/Applications",0); + + if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"/Network/Applications",0); + + // if app path has been found, launch the app. + if (appPath) { + [[NSWorkspace sharedWorkspace] launchApplication: appName] ; + } else { + LinkBackRunAppNotFoundPanel(appName, url) ; + connect = NO ; + } + } + + // if needed, try to connect. + // retry connection for a while if we did not succeed at first. This gives the app time to launch. + if (connect && (nil==ret)) { + tryMark = [NSDate timeIntervalSinceReferenceDate] ; + do { + ret = [NSConnection rootProxyForConnectionWithRegisteredName: serverName host: nil] ; + } while ((!ret) && (([NSDate timeIntervalSinceReferenceDate]-tryMark)<10)) ; + + } + + // setup protocol and return + if (ret) [ret setProtocolForProxy: @protocol(LinkBackServer)] ; + return ret ; +} + +- (id)initWithName:(NSString*)aName delegate:(id)aDel +{ + if (self = [super init]) { + name = [aName copy] ; + delegate = aDel ; + listener = nil ; + } + + return self ; +} + +- (void)dealloc +{ + if (listener) [self retract] ; + [name release] ; + [super dealloc] ; +} + +- (BOOL)publish +{ + NSString* serverName = MakeLinkBackServerName([[NSBundle mainBundle] bundleIdentifier], name) ; + BOOL ret = YES ; + + // create listener and connect + NSPort* port = [NSPort port] ; + listener = [NSConnection connectionWithReceivePort: port sendPort:port] ; + [listener setRootObject: self] ; + ret = [listener registerName: serverName] ; + + // if successful, retain connection and add self to list of servers. + if (ret) { + [listener retain] ; + [LinkBackServers setObject: self forKey: name] ; + } else listener = nil ; // listener will dealloc on its own. + + return ret ; +} + +- (void)retract +{ + if (listener) { + [listener invalidate] ; + [listener release] ; + listener = nil ; + } + + [LinkBackServers removeObjectForKey: name] ; +} + +- (LinkBack*)initiateLinkBackFromClient:(LinkBack*)clientLinkBack +{ + LinkBack* ret = [[LinkBack alloc] initServerWithClient: clientLinkBack delegate: delegate] ; + + // NOTE: we do not release because LinkBack will release itself when it the link closes. (caj) + + return ret ; +} + +@end -- 2.39.2