/[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 3472 - (show annotations) (download)
Sat Feb 16 19:56:56 2019 UTC (5 years, 2 months ago) by persson
File size: 20119 byte(s)
* Use std::thread when building with newer glibmm, as Glib::Thread is
  deprecated

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

  ViewVC Help
Powered by ViewVC