summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2016-12-10 10:18:17 -0500
committerDavid Robillard <d@drobilla.net>2016-12-10 10:18:17 -0500
commit80e9c5717d7a68e3a03640de84e36390caa21cbe (patch)
tree5267b394a3cb0e4ae27d9373769a30dbad52ea23
parentd0252036f46ff5eb606973695cb975319e88821b (diff)
downloadsuil-80e9c5717d7a68e3a03640de84e36390caa21cbe.tar.gz
suil-80e9c5717d7a68e3a03640de84e36390caa21cbe.tar.bz2
suil-80e9c5717d7a68e3a03640de84e36390caa21cbe.zip
Add support for X11 in Gtk3
-rw-r--r--NEWS8
-rw-r--r--src/instance.c9
-rw-r--r--src/x11_in_gtk3.c401
-rw-r--r--wscript31
4 files changed, 447 insertions, 2 deletions
diff --git a/NEWS b/NEWS
index 1cf6a91..5da5eab 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,10 @@
-suil (0.8.4) unstable;
+suil (0.8.5) unstable;
+
+ * Add support for X11 in Gtk3
+
+ -- David Robillard <d@drobilla.net> Sat, 10 Dec 2016 10:08:03 -0500
+
+suil (0.8.4) stable;
* Configure based on compiler target OS for cross-compilation
* Add Cocoa in Gtk wrapper (patch from Robin Gareus)
diff --git a/src/instance.c b/src/instance.c
index 675f220..29ff3f9 100644
--- a/src/instance.c
+++ b/src/instance.c
@@ -23,6 +23,7 @@
#include "./suil_internal.h"
#define GTK2_UI_URI LV2_UI__GtkUI
+#define GTK3_UI_URI LV2_UI__Gtk3UI
#define QT4_UI_URI LV2_UI__Qt4UI
#define QT5_UI_URI LV2_UI__Qt5UI
#define X11_UI_URI LV2_UI__X11UI
@@ -49,6 +50,8 @@ suil_ui_supported(const char* container_type_uri,
&& !strcmp(ui_type_uri, GTK2_UI_URI))
|| (!strcmp(container_type_uri, GTK2_UI_URI)
&& !strcmp(ui_type_uri, X11_UI_URI))
+ || (!strcmp(container_type_uri, GTK3_UI_URI)
+ && !strcmp(ui_type_uri, X11_UI_URI))
|| (!strcmp(container_type_uri, GTK2_UI_URI)
&& !strcmp(ui_type_uri, WIN_UI_URI))
|| (!strcmp(container_type_uri, GTK2_UI_URI)
@@ -95,6 +98,12 @@ open_wrapper(SuilHost* host,
module_name = "suil_x11_in_gtk2";
}
#endif
+#ifdef SUIL_WITH_X11_IN_GTK3
+ if (!strcmp(container_type_uri, GTK3_UI_URI)
+ && !strcmp(ui_type_uri, X11_UI_URI)) {
+ module_name = "suil_x11_in_gtk3";
+ }
+#endif
#ifdef SUIL_WITH_WIN_IN_GTK2
if (!strcmp(container_type_uri, GTK2_UI_URI)
&& !strcmp(ui_type_uri, WIN_UI_URI)) {
diff --git a/src/x11_in_gtk3.c b/src/x11_in_gtk3.c
new file mode 100644
index 0000000..2fd7522
--- /dev/null
+++ b/src/x11_in_gtk3.c
@@ -0,0 +1,401 @@
+/*
+ Copyright 2011-2016 David Robillard <http://drobilla.net>
+
+ 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.
+
+ THIS 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 <string.h>
+
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+
+#include "./suil_internal.h"
+
+#include "lv2/lv2plug.in/ns/ext/options/options.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+
+#define SUIL_TYPE_X11_WRAPPER (suil_x11_wrapper_get_type())
+#define SUIL_X11_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_X11_WRAPPER, SuilX11Wrapper))
+
+typedef struct _SuilX11Wrapper SuilX11Wrapper;
+typedef struct _SuilX11WrapperClass SuilX11WrapperClass;
+
+struct _SuilX11Wrapper {
+ GtkSocket socket;
+ GtkPlug* plug;
+ SuilWrapper* wrapper;
+ SuilInstance* instance;
+ const LV2UI_Idle_Interface* idle_iface;
+ guint idle_id;
+ guint idle_ms;
+};
+
+struct _SuilX11WrapperClass {
+ GtkSocketClass parent_class;
+};
+
+GType suil_x11_wrapper_get_type(void); // Accessor for SUIL_TYPE_X11_WRAPPER
+
+G_DEFINE_TYPE(SuilX11Wrapper, suil_x11_wrapper, GTK_TYPE_SOCKET)
+
+/**
+ Check if 'swallowed' subwindow is known to the X server.
+
+ Gdk/GTK can mark the window as realized, mapped and visible even though
+ there is no window-ID on the X server for it yet. Then,
+ suil_x11_on_size_allocate() will cause a "BadWinow" X error.
+*/
+static bool
+x_window_is_valid(SuilX11Wrapper* socket)
+{
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
+ Window root = 0;
+ Window parent = 0;
+ Window* children = NULL;
+ unsigned childcount = 0;
+
+ XQueryTree(GDK_WINDOW_XDISPLAY(window),
+ GDK_WINDOW_XID(window),
+ &root, &parent, &children, &childcount);
+ for (unsigned i = 0; i < childcount; ++i) {
+ if (children[i] == (Window)socket->instance->ui_widget) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static gboolean
+on_plug_removed(GtkSocket* sock, gpointer data)
+{
+ SuilX11Wrapper* const self = SUIL_X11_WRAPPER(sock);
+
+ if (self->idle_id) {
+ g_source_remove(self->idle_id);
+ self->idle_id = 0;
+ }
+
+ if (self->instance->handle) {
+ self->instance->descriptor->cleanup(self->instance->handle);
+ self->instance->handle = NULL;
+ }
+
+ self->plug = NULL;
+ return TRUE;
+}
+
+static void
+suil_x11_wrapper_finalize(GObject* gobject)
+{
+ SuilX11Wrapper* const self = SUIL_X11_WRAPPER(gobject);
+
+ self->wrapper->impl = NULL;
+
+ G_OBJECT_CLASS(suil_x11_wrapper_parent_class)->finalize(gobject);
+}
+
+static void
+suil_x11_wrapper_realize(GtkWidget* w)
+{
+ SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w);
+ GtkSocket* const socket = GTK_SOCKET(w);
+
+ if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize) {
+ GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->realize(w);
+ }
+
+ gtk_socket_add_id(socket, gtk_plug_get_id(wrap->plug));
+
+ gtk_widget_realize(GTK_WIDGET(wrap->plug));
+
+ gtk_widget_set_sensitive(GTK_WIDGET(wrap->plug), TRUE);
+ gtk_widget_set_can_focus(GTK_WIDGET(wrap->plug), TRUE);
+ gtk_widget_grab_focus(GTK_WIDGET(wrap->plug));
+}
+
+static void
+suil_x11_wrapper_show(GtkWidget* w)
+{
+ SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(w);
+
+ if (GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show) {
+ GTK_WIDGET_CLASS(suil_x11_wrapper_parent_class)->show(w);
+ }
+
+ gtk_widget_show(GTK_WIDGET(wrap->plug));
+}
+
+static gboolean
+forward_key_event(SuilX11Wrapper* socket,
+ GdkEvent* gdk_event)
+{
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
+ GdkScreen* screen = gdk_visual_get_screen(gdk_window_get_visual(window));
+
+ Window target_window;
+ if (gdk_event->any.window == window) {
+ // Event sent up to the plug window, forward it up to the parent
+ GtkWidget* widget = GTK_WIDGET(socket->instance->host_widget);
+ GdkWindow* parent = gtk_widget_get_parent_window(widget);
+ if (parent) {
+ target_window = GDK_WINDOW_XID(parent);
+ } else {
+ return FALSE; // Wrapper is a top-level window, do nothing
+ }
+ } else {
+ // Event sent anywhere else, send to the plugin
+ target_window = (Window)socket->instance->ui_widget;
+ }
+
+ XKeyEvent xev;
+ memset(&xev, 0, sizeof(xev));
+ xev.type = (gdk_event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
+ xev.root = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
+ xev.window = target_window;
+ xev.subwindow = None;
+ xev.time = gdk_event->key.time;
+ xev.state = gdk_event->key.state;
+ xev.keycode = gdk_event->key.hardware_keycode;
+
+ XSendEvent(GDK_WINDOW_XDISPLAY(window),
+ target_window,
+ False,
+ NoEventMask,
+ (XEvent*)&xev);
+
+ return (gdk_event->any.window != window);
+}
+
+static gboolean
+idle_size_request(gpointer user_data)
+{
+ GtkWidget* w = GTK_WIDGET(user_data);
+ gtk_widget_queue_resize(w);
+ return FALSE;
+}
+
+static void
+forward_size_request(SuilX11Wrapper* socket,
+ GtkAllocation* allocation)
+{
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(socket->plug));
+ if (x_window_is_valid(socket)) {
+ // Calculate allocation size constrained to X11 limits for widget
+ int width = allocation->width;
+ int height = allocation->height;
+ XSizeHints hints;
+ XGetNormalHints(GDK_WINDOW_XDISPLAY(window),
+ (Window)socket->instance->ui_widget,
+ &hints);
+ if (hints.flags & PMaxSize) {
+ width = MIN(width, hints.max_width);
+ height = MIN(height, hints.max_height);
+ }
+ if (hints.flags & PMinSize) {
+ width = MAX(width, hints.min_width);
+ height = MAX(height, hints.min_height);
+ }
+
+ // Resize widget window
+ XResizeWindow(GDK_WINDOW_XDISPLAY(window),
+ (Window)socket->instance->ui_widget,
+ width, height);
+
+ // Get actual widget geometry
+ Window root;
+ int wx, wy;
+ unsigned int ww, wh;
+ unsigned int ignored;
+ XGetGeometry(GDK_WINDOW_XDISPLAY(window),
+ (Window)socket->instance->ui_widget,
+ &root,
+ &wx, &wy, &ww, &wh,
+ &ignored, &ignored);
+
+ // Center widget in allocation
+ wx = (allocation->width - ww) / 2;
+ wy = (allocation->height - wh) / 2;
+ XMoveWindow(GDK_WINDOW_XDISPLAY(window),
+ (Window)socket->instance->ui_widget,
+ wx, wy);
+ } else {
+ /* Child has not been realized, so unable to resize now.
+ Queue an idle resize. */
+ g_idle_add(idle_size_request, socket->plug);
+ }
+}
+
+static gboolean
+suil_x11_wrapper_key_event(GtkWidget* widget,
+ GdkEventKey* event)
+{
+ SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
+
+ if (self->plug) {
+ return forward_key_event(self, (GdkEvent*)event);
+ }
+
+ return FALSE;
+}
+
+static void
+suil_x11_on_size_allocate(GtkWidget* widget,
+ GtkAllocation* a)
+{
+ SuilX11Wrapper* const self = SUIL_X11_WRAPPER(widget);
+
+ if (self->plug
+ && gtk_widget_get_realized(widget)
+ && gtk_widget_get_mapped(widget)
+ && gtk_widget_get_visible(widget)) {
+ forward_size_request(self, a);
+ }
+}
+
+static void
+suil_x11_wrapper_class_init(SuilX11WrapperClass* klass)
+{
+ GObjectClass* const gobject_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass* const widget_class = GTK_WIDGET_CLASS(klass);
+
+ gobject_class->finalize = suil_x11_wrapper_finalize;
+ widget_class->realize = suil_x11_wrapper_realize;
+ widget_class->show = suil_x11_wrapper_show;
+ widget_class->key_press_event = suil_x11_wrapper_key_event;
+}
+
+static void
+suil_x11_wrapper_init(SuilX11Wrapper* self)
+{
+ self->plug = GTK_PLUG(gtk_plug_new(0));
+ self->wrapper = NULL;
+ self->instance = NULL;
+ self->idle_iface = NULL;
+ self->idle_ms = 1000 / 30; // 30 Hz default
+}
+
+static int
+wrapper_resize(LV2UI_Feature_Handle handle, int width, int height)
+{
+ gtk_widget_set_size_request(GTK_WIDGET(handle), width, height);
+ return 0;
+}
+
+static gboolean
+suil_x11_wrapper_idle(void* data)
+{
+ SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(data);
+
+ wrap->idle_iface->idle(wrap->instance->handle);
+
+ return TRUE; // Continue calling
+}
+
+static int
+wrapper_wrap(SuilWrapper* wrapper,
+ SuilInstance* instance)
+{
+ SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl);
+
+ instance->host_widget = GTK_WIDGET(wrap);
+ wrap->wrapper = wrapper;
+ wrap->instance = instance;
+
+ const LV2UI_Idle_Interface* idle_iface = NULL;
+ if (instance->descriptor->extension_data) {
+ idle_iface = (const LV2UI_Idle_Interface*)
+ instance->descriptor->extension_data(LV2_UI__idleInterface);
+ }
+ if (idle_iface) {
+ wrap->idle_iface = idle_iface;
+ wrap->idle_id = g_timeout_add(
+ wrap->idle_ms, suil_x11_wrapper_idle, wrap);
+ }
+
+ g_signal_connect(G_OBJECT(wrap),
+ "plug-removed",
+ G_CALLBACK(on_plug_removed),
+ NULL);
+
+ g_signal_connect(G_OBJECT(wrap),
+ "size-allocate",
+ G_CALLBACK(suil_x11_on_size_allocate),
+ NULL);
+
+ return 0;
+}
+
+static void
+wrapper_free(SuilWrapper* wrapper)
+{
+ if (wrapper->impl) {
+ SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(wrapper->impl);
+ gtk_widget_destroy(GTK_WIDGET(wrap));
+ }
+}
+
+SUIL_LIB_EXPORT
+SuilWrapper*
+suil_wrapper_new(SuilHost* host,
+ const char* host_type_uri,
+ const char* ui_type_uri,
+ LV2_Feature*** features,
+ unsigned n_features)
+{
+ SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper));
+ wrapper->wrap = wrapper_wrap;
+ wrapper->free = wrapper_free;
+
+ SuilX11Wrapper* const wrap = SUIL_X11_WRAPPER(
+ g_object_new(SUIL_TYPE_X11_WRAPPER, NULL));
+
+ wrapper->impl = wrap;
+ wrapper->resize.handle = wrap;
+ wrapper->resize.ui_resize = wrapper_resize;
+
+ gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE);
+ gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE);
+
+ suil_add_feature(features, &n_features, LV2_UI__parent,
+ (void*)(intptr_t)gtk_plug_get_id(wrap->plug));
+
+ suil_add_feature(features, &n_features, LV2_UI__resize,
+ &wrapper->resize);
+
+ suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL);
+
+ // Scan for URID map and options
+ LV2_URID_Map* map = NULL;
+ LV2_Options_Option* options = NULL;
+ for (LV2_Feature** f = *features; *f && (!map || !options); ++f) {
+ if (!strcmp((*f)->URI, LV2_OPTIONS__options)) {
+ options = (LV2_Options_Option*)(*f)->data;
+ } else if (!strcmp((*f)->URI, LV2_URID__map)) {
+ map = (LV2_URID_Map*)(*f)->data;
+ }
+ }
+
+ if (map && options) {
+ // Set UI update rate if given
+ LV2_URID ui_updateRate = map->map(map->handle, LV2_UI__updateRate);
+ for (LV2_Options_Option* o = options; o->key; ++o) {
+ if (o->key == ui_updateRate) {
+ wrap->idle_ms = 1000.0f / *(const float*)o->value;
+ break;
+ }
+ }
+ }
+
+ return wrapper;
+}
diff --git a/wscript b/wscript
index a1b261a..8b2f7c4 100644
--- a/wscript
+++ b/wscript
@@ -10,7 +10,7 @@ from waflib import TaskGen
# major increment <=> incompatible changes
# minor increment <=> compatible changes (additions)
# micro increment <=> no interface changes
-SUIL_VERSION = '0.8.4'
+SUIL_VERSION = '0.8.5'
SUIL_MAJOR_VERSION = '0'
# Mandatory waf variables
@@ -38,6 +38,9 @@ def options(opt):
opt.add_option('--gtk2-lib-name', type='string', dest='gtk2_lib_name',
default="libgtk-x11-2.0.so.0",
help="Gtk2 library name [Default: libgtk-x11-2.0.so.0]")
+ opt.add_option('--gtk3-lib-name', type='string', dest='gtk3_lib_name',
+ default="libgtk-x11-3.0.so.0",
+ help="Gtk3 library name [Default: libgtk-x11-3.0.so.0]")
def configure(conf):
conf.line_just = 40
@@ -77,6 +80,12 @@ def configure(conf):
autowaf.check_pkg(conf, 'gtk+-quartz-2.0', uselib_store='GTK2_QUARTZ',
atleast_version='2.0.0', mandatory=False)
+ autowaf.check_pkg(conf, 'gtk+-3.0', uselib_store='GTK3',
+ atleast_version='3.14.0', mandatory=False)
+
+ autowaf.check_pkg(conf, 'gtk+-x11-3.0', uselib_store='GTK3_X11',
+ atleast_version='3.14.0', mandatory=False)
+
if not Options.options.no_qt:
if not Options.options.no_qt4:
autowaf.check_pkg(conf, 'QtGui', uselib_store='QT4',
@@ -95,6 +104,7 @@ def configure(conf):
conf.env.LIBDIR + '/suil-' + SUIL_MAJOR_VERSION)
autowaf.define(conf, 'SUIL_DIR_SEP', '/')
autowaf.define(conf, 'SUIL_GTK2_LIB_NAME', Options.options.gtk2_lib_name);
+ autowaf.define(conf, 'SUIL_GTK3_LIB_NAME', Options.options.gtk3_lib_name);
if conf.env.HAVE_GTK2 and conf.env.HAVE_QT4:
autowaf.define(conf, 'SUIL_WITH_GTK2_IN_QT4', 1)
@@ -106,6 +116,9 @@ def configure(conf):
if conf.env.HAVE_GTK2 and conf.env.HAVE_GTK2_X11:
autowaf.define(conf, 'SUIL_WITH_X11_IN_GTK2', 1)
+ if conf.env.HAVE_GTK3 and conf.env.HAVE_GTK3_X11:
+ autowaf.define(conf, 'SUIL_WITH_X11_IN_GTK3', 1)
+
if conf.env.HAVE_GTK2 and conf.env.HAVE_GTK2_QUARTZ:
autowaf.define(conf, 'SUIL_WITH_COCOA_IN_GTK2', 1)
@@ -141,6 +154,10 @@ def configure(conf):
if conf.env.HAVE_GTK2:
autowaf.display_msg(conf, "Gtk2 Library Name",
conf.env.SUIL_GTK2_LIB_NAME)
+ autowaf.display_msg(conf, "Gtk3 Support", bool(conf.env.HAVE_GTK3))
+ if conf.env.HAVE_GTK3:
+ autowaf.display_msg(conf, "Gtk3 Library Name",
+ conf.env.SUIL_GTK3_LIB_NAME)
autowaf.display_msg(conf, "Qt4 Support", bool(conf.env.HAVE_QT4))
autowaf.display_msg(conf, "Qt5 Support", bool(conf.env.HAVE_QT5))
print('')
@@ -244,6 +261,18 @@ def build(bld):
linkflags = bld.env.NODELETE_FLAGS)
autowaf.use_lib(bld, obj, 'GTK2 GTK2_X11 LV2')
+ if bld.env.SUIL_WITH_X11_IN_GTK3:
+ obj = bld(features = 'c cshlib',
+ source = 'src/x11_in_gtk3.c',
+ target = 'suil_x11_in_gtk3',
+ includes = ['.'],
+ defines = ['SUIL_SHARED', 'SUIL_INTERNAL'],
+ install_path = module_dir,
+ cflags = cflags,
+ lib = modlib + ['X11'],
+ linkflags = bld.env.NODELETE_FLAGS)
+ autowaf.use_lib(bld, obj, 'GTK3 GTK3_X11 LV2')
+
if bld.env.SUIL_WITH_COCOA_IN_GTK2:
obj = bld(features = 'cxx cshlib',
source = 'src/cocoa_in_gtk2.mm',