Skip to content

Sylpheed Plug in Specification

Hiroyuki Yamamoto edited this page Dec 19, 2022 · 4 revisions

Sylpheed Plug-in Specification (version 3.1.0)

Overview of plug-in system

The following is the architecture of plugin system of Sylpheed.

 +----------+    +----------------------+     +-----------+
 | Sylpheed |----| libsylpheed-plugin-0 |--+--| Plug-in A |
 +----------+    +----------------------+  |  +-----------+
   Sylpheed         Plug-in interface      |   Plug-in DLL
                    library             +--+
        |        +------------+         |  |  +-----------+
        +--------| libsylph-0 |---------+  +--| Plug-in B |
                 +------------+               +-----------+
                 LibSylph mail library

Sylpheed loads the plug-in DLLs installed in the plug-in directory on startup.

A plug-in can only access the functions of Sylpheed through the APIs provided with libsylpheed-plugin-0 and libsylph-0 library.

There are two kinds of plug-in API. One is called directly from plug-ins, another one utilizes the signal mechanism of GObject and calls the callback functions on specific events.

The plug-in system is implemented in libsylph/sylmain.[ch] and src/plugin.[ch].

Though functions of Sylpheed which can be called by plug-ins are still low, they will be increased for each new release.

Plug-in API

Functions used by Sylpheed

 void syl_plugin_signal_connect  (const gchar *name, GCallback callback,
                                  gpointer data);

Connects to signals available with SylPlugin object (obtained inside library). The specification of callback functions that receive signals is similar to that of normal GObject. Refer to the signals list for available signals.

 void syl_plugin_signal_disconnect(gpointer func, gpointer data);

Disconnects signals connected by syl_plugin_signal_connect().

 void syl_plugin_signal_emit(const gchar *name, ...);

Emits SylPlugin object signals.

 gint syl_plugin_init_lib        (void);

Initializes the libsylpheed-plugin-0 library.

 gint syl_plugin_load            (const gchar *file);

Loads plug-in DLL files into memory.

 gint syl_plugin_load_all        (const gchar *dir);

Loads plug-in DLL files in the specified directory into memory.

 void syl_plugin_unload_all      (void);

Unloads all loaded plug-ins.

 GSList *syl_plugin_get_module_list      (void);

Obtains the list of plug-ins loaded into memory. It returns the list of pointers to GModule struct. The list is obtained by the library internally, so it must not be freed.

 SylPluginInfo *syl_plugin_get_info      (GModule *module);

Obtains plug-in information. The information is returned as SylPluginInfo struct.

 gboolean syl_plugin_check_version       (GModule *module);

Compares plug-in interface versions and checks if the plug-in is compatible. Returns TRUE if the version matches, FALSE otherwise.

 gint syl_plugin_add_symbol              (const gchar *name, gpointer sym);

Registers symbol name and pointer value related to it to the library.

 gpointer syl_plugin_lookup_symbol       (const gchar *name);

Searches symbol registered by syl_plugin_add_symbol() and returns its pointer value.

Functions which must be implemented by plug-ins

 void plugin_load(void)

Called from Sylpheed on plug-in load. Do initialization of plug-in etc. here.

 void plugin_unload(void)

Called from Sylpheed on plug-in unload. Do finalization of plug-in etc. here.

 SylPluginInfo *plugin_info(void)

Fuction to return struct which stores plug-in information to Sylpheed. It normally returns pointer to static struct.

 gint plugin_interface_version(void)

Function to return plug-in API interface version to Sylpheed. A plug-in normally returns constant value SYL_PLUGIN_INTERFACE_VERSION. Sylpheed compares this value with its own value and checks if it is compatible. Sylpheed's plug-in interface version must be equal to or greater than the plug-in's plug-in interface verson. If the major versions of the interface version differ, they are treated as incompatible.

 Ex.1: Sylpheed plug-in interface version: 0x0102
       A plug-in plug-in interface version: 0x0100: OK
 Ex.2: Sylpheed plug-in interface version: 0x0102
       A plug-in plug-in interface version: 0x0103: NG

Functions used by plug-ins

Refer to src/plugin.h and libsylph/*.h for the functions list.

Signals list

libsylpheed-plugin-0

Call syl_plugin_signal_connect() to use the following signals.

Example:

 static void plugin_load_cb(GObject *obj, GModule *module, gpointer data)
 {
     ...
 }
 
     syl_plugin_signal_connect("plugin-load", G_CALLBACK(plugin_load_cb), data);

 void (* plugin_load)    (GObject *obj, GModule *module);

Emitted on plug-in loading by syl_plugin_load().

 void (* plugin_unload)  (GObject *obj, GModule *module);

Emitted on plug-in unloading by syl_plugin_unload_all().

 void (* folderview_menu_popup)  (GObject *obj, gpointer ifactory);

Emitted on popup of the context menu of FolderView.

 void (* summaryview_menu_popup)  (GObject *obj, gpointer ifactory);

Emitted on popup of the context menu of SummaryView.

 void (* compose_created)        (GObject *obj, gpointer compose);

Emitted on the creation of message compose window. Retrieved 'compose' pointer will be used with syl_plugin_compose_*() functions.

 void (* compose_destroy)        (GObject *obj, gpointer compose);

Emitted just before the destruction of message compose window.

 void (* textview_menu_popup)    (GObject *obj,
                                  GtkMenu *menu,
                                  GtkTextView *textview,
                                  const gchar *uri,
                                  const gchar *selected_text,
                                  MsgInfo *msginfo);

Emitted on popup of the context menu of TextView. You can add any menu items to the passed GtkMenu.

The menu object will be created on open and destroyed on close, so menu items must be added each time.

 menu: context menu object
 textview: GtkTextView object
 uri: URI string if the menu popups on an URI
 selected_text: string if a string is selected on the text view
 msginfo: the MsgInfo message object displayed in the text view

libsylph-0

The following signals can be used by passing GObject obtained by syl_app_get() to the first argument of g_signal_connect().

Example:

 void init_done_cb(GObject *obj, gpointer data)
 {
     ...
 }
 
     g_signal_connect(syl_app_get(), "init-done", G_CALLBACK(init_done_cb),
                      data);

 void (* init_done) (GObject *obj)

Emitted when the initialization of application completes.

 void (* app_exit) (GObject *obj)

Emitted when application exits.

 void (* app_force_exit) (GObject *obj)

Emitted when application is forced to exit (no confirmation). (ex: sylpheed --exit)

 void (* add_msg) (GObject *obj, FolderItem *item, const gchar *file, guint num)

Emitted when a message (number num) is added into folder item.

 void (* remove_msg) (GObject *obj, FolderItem *item, const gchar *file,
                      guint num)

Emitted when a message (number num) is removed from folder item.

 void (* remove_all_msg) (GObject *obj, FolderItem *item)

Emitted when all messages are removed from folder item.

 void (* remove_folder) (GObject *obj, FolderItem *item)

Emitted when folder item is removed.

 void (* move_folder) (GObject *obj, FolderItem *item, const gchar *old_id,
                       const gchar *new_id)

Emitted when folder item is moved (renamed) from old_id to new_id. old_id and new_id are folder identifier strings.

 void (* folderlist_updated) (GObject *obj)

Emitted when folder information is modified and folderlist.xml, which contains folder list, is updated.

 void (* account_updated) (GObject *obj)

Emitted on the update of account information. It will not be emitted if it is locked by account_update_lock(), though.

Building plug-in

The following procedure is required to build a plug-in:

  • Include the header files of GTK+, libsylph, libsylpheed-plugin
  • Link against the libraries
  • Create shared library (DLL)

The include files of Sylpheed are installed under /usr/local/include/sylpheed/, the library files are installed under /usr/local/lib/ by default ($HOME/dist/include/sylpheed/ and $HOME/dist/lib/ in the case of build using makewin32.sh on MSYS).

Using the included test.c as an example, the following commands are used to compile and install plug-in manually:

(Linux)

 $ gcc `pkg-config --cflags gtk+-2.0` -I/usr/local/include/sylpheed -I/usr/local/include/sylpheed/sylph -c test.c -o test.o
 $ gcc -shared test.o `pkg-config --libs gtk+-2.0` -L/usr/local/lib -lsylpheed-plugin-0 -lsylph-0 -o test.so
 $ sudo cp test.so /usr/local/lib/sylpheed/plugins/

(Windows (MinGW))

 $ gcc `pkg-config --cflags gtk+-2.0` -I$HOME/dist/include/sylpheed -I$HOME/dist/include/sylpheed/sylph -c test.c -o test.o
 $ gcc -shared test.o `pkg-config --libs gtk+-2.0` -L$HOME/dist/lib -lsylpheed-plugin-0 -lsylph-0 -o test.dll
 $ cp test.dll $HOME/dist/lib/sylpheed/plugins/

To automate the procedure, create configure.ac and Makefile.am, and build using autoconf, automake, libtool. Please refer to Makefile.am of the sample plug-in.

Sample plug-in

There is a sample plug-in at plugin/test. This plug-in will not be installed with 'make install'. It is required to enter the directory plugin/test and run 'make install-plugin'.

The 'test' plug-in has the basic structure of Sylpheed plug-in and the following process:

  • Output string "test plug-in loaded!" to stdout on load
  • Get folder list and output to stdout
  • Get Sylpheed version string and output to stdout
  • Get the main window and put it in front
  • Add 'Plugin test' menu item on the 'Tools' menu
  • When 'Plugin test' menu is selected, a window with a button named 'Click this button' is displayed. When it is clicked, a message is displayed
  • Capture the following events and show messages: application initialization and exiting, folder view context menu popup, creating and destroying compose window

test.c

 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
  * Copyright (C) 1999-2010 Hiroyuki Yamamoto
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
 #include <glib.h>
 #include <gtk/gtk.h>
 
 #include "sylmain.h"
 #include "plugin.h"
 #include "test.h" /* test.h is empty */
 #include "folder.h"
 
 static SylPluginInfo info = {
         "Test Plugin",
         "1.0.0",
         "Hiroyuki Yamamoto",
         "Test plug-in for Sylpheed plug-in system"
 };
 
 static void init_done_cb(GObject *obj, gpointer data);
 static void app_exit_cb(GObject *obj, gpointer data);
 static void menu_popup_cb(GObject *obj, GtkItemFactory *ifactory,
                           gpointer data);
 static void compose_created_cb(GObject *obj, gpointer compose);
 static void compose_destroy_cb(GObject *obj, gpointer compose);
 
 static void create_window(void);
 
 void plugin_load(void)
 {
        GList *list, *cur;
        const gchar *ver;
        gpointer mainwin;
 
        g_print("test plug-in loaded!\n");
 
        list = folder_get_list();
        g_print("folder list = %p\n", list);
        for (cur = list; cur != NULL; cur = cur->next) {
                Folder *folder = FOLDER(cur->data);
                gchar *id = folder_get_identifier(folder);
                g_print("folder id = %s\n", id);
        }
 
        ver = syl_plugin_get_prog_version();
        g_print("program ver: %s\n", ver);
 
        mainwin = syl_plugin_main_window_get();
        g_print("mainwin: %p\n", mainwin);
        syl_plugin_main_window_popup(mainwin);
 
        syl_plugin_add_menuitem("/Tools", NULL, NULL, NULL);
        syl_plugin_add_menuitem("/Tools", "Plugin test", create_window, NULL);
 
        g_signal_connect(syl_app_get(), "init-done", G_CALLBACK(init_done_cb),
                         NULL);
        g_signal_connect(syl_app_get(), "app-exit", G_CALLBACK(app_exit_cb),
                         NULL);
        syl_plugin_signal_connect("folderview-menu-popup",
                                  G_CALLBACK(menu_popup_cb), NULL);
        syl_plugin_signal_connect("compose-created",
                                  G_CALLBACK(compose_created_cb), NULL);
        syl_plugin_signal_connect("compose-destroy",
                                  G_CALLBACK(compose_destroy_cb), NULL);
 
        g_print("test plug-in loading done\n");
 }
 
 void plugin_unload(void)
 {
        g_print("test plug-in unloaded!\n");
 }
 
 SylPluginInfo *plugin_info(void)
 {
        return &info;
 }
 
 gint plugin_interface_version(void)
 {
        return SYL_PLUGIN_INTERFACE_VERSION;
 }
 
 static void init_done_cb(GObject *obj, gpointer data)
 {
        g_print("test: %p: app init done\n", obj);
 }
 
 static void app_exit_cb(GObject *obj, gpointer data)
 {
        g_print("test: %p: app will exit\n", obj);
 }
 
 static void menu_popup_cb(GObject *obj, GtkItemFactory *ifactory,
                          gpointer data)
 {
        g_print("test: %p: folderview menu popup\n", obj);
 }
 
 static void compose_created_cb(GObject *obj, gpointer compose)
 {
        gchar *text;
 
        g_print("test: %p: compose created (%p)\n", obj, compose);
 
        text = syl_plugin_compose_entry_get_text(compose, 0);
        g_print("test: compose To: %s\n", text);
        g_free(text);
 #if 0
        syl_plugin_compose_entry_set(compose, "test-plugin@test", 1);
        syl_plugin_compose_entry_append(compose, "second@test", 1);
 #endif
 }
 
 static void compose_destroy_cb(GObject *obj, gpointer compose)
 {
        g_print("test: %p: compose will be destroyed (%p)\n", obj, compose);
 }
 
 static void button_clicked(GtkWidget *widget, gpointer data)
 {
        g_print("button_clicked\n");
        /* syl_plugin_app_will_exit(TRUE); */
 }
 
 static void create_window(void)
 {
        GtkWidget *window;
        GtkWidget *button;
 
        g_print("creating window\n");
 
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        button = gtk_button_new_with_label("Click this button");
        gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);
        gtk_container_add(GTK_CONTAINER(window), button);
        g_signal_connect(G_OBJECT(button), "clicked",
                         G_CALLBACK(button_clicked), NULL);
        gtk_widget_show_all(window);
 }

About license

It is required that a plug-in DLL dynamically loaded by Sylpheed is GPL or GPL-compatible license (ex. modified BSD license) based on the GPL clause because the license of Sylpheed itself is GPL.

If you want to apply non-GPL license like proprietary license to your plug-in, you must make the module an independent executable file, and make it work with inter-process communication with a DLL.