/[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 1396 by schoenebeck, Wed Oct 10 15:48:54 2007 UTC revision 3472 by persson, Sat Feb 16 19:56:56 2019 UTC
# Line 1  Line 1 
1  /*  /*
2   * Copyright (C) 2007 Andreas Persson   * Copyright (C) 2007-2019 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 "compat.h"
21  #include "gigedit.h"  #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>  #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"  #include "mainwindow.h"
50    
51  #include "global.h"  #include "global.h"
52    
53  // the app has to work from a DLL as well, so we hard code argv  #ifdef __APPLE__
54  int argc = 1;  #include <dlfcn.h>
55  const char* argv_c[] = { "gigedit" };  #include <glibmm/fileutils.h>
56  char** argv = const_cast<char**>(argv_c);  #endif
57  //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  
58  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)
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  static void __init_app() {  void init_app() {
158      static bool process_initialized = false;      static bool process_initialized = false;
159      if (!process_initialized) {      if (!process_initialized) {
160          std::cout << "Initializing 3rd party services needed by gigedit.\n"          std::cout << "Initializing 3rd party services needed by gigedit.\n"
161                    << std::flush;                    << std::flush;
162          setlocale(LC_ALL, "");          setlocale(LC_ALL, "");
163    
164  #if HAVE_GETTEXT  #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);          bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
247      #endif
248          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
249          textdomain(GETTEXT_PACKAGE);          textdomain(GETTEXT_PACKAGE);
250  #endif // HAVE_GETTEXT  #endif // HAVE_GETTEXT
251    
252    #ifdef OLD_THREADS
253          // make sure thread_init() is called once and ONLY once per process          // make sure thread_init() is called once and ONLY once per process
254          if (!Glib::thread_supported()) Glib::thread_init();          if (!Glib::thread_supported()) Glib::thread_init();
255    #endif
256          process_initialized = true;          process_initialized = true;
257      }      }
258  }  }
259    
260  static void __connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {  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      // the signals of the "GigEdit" class are actually just proxies, that
281      // is they simply forward the signals of the internal classes to the      // is they simply forward the signals of the internal classes to the
282      // outer world      // outer world
# Line 79  static void __connect_signals(GigEdit* g Line 304  static void __connect_signals(GigEdit* g
304      mainwindow->signal_dimreg_changed().connect(      mainwindow->signal_dimreg_changed().connect(
305          gigedit->signal_dimreg_changed().make_slot()          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(      mainwindow->signal_sample_ref_changed().connect(
311          gigedit->signal_sample_ref_changed().make_slot()          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  int GigEdit::run() {  } // namespace
331      __init_app();  
332      MainWindow window;  GigEdit::GigEdit() {
333      __connect_signals(this, &window);      state = NULL;
     kit.run(window);  
     return 0;  
334  }  }
335    
336  int GigEdit::run(const char* pFileName) {  int GigEdit::run(int argc, char* argv[]) {
337      __init_app();      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;      MainWindow window;
348      __connect_signals(this, &window);      connect_signals(this, &window);
349      if (pFileName) window.load_file(pFileName);      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);      kit.run(window);
352    #else
353        app->run(window, argc, argv);
354    #endif
355        
356      return 0;      return 0;
357  }  }
358    
359  int GigEdit::run(gig::Instrument* pInstrument) {  int GigEdit::run(gig::Instrument* pInstrument) {
360      __init_app();      init_app();
361      MainWindow window;  
362      __connect_signals(this, &window);      GigEditState state(this);
363      if (pInstrument) window.load_instrument(pInstrument);      this->state = &state;
364      kit.run(window);      state.run(pInstrument);
365        this->state = NULL;
366      return 0;      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() {  sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_to_be_changed() {
382      return file_structure_to_be_changed_signal;      return file_structure_to_be_changed_signal;
383  }  }
# Line 142  sigc::signal<void, gig::DimensionRegion* Line 410  sigc::signal<void, gig::DimensionRegion*
410      return dimreg_changed_signal;      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() {  sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/>& GigEdit::signal_sample_ref_changed() {
418      return sample_ref_changed_signal;      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

Legend:
Removed from v.1396  
changed lines
  Added in v.3472

  ViewVC Help
Powered by ViewVC