Using the Bridge APIs
Core
If you need to add support for a new protocol or to add some feature in the GUI that requires that you add some functionality next to the core, then you'll need to code on top of the "Psi Core", "Psi Helper Classes" and/or "SAPO Protocols" layers (see the Application Architecture Overview for more info). The documentation or headers for the involved classes should be consulted for more details.
After adding the new functionality, you may also need to add new APIs to the bridge (or tweak the ones that already exist) to access the new features.
Lilypad Bridge
The Lilypad Bridge defines two APIs with a similar level of abstration. The two APIs reside on opposite sides of the bridge and feature the exact same set of pseudo-methods and pseudo-notifications. However, they cater to different layers of the application stack.
One of the APIs provides a not-as-thin-as-we-would-like C++/Qt layer of abstraction over the "Core". It is implemented by the LfpApi class (core/lfp_api.*), whose header file you should refer to for the most up-to-date definition of the API. This constitutes the first abstraction layer around the "Core". The bridge directs requests coming from higher layers (through the bridge) directly to a singleton instance of this class. This singleton will then handle the request and manipulate the Core as needed to yield the intended result. Notifications are also emitted by this class in the opposite direction. The available bridge API is described in platform/leapfrog_functions.txt. All the methods that can be invoked on this side of the bridge are listed, as well as all the notifications that can be sent by the core to the other side (symbols starting with "notify_").
The other API provides exactly the same set of methods and notifications, as was already mentioned, but does so through class methods defined in an Objective-C class. Here the goal is to hide the crossing of the bridge to potential clients of the API. By invoking one of the methods in the Objective-C class that implements the API, a message containing the request and all the arguments is encoded, sent through the bridge, decoded on the other end, and the corresponding method in the C++/Qt API is invoked. The class providing this API is LFAppController. It isn't supposed to be instantiated, as it only provides a series of class methods that very closely mirror the API provided by the core on the other side of the bridge. It effectively hides the complexity of running the bridge from the rest of the Objective-C code. When coding above this layer, in order to invoke a method in the "Core" module (going through the bridge) one simply has to invoke the corresponding Objective-C class method implemented by LFAppController.
To receive the notifications that come from the Core and cross the bridge the other way around, a client object needs to register as an observer with the LFPlatformBridge Objective-C class using its class method +[LFPlatformBridge registerNotificationsObserver:]. When a notification is emitted by the core, each registered observer is checked in turn for the existence of a method that complies with the notification signature but where the prefix leapfrogBridge_ takes the place of notify_ in the notification method name. For example, if you would like to be notified when the avatar of a contact in the roster is changed, you would add some object as a bridge observer, and then you had to define in the class of the observing object a method with the signature that follows, which would be invoked by the bridge whenever the notify_avatarChanged notification was emitted:
- (void)leapfrogBridge_avatarChanged:(int)entryID :(NSString *)typeOfData :(NSData *)data;
As was mentioned previously, if you need to add new functionality to the core there's a good chance that you'll also need to add new APIs to the bridge. To do this, follow these steps:
- To add a new method to the API (message flow through the bridge: "GUI" module -> "Core" module):
- Add the pseudo-method to the text file platform/leapfrog_functions.txt and document it and its parameters as needed;
- Define the method in the C++ class LfpApi, following the naming conventions that may apply (they aren't defined explicitly anywhere, but are easy to understand from a glance at the names of all the existing methods). The body of this method is completely at your own discretion, but you should probably pull some strings in the actual core to do whatever you want to do :) ;
- Define a similar class method in the Objective-C class LFAppController. The body of this method should be a simple invocation of the corresponding method that was defined on the other side of the bridge. To do this, use the Obj-C class LFPlatformBridge to help you cross the bridge.
- Example:
- Suppose you want to define a method that slaps a buddy with a large trout. Your method could be called slapBuddyWithLargeTrout and take one single argument, the integer ID of the contact.
- At the core side you would define the following C++ method:
void LfpApi::slapBuddyWithLargeTrout(int contactID) { // [...] } - On the other side of the bridge, you would define the following Objective-C method:
@implementation LFAppController // [...] + (void)slapBuddyWithLargeTrout:(int)contactID { [LFPlatformBridge invokeMethodWithName:@"slapBuddyWithLargeTrout" isOneway:YES arguments:ArgInt(contactID), nil]; } // [...] @end
The -[LFPlatformBridge invokeMethodWithName:isOneway:arguments:] method takes care of sending the method invocation through the bridge, which will result in the corresponding C++ method being invoked on the other side.
- To add a new notification to the API (message flow through the bridge: "Core" module -> "GUI" module):
- Add the pseudo-notification to the text file platform/leapfrog_functions.txt and document it and its parameters as needed;
- Define the notification in the C++ class LfpApi, following the naming conventions that may apply (they aren't defined explicitly anywhere, but are easy to understand from a glance at the names of all the existing notifications). The body of the method of the notification should pack the arguments and then send the notification through the bridge. See the example below for more info;
- In the Objective-C side of the bridge, everything is handled dynamically. You simply have to define a method in a bridge observer that matches the notification name, and it will be invoked automatically by the bridge whenever the notification is emitted by the "Core".
- Example:
- Suppose you want to define a notification that is emitted by the "Core" whenever a buddy slaps you with a large trout. Your notification could be called notify_slappedByBuddyWithLargeTrout and take one single argument, the integer ID of the contact that slapped you.
- At the core side you would define the following C++ method:
void LfpApi::notify_slappedByBuddyWithLargeTrout(int contactID) { LfpArgumentList args; args += LfpArgument("contactID", contactID); do_invokeMethod("notify_slappedByBuddyWithLargeTrout", args); } - Whenever you want to trigger the notification in the C++ code of the core, you would have to write something along the lines of:
QMetaObject::invokeMethod(lfpApiInstance, "notify_slappedByBuddyWithLargeTrout", Qt::QueuedConnection, Q_ARG(int, contactID)); - On the other side of the bridge, you would define the following Objective-C method in one of your bridge observers, which will then be invoked whenever the notification is triggered by the Core:
- (void)leapfrogBridge_slappedByBuddyWithLargeTrout:(int)contactID { // [...] }
Synchronous and Asynchronous Messages
The -[LFPlatformBridge invokeMethodWithName:isOneway:arguments:] method, which sends a method invocation request through the bridge, allows for both synchronous and asynchronous operation. This is controlled by the value passed in the isOneway: parameter.
If the value of the isOneway: parameter is TRUE/YES, like in the previous example, then the message is sent asynchronously. The message is added to a queue waiting to be processed and the -[LFPlatformBridge invokeMethodWithName:isOneway:arguments:] method returns right away, allowing the "GUI" module to go along and do its thing without having to wait for the core.
If, on the other hand, the value of the isOneway: parameter is FALSE/NO, then the message is sent synchronously. The method -[LFPlatformBridge invokeMethodWithName:isOneway:arguments:] blocks waiting for the bridge (and, consequently, the core) to process its request. The method will only return when the core signals that it has finished processing this message.
The method -[LFPlatformBridge invokeMethodWithName:isOneway:arguments:] is declared as having a return type of id. When sending an asynchronous message, nil is always returned. However, when sending a synchronous message (or a "two-way" message, as opposed to a "one-way" message), the value that the API method may possibly return is passed back through the bridge and returned by -[LFPlatformBridge invokeMethodWithName:isOneway:arguments:] when it finally unblocks. All returned values are objects, so, if the API method is declared as returning an int, then an NSNumber instance is returned at this point containing the int value that was returned by the core.