diff options
author | Jordan Halase <jordan@halase.me> | 2020-03-09 13:40:51 -0500 |
---|---|---|
committer | Jordan Halase <jordan@halase.me> | 2020-03-09 13:40:51 -0500 |
commit | 760fb45fab539d3b228076f64c3d6480e9e57fd7 (patch) | |
tree | 3860c2b5023c82eadf3d27358679b218930e8478 |
-rw-r--r-- | .gitignore | 3 | ||||
-rwxr-xr-x | build.sh | 1 | ||||
-rw-r--r-- | main.c | 86 | ||||
-rw-r--r-- | readme.md | 22 | ||||
-rw-r--r-- | ui/MainWidget.py | 29 | ||||
-rw-r--r-- | ui/__init__.py | 0 |
6 files changed, 141 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ac82bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +main +__pycache__/ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..167a946 --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +gcc -g main.c `pkg-config --cflags --libs python3` `pkg-config --cflags --libs gtk+-3.0` -o main @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 Jordan Halase <jordan@halase.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <python3.7/Python.h> +#include <pygobject-3.0/pygobject.h> +#include <gtk/gtk.h> + +/* Name of the Python GTK Widget class */ +#define WIDGET_NAME "MainWidget" + +/* Name of the Python module */ +#define MODULE_NAME ("ui." WIDGET_NAME) + +/** Initialize a Python interpreter and load the main module */ +static PyObject *init_python_module(const char *module_name) +{ + Py_Initialize(); + + PyObject *sys_path = PySys_GetObject("path"); + PyList_Append(sys_path, PyUnicode_FromString(".")); + + PyObject *module = PyImport_ImportModule(module_name); + if (!module) { + PyErr_Print(); + } + + return module; +} + +/** Construct a new Python widget from a loaded module */ +static GtkWidget *new_python_widget(PyObject *module) +{ + PyObject *widget_py = PyObject_CallMethod(module, WIDGET_NAME, ""); + if (!widget_py) { + PyErr_Print(); + return NULL; + } + + GObject *observed = pygobject_get(widget_py); + if (!observed || !G_IS_OBJECT(observed)) { + fprintf(stderr, WIDGET_NAME " not a GObject\n"); + } + return GTK_WIDGET(observed); +} + +static void on_activate(GtkApplication *app) +{ + PyObject *module = init_python_module(MODULE_NAME); + + GtkWidget *win = gtk_application_window_new(app); + gtk_window_set_title(GTK_WINDOW(win), "PyGObject Widget from C"); + + GtkWidget *widget = new_python_widget(module); + gtk_container_add(GTK_CONTAINER(win), widget); + + g_signal_connect_swapped(win, "destroy", G_CALLBACK(gtk_widget_destroy), win); + + gtk_widget_show_all(win); +} + +int main(int argc, char **argv) +{ + GtkApplication *app = gtk_application_new("com.example.GtkApplication", + G_APPLICATION_FLAGS_NONE); + g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); + + g_application_run(G_APPLICATION(app), argc, argv); + + return Py_FinalizeEx(); +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..cd78d67 --- /dev/null +++ b/readme.md @@ -0,0 +1,22 @@ +# PyGObject From C + +Normally when creating graphical user interfaces using [GTK](https://gtk.org), +one would choose to write their whole project in C or another programming +language for which GTK bindings exist. + +For this example, we wish to use [PyGObject](https://pygobject.readthedocs.io) +to write a GTK application in Python, due to its simplicity, ubiquity, and +ablilty to do rapid ad-hoc development. However, if our application is part of +a larger user interface *not* written in Python, we need a way to bridge the +interface between Python and the host language. + +This proof-of-concept bridges PyGObject and C so that one may create GTK-based +user interfaces in Python as part of a larger interface with a C interface. +It does this by extracting the raw `GObject` C-pointer from the user-defined +`PyGObject` and passes it as a `GtkWidget` to the host. + +Please note that this is a proof-of-concept and may have issues with memory +management, error handling, and versioning. Whether Python or GTK is a good +fit for plugin UI development is debatable, the motivation for creating this +is for rapid ad-hoc UI creation during the development stage only, and not +intended for use in production. diff --git a/ui/MainWidget.py b/ui/MainWidget.py new file mode 100644 index 0000000..f285efc --- /dev/null +++ b/ui/MainWidget.py @@ -0,0 +1,29 @@ +# Copyright (C) 2020 Jordan Halase <jordan@halase.me> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +class MainWidget(Gtk.Box): + + def __init__(self): + Gtk.Box.__init__(self) + + self.button = Gtk.Button(label="Python Button") + self.button.connect("clicked", self.on_button_clicked) + self.pack_start(self.button, True, True, 0) + + def on_button_clicked(self, widget): + print("Hello, from a custom Python widget!") diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ui/__init__.py |