/[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 3068 - (show annotations) (download)
Mon Jan 2 22:13:01 2017 UTC (7 years, 3 months ago) by schoenebeck
File size: 18838 byte(s)
- Preparations for Xcode project update.

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

  ViewVC Help
Powered by ViewVC