/[svn]/gigedit/trunk/src/gigedit/gigedit.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/gigedit.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3021 - (show annotations) (download)
Sun Oct 23 07:24:40 2016 UTC (21 months, 3 weeks ago) by persson
File size: 17645 byte(s)
* windows, 32-bit: fixed crashes by making sure the stack in sub
  threads is 16-byte aligned

1 /*
2 * Copyright (C) 2007-2015 Andreas Persson
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2, or (at
7 * your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with program; see the file COPYING. If not, write to the Free
16 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20 #include <glibmmconfig.h>
21 // threads.h must be included first to be able to build with
22 // G_DISABLE_DEPRECATED
23 #if (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION == 31 && GLIBMM_MICRO_VERSION >= 2) || \
24 (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION > 31) || GLIBMM_MAJOR_VERSION > 2
25 #include <glibmm/threads.h>
26 #endif
27
28 #include "gigedit.h"
29
30 #include <gtkmmconfig.h>
31 #if GTKMM_MAJOR_VERSION < 3
32 #include <gdkmm/region.h>
33 #endif
34 #include <glibmm/dispatcher.h>
35 #include <glibmm/main.h>
36 #include <gtkmm/main.h>
37
38 #if defined(__APPLE__)
39 # include <CoreFoundation/CoreFoundation.h>
40 # include "MacHelper.h"
41 #endif
42
43 #include "mainwindow.h"
44
45 #include "global.h"
46
47 #ifdef __APPLE__
48 #include <dlfcn.h>
49 #include <glibmm/fileutils.h>
50 #include <glibmm/miscutils.h>
51 #endif
52
53 //TODO: (hopefully) just a temporary nasty hack for launching gigedit on the main thread on Mac (see comments below in this file for details)
54 #if defined(__APPLE__) && HAVE_LINUXSAMPLER // the following global external variables are defined in LinuxSampler's global_private.cpp ...
55 extern bool g_mainThreadCallbackSupported;
56 extern void (*g_mainThreadCallback)(void* info);
57 extern void* g_mainThreadCallbackInfo;
58 extern bool g_fireMainThreadCallback;
59 #endif
60
61 namespace {
62
63 // State for a gigedit thread.
64 //
65 // This class is only used when gigedit is run as a plugin and makes
66 // sure that there's only one Gtk::Main event loop. The event loop is
67 // started in a separate thread. The plugin thread just dispatches an
68 // event to the main loop to open a window and then goes to sleep
69 // until the window is closed.
70 //
71 class GigEditState : public sigc::trackable {
72 public:
73 GigEditState(GigEdit* parent) :
74 window(0), parent(parent), instrument(0) { }
75 void run(gig::Instrument* pInstrument);
76
77 MainWindow* window;
78
79 private:
80
81 // simple condition variable abstraction
82 class Cond {
83 private:
84 bool pred;
85 Glib::Threads::Mutex mutex;
86 Glib::Threads::Cond cond;
87 public:
88 Cond() : pred(false) { }
89 void signal() {
90 Glib::Threads::Mutex::Lock lock(mutex);
91 pred = true;
92 cond.signal();
93 }
94 void wait() {
95 Glib::Threads::Mutex::Lock lock(mutex);
96 while (!pred) cond.wait(mutex);
97 }
98 };
99
100 #ifdef OLD_THREADS
101 static Glib::StaticMutex mutex;
102 #else
103 static Glib::Threads::Mutex mutex;
104 #endif
105 static Glib::Dispatcher* dispatcher;
106 static GigEditState* current;
107
108 static void main_loop_run(Cond* intialized);
109 static void open_window_static();
110
111 GigEdit* parent;
112 Cond open;
113 Cond close;
114 Cond initialized;
115 gig::Instrument* instrument;
116
117 void open_window();
118 void close_window();
119 #if defined(__APPLE__)
120 static void runInCFMainLoop(void* info);
121 #endif
122 };
123
124 #ifdef WIN32
125 HINSTANCE gigedit_dll_handle = 0;
126 #endif
127
128 #ifdef __APPLE__
129 std::string gigedit_localedir;
130 #endif
131
132 void init_app() {
133 static bool process_initialized = false;
134 if (!process_initialized) {
135 std::cout << "Initializing 3rd party services needed by gigedit.\n"
136 << std::flush;
137 setlocale(LC_ALL, "");
138
139 #ifdef __APPLE__
140 // Look for pango.modules, gdk-pixbuf.loaders and locale files
141 // under the same dir as the gigedit dylib is installed in.
142 Dl_info info;
143 if (dladdr((void*)&init_app, &info)) {
144 std::string libdir = Glib::path_get_dirname(info.dli_fname);
145
146 if (Glib::getenv("PANGO_SYSCONFDIR") == "" &&
147 Glib::file_test(Glib::build_filename(libdir,
148 "pango/pango.modules"),
149 Glib::FILE_TEST_EXISTS)) {
150 Glib::setenv("PANGO_SYSCONFDIR", libdir, true);
151 }
152 if (Glib::getenv("GDK_PIXBUF_MODULE_FILE") == "") {
153 std::string module_file =
154 Glib::build_filename(libdir,
155 "gtk-2.0/gdk-pixbuf.loaders");
156 if (Glib::file_test(module_file, Glib::FILE_TEST_EXISTS)) {
157 Glib::setenv("GDK_PIXBUF_MODULE_FILE", module_file, true);
158 }
159 }
160 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
161 //#if HAVE_GETTEXT
162 std::string localedir = Glib::build_filename(libdir, "locale");
163 if (Glib::file_test(localedir, Glib::FILE_TEST_EXISTS)) {
164 gigedit_localedir = localedir;
165 bindtextdomain(GETTEXT_PACKAGE, gigedit_localedir.c_str());
166 } else {
167 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
168 }
169 //#endif
170 }
171
172 // The gtk file dialog stores its recent files state in
173 // ~/.local/share
174 g_mkdir_with_parents(
175 Glib::build_filename(Glib::get_home_dir(),
176 ".local/share").c_str(), 0777);
177 #endif // __APPLE__
178
179 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
180 #if (HAVE_GETTEXT || defined(__APPLE__))
181
182 #ifdef WIN32
183 #if GLIB_CHECK_VERSION(2, 16, 0)
184 gchar* root =
185 g_win32_get_package_installation_directory_of_module(gigedit_dll_handle);
186 #else
187 gchar* root =
188 g_win32_get_package_installation_directory(NULL, NULL);
189 #endif
190 gchar* temp = g_build_filename(root, "/share/locale", NULL);
191 g_free(root);
192 gchar* localedir = g_win32_locale_filename_from_utf8(temp);
193 g_free(temp);
194 bindtextdomain(GETTEXT_PACKAGE, localedir);
195 g_free(localedir);
196 #elif !defined(__APPLE__)
197 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
198 #endif
199 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
200 textdomain(GETTEXT_PACKAGE);
201 #endif // HAVE_GETTEXT
202
203 #ifdef OLD_THREADS
204 // make sure thread_init() is called once and ONLY once per process
205 if (!Glib::thread_supported()) Glib::thread_init();
206 #endif
207 process_initialized = true;
208 }
209 }
210
211 void connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {
212 // the signals of the "GigEdit" class are actually just proxies, that
213 // is they simply forward the signals of the internal classes to the
214 // outer world
215 mainwindow->signal_file_structure_to_be_changed().connect(
216 gigedit->signal_file_structure_to_be_changed().make_slot()
217 );
218 mainwindow->signal_file_structure_changed().connect(
219 gigedit->signal_file_structure_changed().make_slot()
220 );
221 mainwindow->signal_samples_to_be_removed().connect(
222 gigedit->signal_samples_to_be_removed().make_slot()
223 );
224 mainwindow->signal_samples_removed().connect(
225 gigedit->signal_samples_removed().make_slot()
226 );
227 mainwindow->signal_region_to_be_changed().connect(
228 gigedit->signal_region_to_be_changed().make_slot()
229 );
230 mainwindow->signal_region_changed().connect(
231 gigedit->signal_region_changed().make_slot()
232 );
233 mainwindow->signal_dimreg_to_be_changed().connect(
234 gigedit->signal_dimreg_to_be_changed().make_slot()
235 );
236 mainwindow->signal_dimreg_changed().connect(
237 gigedit->signal_dimreg_changed().make_slot()
238 );
239 mainwindow->signal_sample_changed().connect(
240 gigedit->signal_sample_changed().make_slot()
241 );
242 mainwindow->signal_sample_ref_changed().connect(
243 gigedit->signal_sample_ref_changed().make_slot()
244 );
245 mainwindow->signal_keyboard_key_hit().connect(
246 gigedit->signal_keyboard_key_hit().make_slot()
247 );
248 mainwindow->signal_keyboard_key_released().connect(
249 gigedit->signal_keyboard_key_released().make_slot()
250 );
251 mainwindow->signal_switch_sampler_instrument().connect(
252 gigedit->signal_switch_sampler_instrument().make_slot()
253 );
254 mainwindow->signal_script_to_be_changed.connect(
255 gigedit->signal_script_to_be_changed.make_slot()
256 );
257 mainwindow->signal_script_changed.connect(
258 gigedit->signal_script_changed.make_slot()
259 );
260 }
261
262 } // namespace
263
264 GigEdit::GigEdit() {
265 state = NULL;
266 }
267
268 int GigEdit::run(int argc, char* argv[]) {
269 init_app();
270
271 Gtk::Main kit(argc, argv);
272
273 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
274 #if (/*HAVE_GETTEXT &&*/ defined(__APPLE__))
275 // Gtk::Main binds the gtk locale to a possible non-existent
276 // directory. If we have bundled gtk locale files, we rebind here,
277 // after the Gtk::Main constructor.
278 if (!gigedit_localedir.empty()) {
279 bindtextdomain("gtk20", gigedit_localedir.c_str());
280 }
281 #endif
282
283 MainWindow window;
284 connect_signals(this, &window);
285 if (argc >= 2) window.load_file(argv[1]);
286 kit.run(window);
287 return 0;
288 }
289
290 int GigEdit::run(gig::Instrument* pInstrument) {
291 init_app();
292
293 GigEditState state(this);
294 this->state = &state;
295 state.run(pInstrument);
296 this->state = NULL;
297 return 0;
298 }
299
300 void GigEdit::on_note_on_event(int key, int velocity) {
301 if (!this->state) return;
302 GigEditState* state = static_cast<GigEditState*>(this->state);
303 state->window->signal_note_on().emit(key, velocity);
304 }
305
306 void GigEdit::on_note_off_event(int key, int velocity) {
307 if (!this->state) return;
308 GigEditState* state = static_cast<GigEditState*>(this->state);
309 state->window->signal_note_off().emit(key, velocity);
310 }
311
312 sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_to_be_changed() {
313 return file_structure_to_be_changed_signal;
314 }
315
316 sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_changed() {
317 return file_structure_changed_signal;
318 }
319
320 sigc::signal<void, std::list<gig::Sample*> >& GigEdit::signal_samples_to_be_removed() {
321 return samples_to_be_removed_signal;
322 }
323
324 sigc::signal<void>& GigEdit::signal_samples_removed() {
325 return samples_removed_signal;
326 }
327
328 sigc::signal<void, gig::Region*>& GigEdit::signal_region_to_be_changed() {
329 return region_to_be_changed_signal;
330 }
331
332 sigc::signal<void, gig::Region*>& GigEdit::signal_region_changed() {
333 return region_changed_signal;
334 }
335
336 sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_to_be_changed() {
337 return dimreg_to_be_changed_signal;
338 }
339
340 sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_changed() {
341 return dimreg_changed_signal;
342 }
343
344 sigc::signal<void, gig::Sample*>& GigEdit::signal_sample_changed() {
345 return sample_changed_signal;
346 }
347
348 sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/>& GigEdit::signal_sample_ref_changed() {
349 return sample_ref_changed_signal;
350 }
351
352 sigc::signal<void, int/*key*/, int/*velocity*/>& GigEdit::signal_keyboard_key_hit() {
353 return keyboard_key_hit_signal;
354 }
355
356 sigc::signal<void, int/*key*/, int/*velocity*/>& GigEdit::signal_keyboard_key_released() {
357 return keyboard_key_released_signal;
358 }
359
360 sigc::signal<void, gig::Instrument*>& GigEdit::signal_switch_sampler_instrument() {
361 return switch_sampler_instrument_signal;
362 }
363
364 #ifdef OLD_THREADS
365 Glib::StaticMutex GigEditState::mutex = GLIBMM_STATIC_MUTEX_INIT;
366 #else
367 Glib::Threads::Mutex GigEditState::mutex;
368 #endif
369 Glib::Dispatcher* GigEditState::dispatcher = 0;
370 GigEditState* GigEditState::current = 0;
371
372 void GigEditState::open_window_static() {
373 GigEditState* c = GigEditState::current;
374 c->open.signal();
375 c->open_window();
376 }
377
378 void GigEditState::open_window() {
379 window = new MainWindow();
380
381 connect_signals(parent, window);
382 if (instrument) window->load_instrument(instrument);
383
384 window->signal_hide().connect(sigc::mem_fun(this,
385 &GigEditState::close_window));
386 window->present();
387 }
388
389 void GigEditState::close_window() {
390 delete window;
391 close.signal();
392 }
393
394 #if defined(WIN32) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
395 // make sure stack is 16-byte aligned for SSE instructions
396 __attribute__((force_align_arg_pointer))
397 #endif
398 void GigEditState::main_loop_run(Cond* initialized) {
399 int argc = 1;
400 const char* argv_c[] = { "gigedit" };
401 char** argv = const_cast<char**>(argv_c);
402 Gtk::Main main_loop(argc, argv);
403 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
404 #if (/*HAVE_GETTEXT &&*/ defined(__APPLE__))
405 if (!gigedit_localedir.empty()) {
406 bindtextdomain("gtk20", gigedit_localedir.c_str());
407 }
408 #endif
409
410 dispatcher = new Glib::Dispatcher();
411 dispatcher->connect(sigc::ptr_fun(&GigEditState::open_window_static));
412 initialized->signal();
413
414 main_loop.run();
415 }
416
417 #if defined(__APPLE__)
418
419 void GigEditState::runInCFMainLoop(void* info) {
420 printf("runInCFMainLoop() entered\n"); fflush(stdout);
421 GigEditState* state = static_cast<GigEditState*>(info);
422 state->main_loop_run(
423 &state->initialized
424 );
425 printf("runInCFMainLoop() left\n"); fflush(stdout);
426 }
427
428 #endif // __APPLE__
429
430 void GigEditState::run(gig::Instrument* pInstrument) {
431 mutex.lock(); // lock access to static variables
432
433 static bool main_loop_started = false;
434 instrument = pInstrument;
435 if (!main_loop_started) {
436 #if defined(__APPLE__) && HAVE_LINUXSAMPLER
437 // spawn GUI on main thread :
438 // On OS X the Gtk GUI can only be launched on a process's "main"
439 // thread. When trying to launch the Gtk GUI on any other thread,
440 // there will only be a white box, because the GUI would not receive
441 // any events, since it would listen to the wrong system event loop.
442 // So far we haven't investigated whether there is any kind of
443 // circumvention to allow doing that also on other OS X threads.
444 {
445 // In case the sampler was launched as standalone sampler (not as
446 // plugin), use the following global callback variable hack ...
447 if (g_mainThreadCallbackSupported) {
448 printf("Setting callback ...\n"); fflush(stdout);
449 g_mainThreadCallback = runInCFMainLoop;
450 g_mainThreadCallbackInfo = this;
451 g_fireMainThreadCallback = true;
452 printf("Callback variables set.\n"); fflush(stdout);
453 } else { // Sampler was launched as (i.e. AU / VST) plugin ...
454 // When the sampler was launched as plugin, we have no idea
455 // whether any sampler thread is the process's "main" thread.
456 // So that's why we are trying to use Apple's API for trying to
457 // launch our callback function on the process's main thread.
458 // However this will only work, if the plugin host application
459 // established a CF event loop, that is if the application is
460 // using Cocoa for its GUI. For other host applications the
461 // callback will never be executed and thus gigedit would not
462 // popup.
463
464 // should be pretty much the same as the Objective-C solution below with macHelperRunCFuncOnMainThread()
465 /*CFRunLoopSourceContext sourceContext = CFRunLoopSourceContext();
466 sourceContext.info = this;
467 sourceContext.perform = runInCFMainLoop;
468 printf("CFRunLoopSourceCreate\n"); fflush(stdout);
469 CFRunLoopSourceRef source = CFRunLoopSourceCreate(
470 kCFAllocatorDefault, // allocator
471 1, // priority
472 &sourceContext
473 );
474 printf("CFRunLoopAddSource\n"); fflush(stdout);
475 CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
476 CFRelease(source);*/
477
478 // use Apple's Objective-C API to call our callback function
479 // 'runInCFMainLoop()' on the process's "main" thread
480 macHelperRunCFuncOnMainThread(runInCFMainLoop, this);
481 }
482 }
483 #else
484 #ifdef OLD_THREADS
485 Glib::Thread::create(
486 sigc::bind(sigc::ptr_fun(&GigEditState::main_loop_run),
487 &initialized),
488 false);
489 #else
490 Glib::Threads::Thread::create(
491 sigc::bind(sigc::ptr_fun(&GigEditState::main_loop_run),
492 &initialized));
493 #endif
494 #endif
495 printf("Waiting for GUI being initialized (on main thread) ....\n"); fflush(stdout);
496 initialized.wait();
497 printf("GUI is now initialized. Everything done.\n"); fflush(stdout);
498 main_loop_started = true;
499 }
500 current = this;
501 dispatcher->emit();
502 open.wait(); // wait until the GUI thread has read current
503 mutex.unlock();
504 close.wait(); // sleep until window is closed
505 }
506
507 #if defined(WIN32)
508 extern "C" {
509 BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
510 {
511 switch (reason) {
512 case DLL_PROCESS_ATTACH:
513 gigedit_dll_handle = instance;
514 break;
515 }
516 return TRUE;
517 }
518 }
519 #endif

  ViewVC Help
Powered by ViewVC