Writting a Geany plugin


geany logo

I wrote a simple C language plugin for Geany. It is similar to geany's standard keyrecord plugin which simply records and playbacks key sequences. However unlike keyrecord which uses key events, it uses the Scintilla macro recording functionality like geanymacros plugin. However, unlike geanymacros does not do macro editing, and binding macros to multiple keys.

My plugin can be found in this Github repo. Most of the boiler plate I got it from another plugin, Ctrl-Alt.

Pre-requisites

I use void-linux, so in void you first need to install the pre-requisites. This can be done with the command:

sudo xbps-install -S base-devel geany-devel

Registering

Firs you need to include <geanyplugin.h> which brings all the [Geany][geany] API as well as the necessary GTK headers.

#include <geanyplugin.h>

and export a function named geany_load_module(). This function is used to tell Geany of your plugin. This includes metadata such as name, description, version, etc. But also includes pointers to function such as:

  • init - plugin initialization
  • cleanup - finalization
  • configure - optional configuration function
  • help - optional help functionality
  • callbacks - optional array of PluginCallback functions

Building

To compile a plugin:

gcc -c plugin.c -fPIC `pkg-config --cflags geany` 

Linking the plugin:

gcc plugin.o -o plugin.so -shared `pkg-config --libs geany` 

If all went OK, put the library into one of the paths Geany looks for plugins, e.g. $prefix/lib/geany or $HOME/.config/geany/plugins.

Binding keys

Previous versions of the Geany API a plugin would register bindings itself. Currently, a plugin defines a binding as a _groupname and _keyname and the user can configure the actual key combination.

To define the key bindings you must add to the init function:

key_group = plugin_set_key_group(plugin, PLUGIN_KEY_NAME, KB_COUNT, NULL);
keybindings_set_item(key_group, KB_RECORD, macrec_record, 0, 0,
            "record", "Start/Stop recording macro", NULL);
keybindings_set_item(key_group, KB_PLAY, macrec_play, 0, 0,
            "playback", "Playback macro", NULL);

Where:

  • PLUGIN_KEY_NAME is a string for naming the key group.
  • KB_COUNT is the number of keys in the key group. This is defined using an C enum. KB_RECORD and KB_PLAY are in the same enum and are unique numbers identifying a key combination.
  • macrec_record and macrec_play are the callback functions executed when the given keybinding is pressed.

Callbacks

Callbacks are functions called whenever a Geany event is fired. These are defined in geany_load_module function as:

plugin->funcs->callbacks = macrec_callbacks;

macrec_callbacks is defined as:

static PluginCallback macrec_callbacks[] =
{
        { "editor-notify", (GCallback) &on_editor_notify, FALSE, NULL },
        { "document-close", (GCallback) &on_document_close, FALSE, NULL },
        { NULL, NULL, FALSE, NULL }
};

We are hooking to events, editor-notify which is used for recording macros and uses the Scintilla built-in recording facility.

document-close is used to abort recording of macros.

Convenience functions

For this plugin I am using a few convenience functions:

Recording

For macro recording we using Scintilla functionality. This is in contrast to the keyrecord plugin which uses low-level GTK functionality which for me is not vedry reliable.

Start recording:

scintilla_send_message(document_get_current()->editor->sci,SCI_STARTRECORD,0,0);

After that, we capture events using editor-notify callback. In that function we use a table to classify and identify the messages we want to handle:

/* structure to hold details of Macro for macro editor */
typedef struct {
  gint message;
  const gchar *description;
} MacroDetailEntry;

/* list of editor messages this plugin can handle & a description */
const MacroDetailEntry MacroDetails[]={
{SCI_CUT,N_("Cut to Clipboard")},
{SCI_COPY,N_("Copy to Clipboard")},
{SCI_PASTE,N_("Paste from Clipboard")},
{SCI_LINECUT,N_("Cut current line to Clipboard")},
{SCI_LINECOPY,N_("Copy current line to Clipboard")},

... content deleted ...

{0,NULL}
};

Once recording we stop Scintilla macro recording.

scintilla_send_message(document_get_current()->editor->sci,SCI_STOPRECORD,0,0);

sci logo

Playback

Since the recording is done using Scintilla facilities we have to use Scintilla playback. This is realized with this function call:

scintilla_send_message(sci,me->message,me->wparam,(sptr_t)clipboardcontents);

To make sure that the undo history is consistent, we call:

scintilla_send_message(sci,SCI_BEGINUNDOACTION,0,0);

before playback, and call:

scintilla_send_message(sci,SCI_ENDUNDOACTION,0,0);

to finish the UNDO action.

Finishing touches

I also added a github action to the repository to compile the code (to ensure that things at least compile).

gh actions logo

For that I am using a simple C/C++ workflow. What I added was the task to make sure that the correct dependancies are available:

- name: Install dependancies
  run: sudo apt-get install -y libgtk-3-dev autoconf automake autopoint gettext geany