diff options
Diffstat (limited to 'HACKING-PLUGINS.md')
| -rw-r--r-- | HACKING-PLUGINS.md | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/HACKING-PLUGINS.md b/HACKING-PLUGINS.md new file mode 100644 index 0000000..5150074 --- /dev/null +++ b/HACKING-PLUGINS.md @@ -0,0 +1,402 @@ +# EOM Plugin Development Guide + +Quick guide for creating plugins for Eye of MATE (EOM). + +## Quick Start + +EOM uses **libpeas** for plugins. A plugin extends EOM by implementing the `EomWindowActivatable` interface. + +**What you can do:** +- Add menu items and toolbar buttons +- React to image changes +- Access image data and metadata +- Add custom UI widgets + +**Languages:** C or Python 3 + +**Install location:** `~/.local/share/eom/plugins/your-plugin-name/` + +## Plugin Structure + +Every plugin needs these files: + +1. **Plugin header** - `my-plugin.h` +2. **Plugin code** - `my-plugin.c` +3. **Plugin descriptor** - `my-plugin.plugin` +4. **Build file** - `Makefile` + +### Basic Plugin Template + +**Note:** This is a simplified template. For a complete working example with all necessary boilerplate, see `plugins/reload/` in the EOM source tree. + +```c +// my-plugin.h +#ifndef __MY_PLUGIN_H__ +#define __MY_PLUGIN_H__ + +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> +#include <eom-window.h> + +G_BEGIN_DECLS + +#define MY_TYPE_PLUGIN (my_plugin_get_type ()) +G_DECLARE_FINAL_TYPE (MyPlugin, my_plugin, MY, PLUGIN, PeasExtensionBase) + +struct _MyPlugin { + PeasExtensionBase parent; + EomWindow *window; + GtkActionGroup *action_group; + guint ui_id; +}; + +GType my_plugin_get_type (void) G_GNUC_CONST; +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* __MY_PLUGIN_H__ */ +``` + +```c +// my-plugin.c +#include "my-plugin.h" +#include <eom-window-activatable.h> + +static void eom_window_activatable_iface_init (EomWindowActivatableInterface *iface); + +// Register the plugin type +G_DEFINE_DYNAMIC_TYPE_EXTENDED (MyPlugin, my_plugin, + PEAS_TYPE_EXTENSION_BASE, 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (EOM_TYPE_WINDOW_ACTIVATABLE, + eom_window_activatable_iface_init)) + +enum { + PROP_0, + PROP_WINDOW +}; + +// Initialize plugin instance +static void +my_plugin_init (MyPlugin *plugin) +{ + // Initialize your plugin data here +} + +// Clean up plugin resources +static void +my_plugin_dispose (GObject *object) +{ + MyPlugin *plugin = MY_PLUGIN (object); + + if (plugin->window != NULL) { + g_object_unref (plugin->window); + plugin->window = NULL; + } + + G_OBJECT_CLASS (my_plugin_parent_class)->dispose (object); +} + +// Set plugin properties +static void +my_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MyPlugin *plugin = MY_PLUGIN (object); + + switch (prop_id) { + case PROP_WINDOW: + plugin->window = EOM_WINDOW (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +// Get plugin properties +static void +my_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MyPlugin *plugin = MY_PLUGIN (object); + + switch (prop_id) { + case PROP_WINDOW: + g_value_set_object (value, plugin->window); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +// Called when plugin is enabled +static void +my_plugin_activate (EomWindowActivatable *activatable) +{ + MyPlugin *plugin = MY_PLUGIN (activatable); + + // Add menu items, connect signals, etc. +} + +// Called when plugin is disabled +static void +my_plugin_deactivate (EomWindowActivatable *activatable) +{ + MyPlugin *plugin = MY_PLUGIN (activatable); + + // Remove menu items, disconnect signals, etc. +} + +// Initialize plugin class +static void +my_plugin_class_init (MyPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = my_plugin_dispose; + object_class->set_property = my_plugin_set_property; + object_class->get_property = my_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); +} + +// Required by G_DEFINE_DYNAMIC_TYPE_EXTENDED +static void +my_plugin_class_finalize (MyPluginClass *klass) +{ + // Dummy function for dynamic type registration +} + +// Implement the activatable interface +static void +eom_window_activatable_iface_init (EomWindowActivatableInterface *iface) +{ + iface->activate = my_plugin_activate; + iface->deactivate = my_plugin_deactivate; +} + +// Register with libpeas +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + my_plugin_register_type (G_TYPE_MODULE (module)); + peas_object_module_register_extension_type (module, + EOM_TYPE_WINDOW_ACTIVATABLE, MY_TYPE_PLUGIN); +} +``` + +### Plugin Descriptor + +```ini +# my-plugin.plugin +[Plugin] +Module=my-plugin +IAge=2 +Name=My Plugin Name +Description=What this plugin does +Authors=Your Name <[email protected]> +Copyright=Copyright © 2025 Your Name +Website=https://mate-desktop.org +``` + +### Build File + +```makefile +# Makefile +PLUGIN_NAME = my-plugin +SOURCES = my-plugin.c +HEADERS = my-plugin.h + +USER_DIR = $(HOME)/.local/share/eom/plugins/$(PLUGIN_NAME) +CFLAGS = `pkg-config --cflags gtk+-3.0 eom libpeas-1.0` -fPIC +LDFLAGS = `pkg-config --libs gtk+-3.0 eom libpeas-1.0` -shared + +all: lib$(PLUGIN_NAME).so + +lib$(PLUGIN_NAME).so: $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) $(SOURCES) -o $@ $(LDFLAGS) + +install: all + mkdir -p $(USER_DIR) + cp lib$(PLUGIN_NAME).so $(USER_DIR)/ + cp $(PLUGIN_NAME).plugin $(USER_DIR)/ + +clean: + rm -f lib$(PLUGIN_NAME).so +``` + +## Common Tasks + +### Adding a Menu Item + +```c +static void +my_action_callback (GtkAction *action, EomWindow *window) +{ + // Do something when menu item is clicked +} + +static const GtkActionEntry actions[] = { + { "MyAction", NULL, "Menu Label", "<Ctrl>M", + "Tooltip text", G_CALLBACK (my_action_callback) } +}; + +static const gchar* ui_xml = + "<ui><menubar name='MainMenu'>" + "<menu name='ToolsMenu' action='Tools'>" + "<menuitem action='MyAction'/>" + "</menu></menubar></ui>"; + +static void +my_plugin_activate (EomWindowActivatable *activatable) +{ + MyPlugin *plugin = MY_PLUGIN (activatable); + GtkUIManager *manager = eom_window_get_ui_manager (plugin->window); + + plugin->action_group = gtk_action_group_new ("MyActions"); + gtk_action_group_add_actions (plugin->action_group, actions, + G_N_ELEMENTS (actions), plugin->window); + gtk_ui_manager_insert_action_group (manager, plugin->action_group, -1); + plugin->ui_id = gtk_ui_manager_add_ui_from_string (manager, ui_xml, -1, NULL); +} +``` + +### Working with Images + +```c +// Get current image +EomImage *image = eom_window_get_image (window); + +// Get image data +GdkPixbuf *pixbuf = eom_image_get_pixbuf (image); +gint width, height; +eom_image_get_size (image, &width, &height); + +// Check image type +if (eom_image_is_jpeg (image)) { /* ... */ } + +// Transform image +EomTransform *transform = eom_transform_rotate_new (90); +eom_image_transform (image, transform, NULL); +g_object_unref (transform); + +// Mark as modified +eom_image_modified (image); +``` + +### Responding to Events + +```c +static void +on_image_changed (EomThumbView *view, MyPlugin *plugin) +{ + EomImage *image = eom_window_get_image (plugin->window); + // React to image change +} + +static void +my_plugin_activate (EomWindowActivatable *activatable) +{ + MyPlugin *plugin = MY_PLUGIN (activatable); + GtkWidget *thumbview = eom_window_get_thumb_view (plugin->window); + + plugin->signal_id = g_signal_connect (thumbview, "selection_changed", + G_CALLBACK (on_image_changed), plugin); +} + +static void +my_plugin_deactivate (EomWindowActivatable *activatable) +{ + MyPlugin *plugin = MY_PLUGIN (activatable); + GtkWidget *thumbview = eom_window_get_thumb_view (plugin->window); + + g_signal_handler_disconnect (thumbview, plugin->signal_id); +} +``` + +## Key APIs + +### EomWindow + +```c +EomImage * eom_window_get_image (EomWindow *window); +GtkUIManager * eom_window_get_ui_manager (EomWindow *window); +GtkWidget * eom_window_get_view (EomWindow *window); +GtkWidget * eom_window_get_sidebar (EomWindow *window); +GtkWidget * eom_window_get_statusbar (EomWindow *window); +void eom_window_reload_image (EomWindow *window); +``` + +### EomImage + +```c +GdkPixbuf * eom_image_get_pixbuf (EomImage *img); +void eom_image_get_size (EomImage *img, gint *width, gint *height); +void eom_image_transform (EomImage *img, EomTransform *trans, EomJob *job); +void eom_image_modified (EomImage *img); +gboolean eom_image_is_jpeg (EomImage *img); +``` + +See `src/eom-window.h` and `src/eom-image.h` for complete API. + +## Building and Testing + +```bash +# Build the plugin +make + +# Install to user directory +make install + +# Test +eom +# Go to Edit → Preferences → Plugins +# Enable your plugin +``` + +## Built-in Examples + +Study these plugins in the `plugins/` directory: + +1. **fullscreen** - Simple event handling (double-click to toggle fullscreen) +2. **reload** - Adding menu items (reload image from disk) +3. **statusbar-date** - Widget manipulation and EXIF reading + +## Tips + +- **Memory management**: Always `g_object_unref()` objects and `g_free()` allocated memory +- **Cleanup**: Remove all UI elements and disconnect all signals in `deactivate()` +- **Debugging**: Run with `EOM_DEBUG_PLUGINS=1 eom` or `EOM_DEBUG=1 eom` to see plugin messages +- **Headers**: All EOM headers are in `/usr/include/eom/` (install `libeom-dev`) + +## Complete Example + +See `plugins/reload/` in the EOM source for a complete, simple example showing: +- Plugin structure with proper GObject boilerplate +- Adding a menu item with keyboard shortcut +- Calling EOM window functions +- Proper activation and deactivation + +## Resources + +- **libpeas source/examples**: https://gitlab.gnome.org/GNOME/libpeas/-/tree/1.36?ref_type=heads +- **EOM headers**: `/usr/include/eom/` or `src/` in source tree (install `libeom-dev`) +- **GTK+ 3 docs**: https://docs.gtk.org/gtk3/ +- **MATE wiki**: https://wiki.mate-desktop.org/ +- **GObject Tutorial**: https://docs.gtk.org/gobject/ + +## Next Steps + +1. Copy one of the built-in plugins as a template +2. Modify it for your needs +3. Build and test iteratively +4. Share your plugin with the community! + +For advanced topics (GSettings integration, complex UI, Python plugins), refer to the built-in plugin code and GTK+/libpeas documentation. |
