1 /*
2   Copyright 2011-2015 David Robillard <http://drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 #include <string.h>
18 
19 #include <gtk/gtk.h>
20 #include <gdk/gdkwin32.h>
21 
22 #ifndef WM_MOUSEWHEEL
23 #    define WM_MOUSEWHEEL 0x020A
24 #endif
25 #ifndef WM_MOUSEHWHEEL
26 #    define WM_MOUSEHWHEEL 0x020E
27 #endif
28 
29 #include "./suil_internal.h"
30 
31 #include "lv2/options/options.h"
32 #include "lv2/urid/urid.h"
33 
34 extern "C" {
35 
36 #define SUIL_TYPE_WIN_WRAPPER (suil_win_wrapper_get_type())
37 #define SUIL_WIN_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SUIL_TYPE_WIN_WRAPPER, SuilWinWrapper))
38 
39 typedef struct _SuilWinWrapper      SuilWinWrapper;
40 typedef struct _SuilWinWrapperClass SuilWinWrapperClass;
41 
42 struct _SuilWinWrapper {
43 	GtkDrawingArea              area;
44 	SuilWrapper*                wrapper;
45 	SuilInstance*               instance;
46 	GdkWindow*                  flt_win;
47 	const LV2UI_Idle_Interface* idle_iface;
48 	guint                       idle_id;
49 	guint                       idle_ms;
50 };
51 
52 struct _SuilWinWrapperClass {
53 	GtkDrawingAreaClass parent_class;
54 };
55 
56 GType suil_win_wrapper_get_type(void);  // Accessor for SUIL_TYPE_WIN_WRAPPER
57 
G_DEFINE_TYPE(SuilWinWrapper,suil_win_wrapper,GTK_TYPE_DRAWING_AREA)58 G_DEFINE_TYPE(SuilWinWrapper, suil_win_wrapper, GTK_TYPE_DRAWING_AREA)
59 
60 static void
61 suil_win_wrapper_finalize(GObject* gobject)
62 {
63 	SuilWinWrapper* const self = SUIL_WIN_WRAPPER(gobject);
64 
65 	self->wrapper->impl = NULL;
66 	self->instance      = NULL;
67 
68 	G_OBJECT_CLASS(suil_win_wrapper_parent_class)->finalize(gobject);
69 }
70 
71 static void
suil_win_size_allocate(GtkWidget * widget,GtkAllocation * allocation)72 suil_win_size_allocate(GtkWidget* widget, GtkAllocation* allocation)
73 {
74 	SuilWinWrapper* const self = SUIL_WIN_WRAPPER(widget);
75 	g_return_if_fail(self != NULL);
76 
77 	widget->allocation = *allocation;
78 	if (gtk_widget_get_realized(widget)) {
79 		gdk_window_move_resize(widget->window,
80 		                       allocation->x, allocation->y,
81 		                       allocation->width, allocation->height);
82 
83 		RECT wr = { 0, 0, (long)allocation->width, (long)allocation->height };
84 		AdjustWindowRectEx(&wr, WS_CHILD, FALSE, WS_EX_TOPMOST);
85 
86 		SetWindowPos((HWND)self->instance->ui_widget, HWND_NOTOPMOST,
87 		             0, 0, wr.right - wr.left, wr.bottom - wr.top,
88 		             SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
89 		UpdateWindow((HWND)self->instance->ui_widget);
90 		PostMessage((HWND)self->instance->ui_widget, WM_PAINT, 0, 0);
91 	}
92 }
93 
94 static void
suil_win_wrapper_class_init(SuilWinWrapperClass * klass)95 suil_win_wrapper_class_init(SuilWinWrapperClass* klass)
96 {
97 	GObjectClass* const   gobject_class = G_OBJECT_CLASS(klass);
98 	GtkWidgetClass* const widget_class  = (GtkWidgetClass*)(klass);
99 
100 	widget_class->size_allocate = suil_win_size_allocate;
101 	gobject_class->finalize     = suil_win_wrapper_finalize;
102 }
103 
104 static void
suil_win_wrapper_init(SuilWinWrapper * self)105 suil_win_wrapper_init(SuilWinWrapper* self)
106 {
107 	self->instance   = NULL;
108 	self->flt_win    = NULL;
109 	self->idle_iface = NULL;
110 	self->idle_ms    = 1000 / 30;  // 30 Hz default
111 }
112 
113 static gboolean
suil_win_wrapper_idle(void * data)114 suil_win_wrapper_idle(void* data)
115 {
116 	SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(data);
117 	wrap->idle_iface->idle(wrap->instance->handle);
118 	return TRUE;  // Continue calling
119 }
120 
121 static int
wrapper_resize(LV2UI_Feature_Handle handle,int width,int height)122 wrapper_resize(LV2UI_Feature_Handle handle, int width, int height)
123 {
124 	gtk_drawing_area_size(GTK_DRAWING_AREA(handle), width, height);
125 	return 0;
126 }
127 
128 static int
wrapper_wrap(SuilWrapper * wrapper,SuilInstance * instance)129 wrapper_wrap(SuilWrapper*  wrapper,
130              SuilInstance* instance)
131 {
132 	SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(wrapper->impl);
133 
134 	instance->host_widget = GTK_WIDGET(wrap);
135 	wrap->wrapper         = wrapper;
136 	wrap->instance        = instance;
137 
138 	const LV2UI_Idle_Interface* idle_iface = NULL;
139 	if (instance->descriptor->extension_data) {
140 		idle_iface = (const LV2UI_Idle_Interface*)
141 			instance->descriptor->extension_data(LV2_UI__idleInterface);
142 	}
143 	if (idle_iface) {
144 		wrap->idle_iface = idle_iface;
145 		wrap->idle_id    = g_timeout_add (wrap->idle_ms, suil_win_wrapper_idle, wrap);
146 	}
147 
148 	return 0;
149 }
150 
151 static GdkFilterReturn
event_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)152 event_filter(GdkXEvent* xevent, GdkEvent* event, gpointer data)
153 {
154 	SuilWinWrapper* wrap = (SuilWinWrapper*)data;
155 	MSG*            msg  = (MSG*)xevent;
156 	if (msg->message == WM_KEYDOWN || msg->message == WM_KEYUP) {
157 		// Forward keyboard events to UI window
158 		PostMessage((HWND)wrap->instance->ui_widget,
159 		            msg->message, msg->wParam, msg->lParam);
160 		return GDK_FILTER_REMOVE;
161 	} else if (msg->message == WM_MOUSEWHEEL || msg->message == WM_MOUSEHWHEEL) {
162 		PostMessage((HWND)wrap->instance->ui_widget,
163 		            msg->message, msg->wParam, msg->lParam);
164 		return GDK_FILTER_REMOVE;
165 	}
166 	return GDK_FILTER_CONTINUE;
167 }
168 
169 static void
wrapper_free(SuilWrapper * wrapper)170 wrapper_free(SuilWrapper* wrapper)
171 {
172 	if (wrapper->impl) {
173 		SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(wrapper->impl);
174 		if (wrap->idle_id) {
175 			g_source_remove(wrap->idle_id);
176 			wrap->idle_id = 0;
177 		}
178 
179 		gdk_window_remove_filter(wrap->flt_win, event_filter, wrapper->impl);
180 		gtk_object_destroy(GTK_OBJECT(wrap));
181 	}
182 }
183 
184 SUIL_LIB_EXPORT
185 SuilWrapper*
suil_wrapper_new(SuilHost * host,const char * host_type_uri,const char * ui_type_uri,LV2_Feature *** features,unsigned n_features)186 suil_wrapper_new(SuilHost*      host,
187                  const char*    host_type_uri,
188                  const char*    ui_type_uri,
189                  LV2_Feature*** features,
190                  unsigned       n_features)
191 {
192 	GtkWidget* parent = NULL;
193 	for (unsigned i = 0; i < n_features; ++i) {
194 		if (!strcmp((*features)[i]->URI, LV2_UI__parent)) {
195 			parent = (GtkWidget*)(*features)[i]->data;
196 		}
197 	}
198 
199 	if (!GTK_CONTAINER(parent)) {
200 		SUIL_ERRORF("No GtkContainer parent given for %s UI\n",
201 		            ui_type_uri);
202 		return NULL;
203 	}
204 
205 	SuilWrapper* wrapper = (SuilWrapper*)calloc(1, sizeof(SuilWrapper));
206 	wrapper->wrap = wrapper_wrap;
207 	wrapper->free = wrapper_free;
208 
209 	SuilWinWrapper* const wrap = SUIL_WIN_WRAPPER(
210 		g_object_new(SUIL_TYPE_WIN_WRAPPER, NULL));
211 
212 	wrap->wrapper = NULL;
213 
214 	wrapper->impl             = wrap;
215 	wrapper->resize.handle    = wrap;
216 	wrapper->resize.ui_resize = wrapper_resize;
217 
218 	gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(wrap));
219 	gtk_widget_set_can_focus(GTK_WIDGET(wrap), TRUE);
220 	gtk_widget_set_sensitive(GTK_WIDGET(wrap), TRUE);
221 	gtk_widget_realize(GTK_WIDGET(wrap));
222 
223 	GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(wrap));
224 
225 	wrap->flt_win = gtk_widget_get_window(parent);
226 	gdk_window_add_filter(wrap->flt_win, event_filter, wrap);
227 
228 	HWND parent_window = (HWND)GDK_WINDOW_HWND(window);
229 	suil_add_feature(features, &n_features, LV2_UI__parent, parent_window);
230 	suil_add_feature(features, &n_features, LV2_UI__resize, &wrapper->resize);
231 	suil_add_feature(features, &n_features, LV2_UI__idleInterface, NULL);
232 
233 	// Scan for URID map and options
234 	LV2_URID_Map*       map     = NULL;
235 	LV2_Options_Option* options = NULL;
236 	for (LV2_Feature** f = *features; *f && (!map || !options); ++f) {
237 		if (!strcmp((*f)->URI, LV2_OPTIONS__options)) {
238 			options = (LV2_Options_Option *)(*f)->data;
239 		} else if (!strcmp((*f)->URI, LV2_URID__map)) {
240 			map = (LV2_URID_Map *)(*f)->data;
241 		}
242 	}
243 
244 	if (map && options) {
245 		// Set UI update rate if given
246 		LV2_URID ui_updateRate = map->map(map->handle, LV2_UI__updateRate);
247 		for (LV2_Options_Option* o = options; o->key; ++o) {
248 			if (o->key == ui_updateRate) {
249 				wrap->idle_ms = 1000.0f / *(const float*)o->value;
250 				break;
251 			}
252 		}
253 	}
254 
255 	return wrapper;
256 }
257 
258 }  // extern "C"
259