diff options
-rw-r--r-- | NEWS | 8 | ||||
-rw-r--r-- | src/instance.c | 9 | ||||
-rw-r--r-- | src/x11_in_gtk3.c | 401 | ||||
-rw-r--r-- | wscript | 31 |
4 files changed, 447 insertions, 2 deletions
@@ -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; +} @@ -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', |