]> git.lyx.org Git - lyx.git/commitdiff
* LinkBack support files, now in pure Objective-C (without any Objective-C++ which...
authorStefan Schimanski <sts@lyx.org>
Sun, 3 Feb 2008 10:41:23 +0000 (10:41 +0000)
committerStefan Schimanski <sts@lyx.org>
Sun, 3 Feb 2008 10:41:23 +0000 (10:41 +0000)
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@22761 a592a061-630c-0410-9148-cb99ea01b6c8

configure.ac
development/cmake/src/support/CMakeLists.txt
src/support/Makefile.am
src/support/linkback/LinkBack.h [new file with mode: 0644]
src/support/linkback/LinkBack.m [new file with mode: 0644]
src/support/linkback/LinkBackProxy.h [new file with mode: 0644]
src/support/linkback/LinkBackProxy.m [new file with mode: 0644]
src/support/linkback/LinkBackServer.h [new file with mode: 0644]
src/support/linkback/LinkBackServer.m [new file with mode: 0644]

index 1f176e78b5d6f510b5584f8b5e61b79153232b91..97bf6bcfe60fab4a724e714217acb30a59b31f36 100644 (file)
@@ -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]),,
index 0500e4026cdd1f8eddb35e25f9c866d230006ca1..46ca63eecb7dda806af9873d59f6b59116aa5479 100644 (file)
@@ -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()
 
index 9db3a24fb6ac60f5fd3e08864ad32e7330e5a928..5744f815fa7673a5927cc0737adb27582070e8ec 100644 (file)
@@ -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 (file)
index 0000000..ccf3274
--- /dev/null
@@ -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 <Cocoa/Cocoa.h>
+
+// 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> {
+    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<LinkBackServerDelegate>)del ;
+
++ (void)retractServerWithName:(NSString*)name ;
+
+- (void)sendEdit ;
+
+// ...........................................................................
+// Client-Side Methods
+//
++ (LinkBack*)editLinkBackData:(id)data sourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)del itemKey:(NSString*)aKey ;
+
+@end
+
+@interface LinkBack (InternalUseOnly)
+
+- (id)initServerWithClient: (LinkBack*)aLinkBack delegate: (id<LinkBackServerDelegate>)aDel ;
+
+- (id)initClientWithSourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)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 (file)
index 0000000..063f61a
--- /dev/null
@@ -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<LinkBackServerDelegate>)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<LinkBackClientDelegate>)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<LinkBackServerDelegate>)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<LinkBackClientDelegate>)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 (file)
index 0000000..a535aa4
--- /dev/null
@@ -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 (file)
index 0000000..c86649f
--- /dev/null
@@ -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 <config.h>
+
+#include "support/linkback/LinkBackProxy.h"
+
+#include "support/linkback/LinkBack.h"
+
+///////////////////////////////////////////////////////////////////////
+
+static NSAutoreleasePool * pool = nil;
+
+@interface LyXLinkBackClient : NSObject <LinkBackClientDelegate> {
+       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 (file)
index 0000000..b8b5c20
--- /dev/null
@@ -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 <Cocoa/Cocoa.h>
+
+@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 <LinkBackServer> {
+    NSConnection* listener ;
+    NSString* name ;
+    id<LinkBackServerDelegate> delegate ;
+}
+
++ (LinkBackServer*)LinkBackServerWithName:(NSString*)name  ;
++ (BOOL)publishServerWithName:(NSString*)name delegate:(id<LinkBackServerDelegate>)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<LinkBackServerDelegate>)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 (file)
index 0000000..f4fff31
--- /dev/null
@@ -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<LinkBackServerDelegate>)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<LinkBackServerDelegate>)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