fix VST GUI swallowing for windows
[ardour.git] / libs / fst / vstwin.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <windows.h>
4
5 #define fst_error(...) fprintf(stderr, __VA_ARGS__)
6
7 #ifdef PLATFORM_WINDOWS
8
9 #include <pthread.h>
10 static UINT_PTR idle_timer_id   = 0;
11
12 #else /* linux + wine */
13
14 #include <linux/limits.h> // PATH_MAX
15 #include <winnt.h>
16 #include <wine/exception.h>
17 #include <pthread.h>
18 static int gui_quit = 0;
19 static unsigned int idle_id = 0;
20
21 #endif
22
23 extern char * strdup (const char *);
24 #include <glib.h>
25 #include "fst.h"
26
27 struct ERect {
28         short top;
29         short left;
30         short bottom;
31         short right;
32 };
33
34 static pthread_mutex_t  plugin_mutex;
35 static VSTState*        fst_first        = NULL; /**< Head of linked list of all FSTs */
36 static int              host_initialized = 0;
37 static const char       magic[]          =  "FST Plugin State v002";
38
39
40 static LRESULT WINAPI
41 vstedit_wndproc (HWND w, UINT msg, WPARAM wp, LPARAM lp)
42 {
43         switch (msg) {
44                 case WM_KEYUP:
45                 case WM_KEYDOWN:
46                         break;
47
48                 case WM_CLOSE:
49                         /* we don't care about windows closing ...
50                          * WM_CLOSE is used for minimizing the window.
51                          * Our window has no frame so it shouldn't ever
52                          * get sent - but if it does, we don't want our
53                          * window to get minimized!
54                          */
55                         return 0;
56                         break;
57
58                 case WM_DESTROY:
59                 case WM_NCDESTROY:
60                         /* we don't care about windows being destroyed ... */
61                         return 0;
62                         break;
63
64                 default:
65                         break;
66         }
67
68         return DefWindowProcA (w, msg, wp, lp );
69 }
70
71
72 static void
73 maybe_set_program (VSTState* fst)
74 {
75         if (fst->want_program != -1) {
76                 if (fst->vst_version >= 2) {
77                         fst->plugin->dispatcher (fst->plugin, effBeginSetProgram, 0, 0, NULL, 0);
78                 }
79
80                 fst->plugin->dispatcher (fst->plugin, effSetProgram, 0, fst->want_program, NULL, 0);
81
82                 if (fst->vst_version >= 2) {
83                         fst->plugin->dispatcher (fst->plugin, effEndSetProgram, 0, 0, NULL, 0);
84                 }
85                 fst->want_program = -1;
86         }
87
88         if (fst->want_chunk == 1) {
89                 // XXX check
90                 // 24 == audioMasterGetAutomationState,
91                 // 48 == audioMasterGetChunkFile
92                 fst->plugin->dispatcher (fst->plugin, 24 /* effSetChunk */, 1, fst->wanted_chunk_size, fst->wanted_chunk, 0);
93                 fst->want_chunk = 0;
94         }
95 }
96
97 static VOID CALLBACK
98 idle_hands(
99                 HWND hwnd,        // handle to window for timer messages
100                 UINT message,     // WM_TIMER message
101                 UINT idTimer,     // timer identifier
102                 DWORD dwTime)     // current system time
103 {
104         VSTState* fst;
105
106         pthread_mutex_lock (&plugin_mutex);
107
108         for (fst = fst_first; fst; fst = fst->next) {
109                 if (fst->gui_shown) {
110                         // this seems insane, but some plugins will not draw their meters if you don't
111                         // call this every time.  Example Ambience by Magnus @ Smartelectron:x
112                         fst->plugin->dispatcher (fst->plugin, effEditIdle, 0, 0, NULL, 0);
113
114                         if (fst->wantIdle) {
115                                 fst->wantIdle = fst->plugin->dispatcher (fst->plugin, effIdle, 0, 0, NULL, 0);
116                         }
117                 }
118
119                 pthread_mutex_lock (&fst->lock);
120 #ifndef PLATFORM_WINDOWS /* linux + wine */
121                 /* Dispatch messages to send keypresses to the plugin */
122                 int i;
123
124                 for (i = 0; i < fst->n_pending_keys; ++i) {
125                         MSG msg;
126                         /* I'm not quite sure what is going on here; it seems
127                          * `special' keys must be delivered with WM_KEYDOWN,
128                          * but that alphanumerics etc. must use WM_CHAR or
129                          * they will be ignored.  Ours is not to reason why ...
130                          */
131                         if (fst->pending_keys[i].special != 0) {
132                                 msg.message = WM_KEYDOWN;
133                                 msg.wParam = fst->pending_keys[i].special;
134                         } else {
135                                 msg.message = WM_CHAR;
136                                 msg.wParam = fst->pending_keys[i].character;
137                         }
138                         msg.hwnd = GetFocus ();
139                         msg.lParam = 0;
140                         DispatchMessageA (&msg);
141                 }
142
143                 fst->n_pending_keys = 0;
144 #endif
145
146                 /* See comment for maybe_set_program call below */
147                 maybe_set_program (fst);
148                 fst->want_program = -1;
149                 fst->want_chunk = 0;
150                 /* If we don't have an editor window yet, we still need to
151                  * set up the program, otherwise when we load a plugin without
152                  * opening its window it will sound wrong.  However, it seems
153                  * that if you don't also load the program after opening the GUI,
154                  * the GUI does not reflect the program properly.  So we'll not
155                  * mark that we've done this (ie we won't set want_program to -1)
156                  * and so it will be done again if and when the GUI arrives.
157                  */
158                 if (fst->program_set_without_editor == 0) {
159                         maybe_set_program (fst);
160                         fst->program_set_without_editor = 1;
161                 }
162
163                 pthread_mutex_unlock (&fst->lock);
164         }
165
166         pthread_mutex_unlock (&plugin_mutex);
167 }
168
169 static void
170 fst_idle_timer_add_plugin (VSTState* fst)
171 {
172         pthread_mutex_lock (&plugin_mutex);
173
174         if (fst_first == NULL) {
175                 fst_first = fst;
176         } else {
177                 VSTState* p = fst_first;
178                 while (p->next) {
179                         p = p->next;
180                 }
181                 p->next = fst;
182         }
183
184         pthread_mutex_unlock (&plugin_mutex);
185 }
186
187 static void
188 fst_idle_timer_remove_plugin (VSTState* fst)
189 {
190         VSTState* p;
191         VSTState* prev;
192
193         pthread_mutex_lock (&plugin_mutex);
194
195         for (p = fst_first, prev = NULL; p; prev = p, p = p->next) {
196                 if (p == fst) {
197                         if (prev) {
198                                 prev->next = p->next;
199                         }
200                         break;
201                 }
202                 if (!p->next) {
203                         break;
204                 }
205         }
206
207         if (fst_first == fst) {
208                 fst_first = fst_first->next;
209         }
210
211         pthread_mutex_unlock (&plugin_mutex);
212 }
213
214 static VSTState*
215 fst_new (void)
216 {
217         VSTState* fst = (VSTState*) calloc (1, sizeof (VSTState));
218         pthread_mutex_init (&fst->lock, NULL);
219         pthread_cond_init (&fst->window_status_change, NULL); // unused ?? -> TODO check gtk2ardour
220         pthread_cond_init (&fst->plugin_dispatcher_called, NULL); // unused ??
221         fst->want_program = -1;
222         fst->want_chunk = 0;
223         fst->n_pending_keys = 0;
224         fst->has_editor = 0;
225 #ifdef PLATFORM_WINDOWS
226         fst->voffset = 50;
227         fst->hoffset = 0;
228 #else /* linux + wine */
229         fst->voffset = 24;
230         fst->hoffset = 6;
231 #endif
232         fst->program_set_without_editor = 0;
233         return fst;
234 }
235
236 static void
237 fst_delete (VSTState* fst)
238 {
239         if (fst) {
240                 free((void*)fst);
241                 fst = NULL;
242         }
243 }
244
245 static VSTHandle*
246 fst_handle_new (void)
247 {
248         VSTHandle* fst = (VSTHandle*) calloc (1, sizeof (VSTHandle));
249         return fst;
250 }
251
252 #ifndef PLATFORM_WINDOWS /* linux + wine */
253 static gboolean
254 g_idle_call (gpointer ignored) {
255         if (gui_quit) return FALSE;
256         MSG msg;
257         if (PeekMessageA (&msg, NULL, 0, 0, 1)) {
258                 TranslateMessage (&msg);
259                 DispatchMessageA (&msg);
260         }
261         idle_hands(NULL, 0, 0, 0);
262         g_main_context_iteration(NULL, FALSE);
263         return gui_quit ? FALSE : TRUE;
264 }
265 #endif
266
267
268 int
269 fst_init (void* possible_hmodule)
270 {
271         if (host_initialized) return 0;
272         HMODULE hInst;
273
274         if (possible_hmodule) {
275 #ifdef PLATFORM_WINDOWS
276                 fst_error ("Error in fst_init(): (module handle is unnecessary for Win32 build)");
277                 return -1;
278 #else /* linux + wine */
279                 hInst = (HMODULE) possible_hmodule;
280 #endif
281         } else if ((hInst = GetModuleHandleA (NULL)) == NULL) {
282                 fst_error ("can't get module handle");
283                 return -1;
284         }
285
286         if (!hInst) {
287                 fst_error ("Cannot initialise VST host");
288                 return -1;
289         }
290
291         WNDCLASSEX wclass;
292
293         wclass.cbSize = sizeof(WNDCLASSEX);
294 #ifdef PLATFORM_WINDOWS
295         wclass.style = (CS_HREDRAW | CS_VREDRAW);
296         wclass.hIcon = NULL;
297         wclass.hCursor = LoadCursor(0, IDC_ARROW);
298 #else /* linux + wine */
299         wclass.style = 0;
300         wclass.hIcon = LoadIcon(hInst, "FST");
301         wclass.hCursor = LoadCursor(0, IDI_APPLICATION);
302 #endif
303         wclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
304         wclass.lpfnWndProc = vstedit_wndproc;
305         wclass.cbClsExtra = 0;
306         wclass.cbWndExtra = 0;
307         wclass.hInstance = hInst;
308         wclass.lpszMenuName = "MENU_FST";
309         wclass.lpszClassName = "FST";
310         wclass.hIconSm = 0;
311
312         pthread_mutex_init (&plugin_mutex, NULL);
313         host_initialized = -1;
314
315         if (!RegisterClassExA(&wclass)){
316                 fst_error ("Error in fst_init(): (class registration failed");
317                 return -1;
318         }
319         return 0;
320 }
321
322 void
323 fst_start_threading(void)
324 {
325 #ifndef PLATFORM_WINDOWS /* linux + wine */
326         if (idle_id == 0) {
327                 gui_quit = 0;
328                 idle_id = g_idle_add (g_idle_call, NULL);
329         }
330 #endif
331 }
332
333 void
334 fst_stop_threading(void) {
335 #ifndef PLATFORM_WINDOWS /* linux + wine */
336         if (idle_id != 0) {
337                 gui_quit = 1;
338                 PostQuitMessage (0);
339                 g_main_context_iteration(NULL, FALSE);
340                 //g_source_remove(idle_id);
341                 idle_id = 0;
342         }
343 #endif
344 }
345
346 void
347 fst_exit (void)
348 {
349         if (!host_initialized) return;
350         VSTState* fst;
351         // If any plugins are still open at this point, close them!
352         while ((fst = fst_first))
353                 fst_close (fst);
354
355 #ifdef PLATFORM_WINDOWS
356         if (idle_timer_id != 0) {
357                 KillTimer(NULL, idle_timer_id);
358         }
359 #else /* linux + wine */
360         if (idle_id) {
361                 gui_quit = 1;
362                 PostQuitMessage (0);
363         }
364 #endif
365
366         host_initialized = FALSE;
367         pthread_mutex_destroy (&plugin_mutex);
368 }
369
370
371 int
372 fst_run_editor (VSTState* fst, void* window_parent)
373 {
374         if (fst->windows_window == NULL) {
375                 HMODULE hInst;
376                 HWND window;
377                 struct ERect* er;
378
379                 if (!(fst->plugin->flags & effFlagsHasEditor)) {
380                         fst_error ("Plugin \"%s\" has no editor", fst->handle->name);
381                         return -1;
382                 }
383
384                 if ((hInst = GetModuleHandleA (NULL)) == NULL) {
385                         fst_error ("fst_create_editor() can't get module handle");
386                         return 1;
387                 }
388
389                 if ((window = CreateWindowExA (0, "FST", fst->handle->name,
390                                                 window_parent ? WS_CHILD : (WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX),
391                                                 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
392                                                 (HWND)window_parent, NULL,
393                                                 hInst,
394                                                 NULL) ) == NULL) {
395                         fst_error ("fst_create_editor() cannot create editor window");
396                         return 1;
397                 }
398
399                 if (!SetPropA (window, "fst_ptr", fst)) {
400                         fst_error ("fst_create_editor() cannot set fst_ptr on window");
401                 }
402
403                 fst->windows_window = window;
404
405                 if (window_parent) {
406                         // This is requiredv for some reason. Note the parent is set above when the window
407                         // is created. Without this extra call the actual plugin window will draw outside
408                         // of our plugin window.
409                         SetParent((HWND)fst->windows_window, (HWND)window_parent);
410                         fst->xid = 0;
411 #ifndef PLATFORM_WINDOWS /* linux + wine */
412                 } else {
413                         SetWindowPos (fst->windows_window, 0, 9999, 9999, 2, 2, 0);
414                         ShowWindow (fst->windows_window, SW_SHOWNA);
415                         fst->xid = (int) GetPropA (fst->windows_window, "__wine_x11_whole_window");
416 #endif
417                 }
418
419                 // This is the suggested order of calls.
420                 fst->plugin->dispatcher (fst->plugin, effEditGetRect, 0, 0, &er, 0 );
421                 fst->plugin->dispatcher (fst->plugin, effEditOpen, 0, 0, fst->windows_window, 0 );
422                 fst->plugin->dispatcher (fst->plugin, effEditGetRect, 0, 0, &er, 0 );
423
424                 fst->width =  er->right-er->left;
425                 fst->height =  er->bottom-er->top;
426
427
428                 fst->been_activated = TRUE;
429
430         }
431
432         if (fst->windows_window) {
433 #ifdef PLATFORM_WINDOWS
434                 if (idle_timer_id == 0) {
435                         // Init the idle timer if needed, so that the main window calls us.
436                         idle_timer_id = SetTimer(NULL, idle_timer_id, 50, (TIMERPROC) idle_hands);
437                 }
438 #endif
439
440                 fst_idle_timer_add_plugin (fst);
441         }
442
443         return fst->windows_window == NULL ? -1 : 0;
444 }
445
446 void
447 fst_destroy_editor (VSTState* fst)
448 {
449         if (fst->windows_window) {
450                 fprintf (stderr, "%s destroying edit window\n", fst->handle->name);
451
452                 fst_idle_timer_remove_plugin (fst);
453                 fst->plugin->dispatcher( fst->plugin, effEditClose, 0, 0, NULL, 0.0 );
454
455                 DestroyWindow ((HWND)(fst->windows_window));
456
457                 fst->windows_window = NULL;
458         }
459
460         fst->been_activated = FALSE;
461 }
462
463 void
464 fst_move_window_into_view (VSTState* fst)
465 {
466         if (fst->windows_window) {
467 #ifdef PLATFORM_WINDOWS
468                 SetWindowPos ((HWND)(fst->windows_window), 0, fst->hoffset, fst->voffset, fst->width + fst->hoffset, fst->height + fst->voffset, 0);
469 #else /* linux + wine */
470                 SetWindowPos ((HWND)(fst->windows_window), 0, 0, 0, fst->width + fst->hoffset, fst->height + fst->voffset, 0);
471 #endif
472                 ShowWindow ((HWND)(fst->windows_window), SW_SHOWNA);
473         }
474 }
475
476 static HMODULE
477 fst_load_vst_library(const char * path)
478 {
479         char legalized_path[PATH_MAX];
480         strcpy (legalized_path, g_locale_from_utf8(path, -1, NULL, NULL, NULL));
481         return ( LoadLibraryA (legalized_path) );
482 }
483
484 VSTHandle *
485 fst_load (const char *path)
486 {
487         VSTHandle* fhandle = NULL;
488
489         if ((strlen(path)) && (NULL != (fhandle = fst_handle_new ())))
490         {
491                 char* period;
492                 fhandle->path = strdup (path);
493                 fhandle->name = g_path_get_basename(path);
494                 if ((period = strrchr (fhandle->name, '.'))) {
495                         *period = '\0';
496                 }
497
498                 // See if we can load the plugin DLL
499                 if ((fhandle->dll = (HMODULE)fst_load_vst_library (path)) == NULL) {
500                         fst_unload (&fhandle);
501                         return NULL;
502                 }
503
504                 fhandle->main_entry = (main_entry_t) GetProcAddress ((HMODULE)fhandle->dll, "main");
505
506                 if (fhandle->main_entry == 0) {
507                         if ((fhandle->main_entry = (main_entry_t) GetProcAddress ((HMODULE)fhandle->dll, "VSTPluginMain"))) {
508                                 fprintf(stderr, "VST >= 2.4 plugin '%s'\n", path);
509                                 //PBD::warning << path << _(": is a VST >= 2.4 - this plugin may or may not function correctly with this version of Ardour.") << endmsg;
510                         }
511                 }
512
513                 if (fhandle->main_entry == 0) {
514                         fst_unload (&fhandle);
515                         return NULL;
516                 }
517         }
518         return fhandle;
519 }
520
521 int
522 fst_unload (VSTHandle** fhandle)
523 {
524         if (!(*fhandle)) {
525                 return -1;
526         }
527
528         if ((*fhandle)->plugincnt) {
529                 return -1;
530         }
531
532         if ((*fhandle)->dll) {
533                 FreeLibrary ((HMODULE)(*fhandle)->dll);
534                 (*fhandle)->dll = NULL;
535         }
536
537         if ((*fhandle)->path) {
538                 free ((*fhandle)->path);
539                 (*fhandle)->path = NULL;
540         }
541
542         if ((*fhandle)->name) {
543                 free ((*fhandle)->name);
544                 (*fhandle)->name = NULL;
545         }
546
547         free (*fhandle);
548         *fhandle = NULL;
549
550         return 0;
551 }
552
553 VSTState*
554 fst_instantiate (VSTHandle* fhandle, audioMasterCallback amc, void* userptr)
555 {
556         VSTState* fst = NULL;
557
558         if( fhandle == NULL ) {
559                 fst_error( "fst_instantiate(): (the handle was NULL)\n" );
560                 return NULL;
561         }
562
563         fst = fst_new ();
564
565         if ((fst->plugin = fhandle->main_entry (amc)) == NULL)  {
566                 fst_error ("fst_instantiate: %s could not be instantiated\n", fhandle->name);
567                 free (fst);
568                 return NULL;
569         }
570
571         fst->handle = fhandle;
572         fst->plugin->user = userptr;
573
574         if (fst->plugin->magic != kEffectMagic) {
575                 fst_error ("fst_instantiate: %s is not a vst plugin\n", fhandle->name);
576                 fst_close(fst);
577                 return NULL;
578         }
579
580         fst->plugin->dispatcher (fst->plugin, effOpen, 0, 0, 0, 0);
581         fst->vst_version = fst->plugin->dispatcher (fst->plugin, effGetVstVersion, 0, 0, 0, 0);
582
583         fst->handle->plugincnt++;
584         fst->wantIdle = 0;
585
586         return fst;
587 }
588
589 void fst_audio_master_idle(void) {
590         while(g_main_context_iteration(NULL, FALSE)) ;
591 }
592
593 void
594 fst_close (VSTState* fst)
595 {
596         if (fst != NULL) {
597                 fst_destroy_editor (fst);
598
599                 if (fst->plugin) {
600                         fst->plugin->dispatcher (fst->plugin, effMainsChanged, 0, 0, NULL, 0);
601                         fst->plugin->dispatcher (fst->plugin, effClose, 0, 0, 0, 0);
602                         fst->plugin = NULL;
603                 }
604
605                 if (fst->handle) {
606                         if (fst->handle->plugincnt && --fst->handle->plugincnt == 0) {
607
608                                 fst->handle->main_entry = NULL;
609                                 fst_unload (&fst->handle); // XXX
610                         }
611                 }
612
613                 /* It might be good for this to be in it's own cleanup function
614                         since it will free the memory for the fst leaving the caller
615                         with an invalid pointer.  Caller beware */
616                 fst_delete(fst);
617         }
618 }
619
620 #if 0 // ?? who needs this, where?
621 float htonf (float v)
622 {
623         float result;
624         char * fin = (char*)&v;
625         char * fout = (char*)&result;
626         fout[0] = fin[3];
627         fout[1] = fin[2];
628         fout[2] = fin[1];
629         fout[3] = fin[0];
630         return result;
631 }
632 #endif