HOWTO: Writing plugins for Gedit

<!> Note that the plugin system in the new_mdi cvs branch has been modified. This tutorial applies to the development of plugins for gedit versions up to and including gedit-2.10. For information on the new plugin system, see Gedit/NewMdi.

Gedit is a basic text editor, but it includes a plugin architecture that allows programmers to extend its functionality as desired. Gedit's architecture is centered around a bonobo control. Bonobo is a document viewer, and helps view text, and provides controls.

This document provides the stops required to create a new plugin, and also provides a full example towards the end of this page. In summary, your steps for create a new plugin at the following. Here, $plugin refers to the name of your plugin, for example os or changecase

  1. Make a new folder called $plugin in ~/gedit/plugins/$plugin.

  2. Write your source file in c. $plugin/$plugin.c.

  3. Write your $plugin.gedit-plugin.desktop.in.

  4. Write your $plugin.gedit-plugin.

  5. Write a Makefile.am there.
  6. (Extra step for Fedora users) Install the package 'gnome-common' from Fedora Extras, http://fedoraproject.org/wiki/Extras.

  7. Edit /gedit/configure.in, and add $plugin/Makefile in the AC_OUTPUT variable list.

  8. Edit /gedit/plugins/Makefile.am and add $plugin to DISTDIR SUBDIR variables.

  9. $ cd <gedit source root>
    $ aclocal
    $ autoconf
    $ automake
    $ ./configure
  10. Compile gedit sources [make && sudo make install].

  11. Install the module at $HOME/.gedit-2/plugins/.

Some tips on building Gedit

If you are on an RPM-based system, ensure you have the 'gnome-common' package installed, otherwise the 'autogen.sh' script won't run. In the case of Fedora systems, this can be obtained from the Fedora Extras wiki, http://fedoraproject.org/wiki/Extras

Note that current CVS sources for gedit (new_mdi) can't be built (easily, or at all?) on Fedora Core 3, because newer-than-FC3 libraries are required, such as Glib 2.8.0 (FC3 provides GLib 2.4.8).

Extra notes from TonyNelson:

For instance, I found that when autoconf says one "should" run aclocal it really means "must", and that an error about GNOME_COMPILE_WARNINGS(yes) means that gnome-common is not installed. It may be that only the last two steps would be needed for each new plugin, or that some of the earlier steps might be taken care of if rpmbuild were run with the -bc flag (or not, what do I know?).

plugins directory, and it would be a good thing to suggest looking for it and checking its date.

Make-ing Gedit the first time

I then do a make clean and make in the same directory. You might want to pipe you Make output to a file or pager, for example make 2>&1 | less.

Make-ing the plugin

After an initial make clean and make in the gedit source root, I make the plugin from its own directory. I install the new plugin locally by first quitting Gedit, copying two files to the local Gedit plugins directory, and restarting Gedit.

    cp <plugin>.gedit-plugin .libs/lib<plugin>.so ~/.gedit-2/plugins

My plugin was named "wrap", so the commands are

    (quit Gedit!)
    $ cd <Gedit dir>/plugins/wrap
    $ make
    $ cp wrap.gedit-plugin .libs/libwrap.so ~/.gedit-2/plugins
    $ gedit &

Remember to quit Gedit before changing plugins out from under it!

Debugging using DDD

DDD is a front-end GUI for the GNU debugger, gdb. On Fedora systems, you can install it by entering yum install ddd.

Start up Gedit from inside DDD using File->Open Program, typing /usr/bin/gedit into the Program field, and then pressing Run on the floating window. Make sure that Gedit isn't already running, as in that case new instance will just raise the old instance to the front then exit, which is not what we want.

With Gedit running, you can open the source code for your plugin with File->Open source, and set breakpoints in your source code with right-clicks of the mouse. I don't know of any way to set a breakpoint before Gedit has loaded the plugin (this isn't really even a gdb issue, it's an issue with dynamically loaded shared libraries), so the new plugin should be healthy enough to not crash Gedit when loading. ;)

You can quit the Gedit run from inside DDD, open another Gedit (or not), change the source, make and install the plugin, and press Run in DDD again to do more debugging. The Source menu Reload Source command should be used so that ddd will notice the changes. Breakpoints may need to be adjusted.

Plugin Structure

Plugin structure is defined in gedit sources at /gedit/gedit-plugin.h and its made of a big structure with a entry 'priv' where you can load your plugin[instance] specific data.

typedef struct _GeditPlugin GeditPlugin;

struct _GeditPlugin
{
        gchar   *file;

        gchar   *location;
        GModule *handle;

        /* The following fields are compulsory */
        gchar   *name;
        gchar   *desc;
        gchar   *author;
        gchar   *copyright;

        /* The following fields are compulsory */
        GeditPluginState        (*init)         (GeditPlugin *p);
        GeditPluginState        (*activate)     (GeditPlugin *p);
        GeditPluginState        (*deactivate)   (GeditPlugin *p);

        /* The following fields are optional */
        GeditPluginState        (*configure)    (GeditPlugin *p, GtkWidget *parent);
        GeditPluginState        (*update_ui)    (GeditPlugin *p, BonoboWindow *w);
        GeditPluginState        (*destroy)      (GeditPlugin *p);

        /* Eventually filled in by the plugin */
        gpointer        private_data;
};

For more gyann (Hindi for enlightenment), you may read gedit-plugins-engine.h, on how gedit loads the plugin modules.

Now this gedit-plugins-engine loads the variables

{{{ gchar *location;

}}} into the structure, from your $plugin.gedit-plugin file.

A typical $plugin.gedit-plugin file would look like this.

[Gedit Plugin]
Location=os
Name=Insert OS Name
Description=Inserts OS Name at the cursor position.
Author= Muthiah Annamalai <gnumuthu@users.sf.net>
Copyright=Copyright © 2002-2003 Paolo Maggi, 2005 Muthu

(Now why this $plugin.gedit-plugin.desktop exists I dont know, someone please fillin.)

The rest of the structure's members are given below. These, you will define in your Gedit $plugin.c file and do your programming.

{{{

}; }}}

Plugin Functions

Init

is to setup your plugin for the whole Gedit application. Some static data can be loaded into plugin->private_data field.

Activate

is to add your plugin thing into the menubar of all gedit toplevel windows. It can be copy-paste'd from other plugins. You must replace the menu's "activate" signal callback to your plugin's function. This is the actual starting point of whatever functionality youre going to add to gedit, the rest all is plain boiler-plate code.

Deactivate

remove the menus from toplevel windows. It can also be copy-paste'd from other plugins.

Configure

this is to give the users the choice of plugin functionality. The 'time' plugin in /gedit/plugin/time allows users to choose a time format they want. Its kinda cool.

Update UI

update ui is for enabling you plugin-menu-item to sensitive or insensitive, depending of doc is readonly or not. It can also be copy-paste'd from other plugins.

Destroy

we may destroy the statically allocated priv->data and anyother plugin inited resources can be uninitialized.

Well for more details dive into a full plugin like /gedit/plugins/sample and get started.

Full Example

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * os.c
 * This file is part of gedit
 * [Shamelessly copied from time.c]
 * (C) 2005 Muthiah Annamalai.
 *
 * Copyright (C) 2002 Paolo Maggi
 *
 * 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.
 */

/*
 * Modified by the gedit Team, 2002. See the AUTHORS file for a
 * list of people on the gedit Team.
 * See the ChangeLog files for a list of changes.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <time.h>

#include <glib/gi18n.h>
#include <glade/glade-xml.h>
#include <gconf/gconf-client.h>
#include <libgnome/gnome-help.h>
#include <libgnome/gnome-config.h>

#include <gedit/gedit-plugin.h>
#include <gedit/gedit-debug.h>
#include <gedit/gedit-menus.h>
#include <gedit/gedit-utils.h>

#include <sys/utsname.h>


#define MENU_ITEM_LABEL         N_("In_sert OS Name")
#define MENU_ITEM_PATH          "/menu/Edit/EditOps_5/"
#define MENU_ITEM_NAME          "PluginInsert OS Name"
#define MENU_ITEM_TIP           N_("Insert OS Name at the cursor position")

/* For the people to pickup.. */
G_MODULE_EXPORT GeditPluginState update_ui (GeditPlugin *plugin, BonoboWindow *window);
G_MODULE_EXPORT GeditPluginState destroy (GeditPlugin *pd);
G_MODULE_EXPORT GeditPluginState activate (GeditPlugin *pd);
G_MODULE_EXPORT GeditPluginState deactivate (GeditPlugin *pd);
G_MODULE_EXPORT GeditPluginState init (GeditPlugin *pd);
G_MODULE_EXPORT GeditPluginState configure (GeditPlugin *p, GtkWidget *parent);


static void
insert_os_name (BonoboUIComponent *uic,gpointer user_data, const gchar* verbname)
{
        struct utsname osname={0,};
        GeditDocument *doc;
        gchar *osdata=NULL;

        if(!uname(&osname))
        {
                /* Success */
                osdata=g_strdup_printf(" %s %s %s %s %s ",osname.sysname,
                                                                osname.nodename,
                                                        osname.release,
                                                        osname.version,
                                                        osname.machine);
        }
        else
        {
                osdata=g_strdup_printf(" Unknown OS "); /* Wierdo Error! */
        }

        gedit_debug (DEBUG_PLUGINS, "");

        doc = gedit_get_active_document ();

        gedit_document_begin_user_action (doc);
        gtk_text_buffer_insert_at_cursor (GTK_TEXT_BUFFER (doc), osdata, -1);
        gedit_document_end_user_action (doc);

        g_free(osdata);
}

G_MODULE_EXPORT GeditPluginState
configure (GeditPlugin *p, GtkWidget *parent)
{
        GtkWidget *dlg=NULL;
        gedit_debug (DEBUG_PLUGINS, "Informing Dialog");
        dlg=gtk_message_dialog_new(NULL,1,2,2,"Hello WOrld!\n This is GEDIT OS Insert plugin",NULL);
        g_return_val_if_fail(dlg!=NULL,PLUGIN_OK);

        gtk_widget_show_all(dlg);
        gtk_dialog_run(dlg);
        gtk_widget_destroy (dlg);

        gedit_debug (DEBUG_PLUGINS, "Done");

        return PLUGIN_OK;
}

G_MODULE_EXPORT GeditPluginState
update_ui (GeditPlugin *plugin, BonoboWindow *window)
{
        BonoboUIComponent *uic;
        GeditDocument *doc;
        GeditMDI *mdi;

        gedit_debug (DEBUG_PLUGINS, "OS Update ui");

        g_return_val_if_fail (window != NULL, PLUGIN_ERROR);

        mdi = gedit_get_mdi ();
        g_return_val_if_fail (window != NULL, PLUGIN_ERROR);

        uic = gedit_get_ui_component_from_window (window);

        doc = gedit_get_active_document ();

        if ((doc == NULL) || (gedit_document_is_readonly (doc)) || (gedit_mdi_get_state (mdi) != GEDIT_STATE_NORMAL))
                gedit_menus_set_verb_sensitive (uic, "/commands/" MENU_ITEM_NAME, FALSE);
        else
                gedit_menus_set_verb_sensitive (uic, "/commands/" MENU_ITEM_NAME, TRUE);

        return PLUGIN_OK;
}

G_MODULE_EXPORT GeditPluginState
activate (GeditPlugin *pd)
{
         /* Standard stuff */
         GList *top_windows;
        gedit_debug (DEBUG_PLUGINS, "");

        top_windows = gedit_get_top_windows ();
        g_return_val_if_fail (top_windows != NULL, PLUGIN_ERROR);

        while (top_windows)
        {
                gedit_menus_add_menu_item (BONOBO_WINDOW (top_windows->data),
                                     MENU_ITEM_PATH, MENU_ITEM_NAME,
                                     MENU_ITEM_LABEL, MENU_ITEM_TIP,
                                     NULL, insert_os_name);

                pd->update_ui (pd, BONOBO_WINDOW (top_windows->data));

                top_windows = g_list_next (top_windows);
        }


        return PLUGIN_OK;
}

G_MODULE_EXPORT GeditPluginState
deactivate (GeditPlugin *pd)
{
        gedit_debug (DEBUG_PLUGINS, "Deactivated OS plugin");

        gedit_menus_remove_menu_item_all (MENU_ITEM_PATH, MENU_ITEM_NAME);

        return PLUGIN_OK;
}

G_MODULE_EXPORT GeditPluginState
destroy (GeditPlugin *pd)
{
        gedit_debug (DEBUG_PLUGINS, "Closing OS Plugin");
        return PLUGIN_OK;
}

G_MODULE_EXPORT GeditPluginState
init (GeditPlugin *pd)
{
        /* initialize */
        gedit_debug (DEBUG_PLUGINS, "OS Init plugin");

        pd->private_data = NULL;

        /* NO GConf Stuff */
        return PLUGIN_OK;
}

Get the tarball from GeditPluginHowto.tgz

Original Author

Muthiah Annamalai.

LICENSE

You may distribute this under GNU FDL.

GeditPluginHowto (last edited 2008-02-03 14:46:46 by localhost)