1 /*
2 * plugin.c
3 *
4 * Copyright 2011 Matthew Brush <mbrush@codebrainz.ca>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #if defined(HAVE_CONFIG_H) && !defined(GEANYPY_WINDOWS)
23 # include "config.h"
24 #endif
25
26 #define INCLUDE_PYGOBJECT_ONCE_FULL
27
28 #include "geanypy.h"
29 #include "geanypy-keybindings.h"
30
31 #include <glib.h>
32 #include <glib/gstdio.h>
33
34 GeanyData *geany_data;
35
36 /* Forward declarations to prevent compiler warnings. */
37 PyMODINIT_FUNC initapp(void);
38 PyMODINIT_FUNC initdialogs(void);
39 PyMODINIT_FUNC initdocument(void);
40 PyMODINIT_FUNC initeditor(void);
41 PyMODINIT_FUNC initencoding(void);
42 PyMODINIT_FUNC initfiletypes(void);
43 PyMODINIT_FUNC initglog(void);
44 PyMODINIT_FUNC inithighlighting(void);
45 PyMODINIT_FUNC initmain(void);
46 PyMODINIT_FUNC initmsgwin(void);
47 PyMODINIT_FUNC initnavqueue(void);
48 PyMODINIT_FUNC initprefs(void);
49 PyMODINIT_FUNC initproject(void);
50 PyMODINIT_FUNC initscintilla(void);
51 PyMODINIT_FUNC initsearch(void);
52 PyMODINIT_FUNC inittemplates(void);
53 PyMODINIT_FUNC initui_utils(void);
54 PyMODINIT_FUNC initkeybindings(void);
55
56
57 static void
GeanyPy_start_interpreter(void)58 GeanyPy_start_interpreter(void)
59 {
60 gchar *init_code;
61 gchar *py_dir = NULL;
62
63
64 #ifndef GEANYPY_WINDOWS
65 { /* Prevents a crash in the dynload thingy
66 TODO: is this or the old dlopen version even needed? */
67 GModule *mod = g_module_open(GEANYPY_PYTHON_LIBRARY, G_MODULE_BIND_LAZY);
68 if (!mod) {
69 g_warning(_("Unable to pre-load Python library: %s."), g_module_error());
70 return;
71 }
72 g_module_close(mod);
73 }
74 #endif
75
76 Py_Initialize();
77
78 /* Import the C modules */
79 initapp();
80 initdialogs();
81 initdocument();
82 initeditor();
83 initencoding();
84 initfiletypes();
85 initglog();
86 inithighlighting();
87 initmain();
88 initmsgwin();
89 initnavqueue();
90 initprefs();
91 initproject();
92 initscintilla();
93 initsearch();
94 inittemplates();
95 initui_utils();
96 initkeybindings();
97
98 #ifdef GEANYPY_WINDOWS
99 { /* On windows, get path at runtime since we don't really know where
100 * Geany is installed ahead of time. */
101 gchar *geany_base_dir;
102 geany_base_dir = g_win32_get_package_installation_directory_of_module(NULL);
103 if (geany_base_dir)
104 {
105 py_dir = g_build_filename(geany_base_dir, "lib", "geanypy", NULL);
106 g_free(geany_base_dir);
107 }
108 if (!g_file_test(py_dir, G_FILE_TEST_EXISTS))
109 {
110 g_critical("The path to the `geany' module was not found: %s", py_dir);
111 g_free(py_dir);
112 py_dir = g_strdup(""); /* will put current dir on path? */
113 }
114 }
115 #else
116 py_dir = g_strdup(GEANYPY_PYTHON_DIR);
117 #endif
118
119 /* Adjust Python path to find wrapper package (geany) */
120 init_code = g_strdup_printf(
121 "import os, sys\n"
122 "path = '%s'.replace('~', os.path.expanduser('~'))\n"
123 "sys.path.append(path)\n"
124 "path = '%s/plugins'.replace('~', os.path.expanduser('~'))\n"
125 "sys.path.append(path)\n"
126 "path = '%s'.replace('~', os.path.expanduser('~'))\n"
127 "sys.path.append(path)\n"
128 "import geany\n", py_dir, geany_data->app->configdir, GEANYPY_PLUGIN_DIR);
129 g_free(py_dir);
130
131 PyRun_SimpleString(init_code);
132 g_free(init_code);
133
134 }
135
136 static void
GeanyPy_stop_interpreter(void)137 GeanyPy_stop_interpreter(void)
138 {
139 if (Py_IsInitialized())
140 Py_Finalize();
141 }
142
143 typedef struct
144 {
145 PyObject *base;
146 SignalManager *signal_manager;
147 }
148 GeanyPyData;
149
150 typedef struct
151 {
152 PyObject *class;
153 PyObject *module;
154 PyObject *instance;
155 }
156 GeanyPyPluginData;
157
has_error(void)158 static gboolean has_error(void)
159 {
160 if (PyErr_Occurred())
161 {
162 PyErr_Print();
163 return TRUE;
164 }
165 return FALSE;
166 }
167
geanypy_proxy_init(GeanyPlugin * plugin,gpointer pdata)168 static gboolean geanypy_proxy_init(GeanyPlugin *plugin, gpointer pdata)
169 {
170 GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
171
172 data->instance = PyObject_CallObject(data->class, NULL);
173 if (has_error())
174 return FALSE;
175
176 return TRUE;
177 }
178
179
geanypy_proxy_cleanup(GeanyPlugin * plugin,gpointer pdata)180 static void geanypy_proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
181 {
182 GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
183
184 PyObject_CallMethod(data->instance, "cleanup", NULL);
185 if (has_error())
186 return;
187 }
188
189
geanypy_proxy_configure(GeanyPlugin * plugin,GtkDialog * parent,gpointer pdata)190 static GtkWidget *geanypy_proxy_configure(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata)
191 {
192 GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
193 PyObject *o, *oparent;
194 GObject *widget;
195
196 oparent = pygobject_new(G_OBJECT(parent));
197 o = PyObject_CallMethod(data->instance, "configure", "O", oparent, NULL);
198 Py_DECREF(oparent);
199
200 if (!has_error() && o != Py_None)
201 {
202 /* Geany wants only the underlying GtkWidget, we must only ref that
203 * and free the pygobject wrapper */
204 widget = g_object_ref(pygobject_get(o));
205 Py_DECREF(o);
206 return GTK_WIDGET(widget);
207 }
208
209 Py_DECREF(o); /* Must unref even if it's Py_None */
210 return NULL;
211 }
212
213
do_show_configure(GtkWidget * button,gpointer pdata)214 static void do_show_configure(GtkWidget *button, gpointer pdata)
215 {
216 GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
217 PyObject_CallMethod(data->instance, "show_configure", NULL);
218 }
219
220
geanypy_proxy_configure_legacy(GeanyPlugin * plugin,GtkDialog * parent,gpointer pdata)221 static GtkWidget *geanypy_proxy_configure_legacy(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata)
222 {
223 GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
224 PyObject *o, *oparent;
225 GtkWidget *box, *label, *button, *align;
226 gchar *text;
227
228 /* This creates a simple page that has only one button to show the plugin's legacy configure
229 * dialog. It is for older plugins that implement show_configure(). It's not pretty but
230 * it provides basic backwards compatibility. */
231 box = gtk_vbox_new(FALSE, 2);
232
233 text = g_strdup_printf("The plugin \"%s\" is older and hasn't been updated\nto provide a configuration UI. However, it provides a dialog to\nallow you to change the plugin's preferences.", plugin->info->name);
234 label = gtk_label_new(text);
235
236 align = gtk_alignment_new(0, 0, 1, 1);
237 gtk_container_add(GTK_CONTAINER(align), label);
238 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 6, 2, 2);
239 gtk_box_pack_start(GTK_BOX(box), align, FALSE, FALSE, 0);
240
241 button = gtk_button_new_with_label("Open dialog");
242 align = gtk_alignment_new(0.5, 0, 0.3f, 1);
243 gtk_container_add(GTK_CONTAINER(align), button);
244 g_signal_connect(button, "clicked", (GCallback) do_show_configure, pdata);
245 gtk_box_pack_start(GTK_BOX(box), align, FALSE, TRUE, 0);
246
247 gtk_widget_show_all(box);
248 g_free(text);
249 return box;
250 }
251
geanypy_proxy_help(GeanyPlugin * plugin,gpointer pdata)252 static void geanypy_proxy_help(GeanyPlugin *plugin, gpointer pdata)
253 {
254 GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
255
256 PyObject_CallMethod(data->instance, "help", NULL);
257 if (has_error())
258 return;
259 }
260
261 static gint
geanypy_probe(GeanyPlugin * proxy,const gchar * filename,gpointer pdata)262 geanypy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata)
263 {
264 gchar *file_plugin = g_strdup_printf("%.*s.plugin",
265 (int)(strrchr(filename, '.') - filename), filename);
266 gint ret = PROXY_IGNORED;
267
268 /* avoid clash with libpeas py plugins, those come with a corresponding <plugin>.plugin file */
269 if (!g_file_test(file_plugin, G_FILE_TEST_EXISTS))
270 ret = PROXY_MATCHED;
271
272 g_free(file_plugin);
273 return ret;
274 }
275
276
string_from_attr(PyObject * o,const gchar * attr)277 static const gchar *string_from_attr(PyObject *o, const gchar *attr)
278 {
279 PyObject *string = PyObject_GetAttrString(o, attr);
280 const gchar *ret = PyString_AsString(string);
281 Py_DECREF(string);
282
283 return ret;
284 }
285
286
287 static gpointer
geanypy_load(GeanyPlugin * proxy,GeanyPlugin * subplugin,const gchar * filename,gpointer pdata)288 geanypy_load(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata)
289 {
290 GeanyPyData *data = pdata;
291 PyObject *fromlist, *module, *dict, *key, *val, *found = NULL;
292 Py_ssize_t pos = 0;
293 gchar *modulename, *dot;
294 gpointer ret = NULL;
295
296 modulename = g_path_get_basename(filename);
297 /* We are guaranteed that filename has a .py extension
298 * because we did geany_plugin_register_proxy() for it */
299 dot = strrchr(modulename, '.');
300 *dot = '\0';
301 /* we need a fromlist to be able to import modules with a '.' in the
302 * name. -- libpeas */
303 fromlist = PyTuple_New (0);
304
305 module = PyImport_ImportModuleEx(modulename, NULL, NULL, fromlist);
306 if (has_error() || !module)
307 goto err;
308
309 dict = PyModule_GetDict(module);
310
311 while (PyDict_Next (dict, &pos, &key, &val) && found == NULL)
312 {
313 if (PyType_Check(val) && PyObject_IsSubclass(val, data->base))
314 found = val;
315 }
316
317 if (found)
318 {
319 GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData);
320 PluginInfo *info = subplugin->info;
321 GeanyPluginFuncs *funcs = subplugin->funcs;
322 PyObject *caps = PyCapsule_New(subplugin, "GeanyPlugin", NULL);
323 Py_INCREF(found);
324 pdata->module = module;
325 pdata->class = found;
326 PyObject_SetAttrString(pdata->class, "__geany_plugin__", caps);
327 pdata->instance = NULL;
328 info->name = string_from_attr(pdata->class, "__plugin_name__");
329 info->description = string_from_attr(pdata->class, "__plugin_description__");
330 info->version = string_from_attr(pdata->class, "__plugin_version__");
331 info->author = string_from_attr(pdata->class, "__plugin_author__");
332 funcs->init = geanypy_proxy_init;
333 funcs->cleanup = geanypy_proxy_cleanup;
334 if (PyObject_HasAttrString(found, "configure"))
335 funcs->configure = geanypy_proxy_configure;
336 else if (PyObject_HasAttrString(found, "show_configure"))
337 funcs->configure = geanypy_proxy_configure_legacy;
338 if (PyObject_HasAttrString(found, "help"))
339 funcs->help = geanypy_proxy_help;
340 if (GEANY_PLUGIN_REGISTER_FULL(subplugin, 224, pdata, NULL))
341 ret = pdata;
342 }
343
344 err:
345 g_free(modulename);
346 Py_DECREF(fromlist);
347 return ret;
348 }
349
350
351 static void
geanypy_unload(GeanyPlugin * plugin,GeanyPlugin * subplugin,gpointer load_data,gpointer pdata_)352 geanypy_unload(GeanyPlugin *plugin, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata_)
353 {
354 GeanyPyPluginData *pdata = load_data;
355
356 Py_XDECREF(pdata->instance);
357 Py_DECREF(pdata->class);
358 Py_DECREF(pdata->module);
359 while (PyGC_Collect());
360 g_slice_free(GeanyPyPluginData, pdata);
361 }
362
363
geanypy_init(GeanyPlugin * plugin_,gpointer pdata)364 static gboolean geanypy_init(GeanyPlugin *plugin_, gpointer pdata)
365 {
366 const gchar *exts[] = { "py", NULL };
367 GeanyPyData *state = pdata;
368 PyObject *module;
369
370 plugin_->proxy_funcs->probe = geanypy_probe;
371 plugin_->proxy_funcs->load = geanypy_load;
372 plugin_->proxy_funcs->unload = geanypy_unload;
373
374 geany_data = plugin_->geany_data;
375
376 GeanyPy_start_interpreter();
377 state->signal_manager = signal_manager_new(plugin_);
378
379 module = PyImport_ImportModule("geany.plugin");
380 if (has_error() || !module)
381 goto err;
382
383 state->base = PyObject_GetAttrString(module, "Plugin");
384 Py_DECREF(module);
385 if (has_error() || !state->base)
386 goto err;
387
388 if (!geany_plugin_register_proxy(plugin_, exts)) {
389 Py_DECREF(state->base);
390 goto err;
391 }
392
393 return TRUE;
394
395 err:
396 signal_manager_free(state->signal_manager);
397 GeanyPy_stop_interpreter();
398 return FALSE;
399 }
400
401
geanypy_cleanup(GeanyPlugin * plugin,gpointer pdata)402 static void geanypy_cleanup(GeanyPlugin *plugin, gpointer pdata)
403 {
404 GeanyPyData *state = pdata;
405 signal_manager_free(state->signal_manager);
406 Py_DECREF(state->base);
407 GeanyPy_stop_interpreter();
408 }
409
410 G_MODULE_EXPORT void
geany_load_module(GeanyPlugin * plugin)411 geany_load_module(GeanyPlugin *plugin)
412 {
413 GeanyPyData *state = g_new0(GeanyPyData, 1);
414
415 plugin->info->name = _("GeanyPy");
416 plugin->info->description = _("Python plugins support");
417 plugin->info->version = "1.0";
418 plugin->info->author = "Matthew Brush <mbrush@codebrainz.ca>";
419 plugin->funcs->init = geanypy_init;
420 plugin->funcs->cleanup = geanypy_cleanup;
421
422 GEANY_PLUGIN_REGISTER_FULL(plugin, 226, state, g_free);
423 }
424