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