Writting a Geany plugin
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 initializationcleanup
- finalizationconfigure
- optional configuration functionhelp
- optional help functionalitycallbacks
- optional array ofPluginCallback
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 Cenum
.KB_RECORD
andKB_PLAY
are in the sameenum
and are unique numbers identifying a key combination.macrec_record
andmacrec_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:
- ui_set_statusbar - Display text on the status bar.
- msgwin_status_add - Logs a formatted status message without setting the status bar.
- utils_open_browser - Tries to open the given URI in a browser.
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);
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).
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