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

Diff of /gigedit/trunk/src/gigedit/gigedit.cpp

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1339 by schoenebeck, Mon Sep 10 19:56:26 2007 UTC revision 3068 by schoenebeck, Mon Jan 2 22:13:01 2017 UTC
# Line 1  Line 1 
1  /*  /*
2   * Copyright (C) 2007 Andreas Persson   * Copyright (C) 2007-2017 Andreas Persson
3   *   *
4   * This program is free software; you can redistribute it and/or   * This program is free software; you can redistribute it and/or
5   * modify it under the terms of the GNU General Public License as   * modify it under the terms of the GNU General Public License as
# Line 17  Line 17 
17   * 02110-1301 USA.   * 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"  #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>  #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"  #include "mainwindow.h"
49    
50  #include <libintl.h>  #include "global.h"
 #include <config.h>  
51    
52  // the app has to work from a DLL as well, so we hard code argv  #ifdef __APPLE__
53  int argc = 1;  #include <dlfcn.h>
54  const char* argv_c[] = { "gigedit" };  #include <glibmm/fileutils.h>
55  char** argv = const_cast<char**>(argv_c);  #endif
56  //FIXME: Gtk only allows to instantiate one Gtk::Main object per process, so this might crash other Gtk applications, i.e. launched as plugins by LinuxSampler  
57  Gtk::Main kit(argc, argv);  //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  static void __init_app() {  void init_app() {
139      static bool process_initialized = false;      static bool process_initialized = false;
140      if (!process_initialized) {      if (!process_initialized) {
141          std::cout << "Initializing 3rd party services needed by gigedit.\n"          std::cout << "Initializing 3rd party services needed by gigedit.\n"
142                    << std::flush;                    << std::flush;
143          setlocale(LC_ALL, "");          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);          bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
228      #endif
229          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
230          textdomain(GETTEXT_PACKAGE);          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          // make sure thread_init() is called once and ONLY once per process
235          if (!Glib::thread_supported()) Glib::thread_init();          if (!Glib::thread_supported()) Glib::thread_init();
236    #endif
237          process_initialized = true;          process_initialized = true;
238      }      }
239  }  }
240    
241  static void __connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {  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      // the signals of the "GigEdit" class are actually just proxies, that
262      // is they simply forward the signals of the internal classes to the      // is they simply forward the signals of the internal classes to the
263      // outer world      // outer world
# Line 76  static void __connect_signals(GigEdit* g Line 285  static void __connect_signals(GigEdit* g
285      mainwindow->signal_dimreg_changed().connect(      mainwindow->signal_dimreg_changed().connect(
286          gigedit->signal_dimreg_changed().make_slot()          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(      mainwindow->signal_sample_ref_changed().connect(
292          gigedit->signal_sample_ref_changed().make_slot()          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  int GigEdit::run() {  } // namespace
312      __init_app();  
313      MainWindow window;  GigEdit::GigEdit() {
314      __connect_signals(this, &window);      state = NULL;
     kit.run(window);  
     return 0;  
315  }  }
316    
317  int GigEdit::run(const char* pFileName) {  int GigEdit::run(int argc, char* argv[]) {
318      __init_app();      init_app();
319    
320        Gtk::Main kit(argc, argv);
321        init_app_after_gtk_init();
322    
323      MainWindow window;      MainWindow window;
324      __connect_signals(this, &window);      connect_signals(this, &window);
325      if (pFileName) window.load_file(pFileName);      if (argc >= 2) window.load_file(argv[1]);
326      kit.run(window);      kit.run(window);
327      return 0;      return 0;
328  }  }
329    
330  int GigEdit::run(gig::Instrument* pInstrument) {  int GigEdit::run(gig::Instrument* pInstrument) {
331      __init_app();      init_app();
332      MainWindow window;  
333      __connect_signals(this, &window);      GigEditState state(this);
334      if (pInstrument) window.load_instrument(pInstrument);      this->state = &state;
335      kit.run(window);      state.run(pInstrument);
336        this->state = NULL;
337      return 0;      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() {  sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_to_be_changed() {
353      return file_structure_to_be_changed_signal;      return file_structure_to_be_changed_signal;
354  }  }
# Line 139  sigc::signal<void, gig::DimensionRegion* Line 381  sigc::signal<void, gig::DimensionRegion*
381      return dimreg_changed_signal;      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() {  sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/>& GigEdit::signal_sample_ref_changed() {
389      return sample_ref_changed_signal;      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

Legend:
Removed from v.1339  
changed lines
  Added in v.3068

  ViewVC Help
Powered by ViewVC