aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Halase <jordan@halase.me>2020-03-09 13:40:51 -0500
committerJordan Halase <jordan@halase.me>2020-03-09 13:40:51 -0500
commit760fb45fab539d3b228076f64c3d6480e9e57fd7 (patch)
tree3860c2b5023c82eadf3d27358679b218930e8478
First commitHEADmaster
-rw-r--r--.gitignore3
-rwxr-xr-xbuild.sh1
-rw-r--r--main.c86
-rw-r--r--readme.md22
-rw-r--r--ui/MainWidget.py29
-rw-r--r--ui/__init__.py0
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
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..47d8741
--- /dev/null
+++ b/main.c
@@ -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