Guitarix
gx_main.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  * Copyright (C) 2011 Pete Shorthose
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * ---------------------------------------------------------------------------
19  * ----------------------------------------------------------------------------
20  *
21  * This is gx_head main.
22  *
23  * ----------------------------------------------------------------------------
24  */
25 
26 #include "guitarix.h" // NOLINT
27 
28 #include <gtkmm/main.h> // NOLINT
29 #include <gxwmm/init.h> // NOLINT
30 #include <string> // NOLINT
31 #include "jsonrpc.h"
32 
33 #ifdef HAVE_AVAHI
34 #include "avahi_discover.h"
35 #endif
36 
37 
38 /****************************************************************
39  ** class PosixSignals
40  **
41  ** Block unix signals and catch them in a special thread.
42  ** Blocking is inherited by all threads created after an
43  ** instance of PosixSignals
44  */
45 
46 class PosixSignals {
47 private:
48  sigset_t waitset;
49  Glib::Thread *thread;
50  pthread_t pthr;
51  bool gui;
52  volatile bool exit;
53  void signal_helper_thread();
54  void quit_slot();
55  void gx_ladi_handler();
56  void create_thread();
57  bool gtk_level();
58  static void relay_sigchld(int);
59 public:
60  PosixSignals(bool gui);
61  ~PosixSignals();
62 };
63 
65  : waitset(),
66  thread(),
67  pthr(),
68  gui(gui_),
69  exit(false) {
71  sigemptyset(&waitset);
72  /* ----- block signal USR1 ---------
73  ** inherited by all threads which are created later
74  ** signals are processed synchronously by signal_helper_thread
75  */
76  sigaddset(&waitset, SIGUSR1);
77  sigaddset(&waitset, SIGCHLD);
78  sigaddset(&waitset, SIGINT);
79  sigaddset(&waitset, SIGQUIT);
80  sigaddset(&waitset, SIGTERM);
81  sigaddset(&waitset, SIGHUP);
82  sigaddset(&waitset, SIGKILL);
83 
84  // ----- leave alone these signals: generated by programming errors
85  // SIGABRT
86  // SIGSEGV
87 
88  sigprocmask(SIG_BLOCK, &waitset, NULL);
89  create_thread();
90  signal(SIGCHLD, relay_sigchld);
91 }
92 
94  if (thread) {
95  exit = true;
96  pthread_kill(pthr, SIGINT);
97  thread->join();
98  }
99  sigprocmask(SIG_UNBLOCK, &waitset, NULL);
100 }
101 
102 void PosixSignals::create_thread() {
103  try {
104  thread = Glib::Thread::create(
105  sigc::mem_fun(*this, &PosixSignals::signal_helper_thread), true);
106  } catch (Glib::ThreadError& e) {
107  throw GxFatalError(
108  boost::format(_("Thread create failed (signal): %1%")) % e.what());
109  }
110 }
111 
112 void PosixSignals::quit_slot() {
114 }
115 
116 void PosixSignals::gx_ladi_handler() {
118  _("signal_handler"), _("signal USR1 received, save settings"));
119  if (gx_preset::GxSettings::instance) {
120  bool cur_state = gx_preset::GxSettings::instance->get_auto_save_state();
121  gx_preset::GxSettings::instance->disable_autosave(false);
122  gx_preset::GxSettings::instance->auto_save_state();
123  gx_preset::GxSettings::instance->disable_autosave(cur_state);
124  }
125 }
126 
127 void PosixSignals::relay_sigchld(int) {
128  kill(getpid(), SIGCHLD);
129 }
130 
131 bool PosixSignals::gtk_level() {
132  if (! gui) {
133  return 1;
134  } else {
135  return Gtk::Main::level();
136  }
137 }
138 
139 // --- wait for USR1 signal to arrive and invoke ladi handler via mainloop
140 void PosixSignals::signal_helper_thread() {
141  pthr = pthread_self();
142  const char *signame;
143  guint source_id_usr1 = 0;
144  pthread_sigmask(SIG_BLOCK, &waitset, NULL);
145  bool seen = false;
146  while (true) {
147  int sig;
148  int ret = sigwait(&waitset, &sig);
149  if (exit) {
150  break;
151  }
152  if (ret != 0) {
153  assert(errno == EINTR);
154  continue;
155  }
156  switch (sig) {
157  case SIGUSR1:
158  if (gtk_level() < 1) {
159  gx_print_info(_("system startup"),
160  _("signal usr1 skipped"));
161  break;
162  }
163  // do not add a new call if another one is already pending
164  if (source_id_usr1 == 0 ||
165  g_main_context_find_source_by_id(NULL, source_id_usr1) == NULL) {
166  const Glib::RefPtr<Glib::IdleSource> idle_source = Glib::IdleSource::create();
167  idle_source->connect(
168  sigc::bind_return<bool>(
169  sigc::mem_fun(*this, &PosixSignals::gx_ladi_handler),false));
170  idle_source->attach();
171  source_id_usr1 = idle_source->get_id();
172  }
173  break;
174  case SIGCHLD:
175  Glib::signal_idle().connect_once(
176  sigc::ptr_fun(gx_child_process::gx_sigchld_handler));
177  break;
178  case SIGINT:
179  case SIGQUIT:
180  case SIGTERM:
181  case SIGHUP:
182  switch (sig) {
183  case SIGINT:
184  signame = _("ctrl-c");
185  break;
186  case SIGQUIT:
187  signame = "SIGQUIT";
188  break;
189  case SIGTERM:
190  signame = "SIGTERM";
191  break;
192  case SIGHUP:
193  signame = "SIGHUP";
194  break;
195  }
196  if (!seen && gtk_level() == 1) {
197  printf("\nquit (%s)\n", signame);
198  Glib::signal_idle().connect_once(sigc::mem_fun(*this, &PosixSignals::quit_slot));
199  } else {
201  (boost::format("\nQUIT (%1%)\n") % signame).str());
202  }
203  seen = true;
204  break;
205  default:
206  assert(false);
207  }
208  }
209 }
210 
211 
212 /****************************************************************
213  ** class ErrorPopup
214  ** show UI popup for kError messages
215  */
216 
217 class ErrorPopup {
218 private:
219  Glib::ustring msg;
220  bool active;
221  Gtk::MessageDialog *dialog;
222  void show_msg();
223  void on_response(int);
224 public:
225  ErrorPopup();
226  ~ErrorPopup();
227  void on_message(const Glib::ustring& msg, GxLogger::MsgType tp, bool plugged);
228 };
229 
231  : msg(),
232  active(false),
233  dialog(0) {
234 }
235 
237  delete dialog;
238 }
239 
240 void ErrorPopup::on_message(const Glib::ustring& msg_, GxLogger::MsgType tp, bool plugged) {
241  if (plugged) {
242  return;
243  }
244  if (tp == GxLogger::kError) {
245  if (active) {
246  msg += "\n" + msg_;
247  if (msg.size() > 1000) {
248  msg.substr(msg.size()-1000);
249  }
250  if (dialog) {
251  dialog->set_message(msg);
252  }
253  } else {
254  msg = msg_;
255  active = true;
256  show_msg();
257  }
258  }
259 }
260 
261 void ErrorPopup::on_response(int) {
262  delete dialog;
263  dialog = 0;
264  active = false;
265 }
266 
267 void ErrorPopup::show_msg() {
268  dialog = new Gtk::MessageDialog(msg, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
269  dialog->set_keep_above(true);
270  //Gtk::VBox *ma = dialog->get_message_area(); // not in Gtkmm 0.20
271  //FIXME: no comment :-)
272  Gtk::VBox *ma = dynamic_cast<Gtk::VBox*>(
273  *(++dynamic_cast<Gtk::HBox*>(
274  *dialog->get_vbox()->get_children().begin())->get_children().begin()));
275  // add an alignment parent to the label widget inside the message area
276  // should better define our own dialog instead of hacking MessageDialog...
277  Gtk::Alignment *align = new Gtk::Alignment();
278  align->show();
279  dynamic_cast<Gtk::Label*>(*ma->get_children().begin())->reparent(*align);
280  ma->pack_start(*manage(align));
281  align->set_padding(50,20,0,10);
282  Gtk::VBox *vbox = dynamic_cast<Gtk::VBox *>(dialog->get_child());
283  vbox->set_redraw_on_allocate(true);
284  g_signal_connect(GTK_WIDGET(vbox->gobj()), "expose-event",
285  G_CALLBACK(gx_cairo::error_box_expose), NULL);
286  // vbox->signal_expose_event().connect(
287  //sigc::group(&gx_cairo::error_box_expose,GTK_WIDGET(vbox->gobj()),sigc::_1,(void*)0),false);
288  dialog->set_title(_("GUITARIX ERROR"));
289  dialog->signal_response().connect(
290  sigc::mem_fun(*this, &ErrorPopup::on_response));
291  dialog->show();
292 }
293 
294 /****************************************************************
295  ** class GxSplashBox
296  ** show splash screen at start up
297  */
298 
299 class GxSplashBox: public Gtk::Window {
300  public:
301  explicit GxSplashBox();
302  ~GxSplashBox();
303  virtual void on_show();
304 };
306 
308  : Gtk::Window(Gtk::WINDOW_POPUP) {
309  set_redraw_on_allocate(true);
310  set_app_paintable();
311  g_signal_connect(GTK_WIDGET(gobj()), "expose-event",
312  G_CALLBACK(gx_cairo::splash_expose), NULL);
313  //signal_expose_event().connect(
314  // sigc::group(&gx_cairo::splash_expose, GTK_WIDGET(gobj()),
315  // sigc::_1, (void*)0), false);
316  set_decorated(false);
317  set_type_hint(Gdk::WINDOW_TYPE_HINT_SPLASHSCREEN);
318  set_position(Gtk::WIN_POS_CENTER );
319  set_default_size(613, 180);
320  show_all();
321 }
322 
324  Gtk::Widget::on_show();
325  while(Gtk::Main::events_pending())
326  Gtk::Main::iteration(false);
327 }
328 
329 /****************************************************************
330  ** main()
331  */
332 #if 0
333 #ifndef NDEBUG
334 int debug_display_glade(gx_engine::GxEngine& engine, gx_system::CmdlineOptions& options,
335  gx_engine::ParamMap& pmap, const string& fname) {
336  pmap.set_init_values();
337  if (!options.get_rcset().empty()) {
338  std::string rcfile = options.get_style_filepath("gx_head_"+options.get_rcset()+".rc");
339  gtk_rc_parse(rcfile.c_str());
340  gtk_rc_reset_styles(gtk_settings_get_default());
341  }
342  Gtk::Window *w = 0;
343  gx_ui::GxUI ui;
344  Glib::RefPtr<gx_gui::GxBuilder> bld = gx_gui::GxBuilder::create_from_file(fname, &machine);
345  w = bld->get_first_window();
346  gx_ui::GxUI::updateAllGuis(true);
347  if (w) {
348  Gtk::Main::run(*w);
349  delete w;
350  }
351  return 0;
352 }
353 #endif
354 #endif
355 
356 #ifdef NDEBUG
357 // switch off GTK warnings in Release build
358 static void null_handler(const char *log_domain, GLogLevelFlags log_level,
359  const gchar *msg, gpointer user_data ) {
360  return ;
361 }
362 #endif
363 
364 static void mainHeadless(int argc, char *argv[]) {
365  Glib::init();
366  Gio::init();
367 
368  PosixSignals posixsig(false); // catch unix signals in special thread
370  options.parse(argc, argv);
371  options.process(argc, argv);
372  // ---------------- Check for working user directory -------------
373  bool need_new_preset;
374  if (gx_preset::GxSettings::check_settings_dir(options, &need_new_preset)) {
375  cerr <<
376  _("old config directory found (.gx_head)."
377  " state file and standard presets file have been copied to"
378  " the new directory (.config/guitarix).\n"
379  " Additional old preset files can be imported into the"
380  " new bank scheme by mouse drag and drop with a file"
381  " manager");
382  return;
383  }
384 
385  gx_engine::GxMachine machine(options);
386 
388  machine.loadstate();
389  //if (!in_session) {
390  // gx_settings.disable_autosave(options.get_opt_auto_save());
391  //}
392 
393  if (! machine.get_jack()->gx_jack_connection(true, true, 0, options)) {
394  cerr << "can't connect to jack\n";
395  return;
396  }
397  if (need_new_preset) {
399  }
400  // ----------------------- Run Glib main loop ----------------------
401  cout << "Ctrl-C to quit\n";
402  Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create();
403  machine.get_jack()->shutdown.connect(sigc::mem_fun(loop.operator->(),&Glib::MainLoop::quit));
404  int port = options.get_rpcport();
405  if (port == RPCPORT_DEFAULT) {
406  port = 7000;
407  }
408  if (port != RPCPORT_NONE) {
409  machine.start_socket(sigc::mem_fun(loop.operator->(),&Glib::MainLoop::quit), options.get_rpcaddress(), port);
410  loop->run();
411  } else {
412  loop->run();
413  }
415 }
416 
417 static void exception_handler() {
418  try {
419  throw; // re-throw current exception
420  } catch (const GxFatalError& error) {
421  cerr << error.what() << endl;
422  gx_print_fatal(_("Guitarix fatal error"), error.what());
423  } catch (const Glib::OptionError &error) {
424  cerr << error.what() << endl;
425  cerr << _("use \"guitarix -h\" to get a help text") << endl;
426  gx_print_fatal(_("Guitarix Commandline Option Error"),
427  Glib::ustring::compose(
428  "%1\n%2",
429  error.what(),
430  _("use \"guitarix -h\" to get a help text")));
431  } catch (const Glib::Error& error) {
432  const GError *perr = error.gobj();
433  Glib::ustring msg = Glib::ustring::compose(
434  "Glib::Error[%1/%2]: %3",
435  g_quark_to_string(perr->domain),
436  perr->code,
437  (perr->message) ? perr->message : "(null)");
438  cerr << msg << endl;
439  gx_print_fatal(_("Guitarix fatal error"), msg);
440  } catch (const std::exception& except) {
441  Glib::ustring msg = Glib::ustring::compose(
442  "std::exception: %1", except.what());
443  cerr << msg << endl;
444  gx_print_fatal(_("Guitarix fatal error"), msg);
445  } catch(...) {
446  cerr << _("unknown error") << endl;
447  gx_print_fatal(_("Guitarix fatal error"),_("unknown error"));
448  }
449 }
450 
451 static void mainGtk(int argc, char *argv[]) {
452  Glib::init();
453  Gxw::init();
454 
455  PosixSignals posixsig(true); // catch unix signals in special thread
456  Glib::add_exception_handler(sigc::ptr_fun(exception_handler));
458  Gtk::Main main(argc, argv, options);
459  options.process(argc, argv);
460  GxSplashBox * Splash = NULL;
461 #ifdef NDEBUG
462  Splash = new GxSplashBox();
463  g_log_set_handler("Gtk",G_LOG_LEVEL_WARNING,null_handler,NULL);
464 #endif
465  GxExit::get_instance().signal_msg().connect(
466  sigc::ptr_fun(gx_gui::show_error_msg)); // show fatal errors in UI
467  ErrorPopup popup;
469  sigc::mem_fun(popup, &ErrorPopup::on_message));
470  // ---------------- Check for working user directory -------------
471  bool need_new_preset;
472  if (gx_preset::GxSettings::check_settings_dir(options, &need_new_preset)) {
473  Gtk::MessageDialog dialog(
474  _("old config directory found (.gx_head)."
475  " state file and standard presets file have been copied to"
476  " the new directory (.config/guitarix).\n"
477  " Additional old preset files can be imported into the"
478  " new bank scheme by mouse drag and drop with a file"
479  " manager"), false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, true);
480  dialog.set_title("Guitarix");
481  dialog.run();
482  }
483 
484  gx_engine::GxMachine machine(options);
485 #if 0
486 #ifndef NDEBUG
487  if (argc > 1) {
488  delete Splash;
489  debug_display_glade(engine, options, gx_engine::parameter_map, argv[1]);
490  return;
491  }
492 #endif
493 #endif
494  // ----------------------- init GTK interface----------------------
495  MainWindow gui(machine, options, Splash, "");
496  if (need_new_preset) {
498  }
499  // ----------------------- run GTK main loop ----------------------
500  delete Splash;
501  gui.run();
503 }
504 
505 static void mainFront(int argc, char *argv[]) {
506  Glib::init();
507  Gxw::init();
508 
509  PosixSignals posixsig(true); // catch unix signals in special thread
510  Glib::add_exception_handler(sigc::ptr_fun(exception_handler));
512  Gtk::Main main(argc, argv, options);
513  options.process(argc, argv);
514  GxSplashBox * Splash = NULL;
515 #ifdef NDEBUG
516  Splash = new GxSplashBox();
517  g_log_set_handler("Gtk",G_LOG_LEVEL_WARNING,null_handler,NULL);
518 #endif
519  GxExit::get_instance().signal_msg().connect(
520  sigc::ptr_fun(gx_gui::show_error_msg)); // show fatal errors in UI
521  ErrorPopup popup;
523  sigc::mem_fun(popup, &ErrorPopup::on_message));
524  // ---------------- Check for working user directory -------------
525  bool need_new_preset;
526  if (gx_preset::GxSettings::check_settings_dir(options, &need_new_preset)) {
527  Gtk::MessageDialog dialog(
528  _("old config directory found (.gx_head)."
529  " state file and standard presets file have been copied to"
530  " the new directory (.config/guitarix).\n"
531  " Additional old preset files can be imported into the"
532  " new bank scheme by mouse drag and drop with a file"
533  " manager"), false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, true);
534  dialog.set_title("Guitarix");
535  dialog.run();
536  }
537 
538  Glib::ustring title;
539 #ifdef HAVE_AVAHI
540  if (options.get_rpcaddress().empty() && options.get_rpcport() == RPCPORT_DEFAULT) {
541  SelectInstance si(options, Splash);
542  if (Splash) {
543  Splash->show();
544  }
545  Glib::ustring a;
546  int port;
547  Glib::ustring name;
548  Glib::ustring host;
549  if (!si.get_address_port(a, port, name, host)) {
550  cerr << "Failed to get address" << endl;
551  return;
552  }
553  options.set_rpcaddress(a);
554  options.set_rpcport(port);
555  title = Glib::ustring::compose("%1 / %2:%3", name, host, port);
556  }
557 #endif // HAVE_AVAHI
558  if (options.get_rpcport() == RPCPORT_DEFAULT) {
559  options.set_rpcport(7000);
560  }
561  if (options.get_rpcaddress().empty()) {
562  options.set_rpcaddress("localhost");
563  }
564  if (title.empty()) {
565  title = Glib::ustring::compose("%1:%2", options.get_rpcaddress(), options.get_rpcport());
566  }
567  gx_engine::GxMachineRemote machine(options);
568 
569  // ----------------------- init GTK interface----------------------
570  MainWindow gui(machine, options, Splash, title);
571  if (need_new_preset) {
573  }
574  // ----------------------- run GTK main loop ----------------------
575  delete Splash;
576  machine.set_init_values();
577  gui.run();
578 }
579 
580 static bool is_headless(int argc, char *argv[]) {
581  for (int i = 0; i < argc; ++i) {
582  if (strcmp(argv[i], "-N") == 0 || strcmp(argv[i], "--nogui") == 0) {
583  return true;
584  }
585  }
586  return false;
587 }
588 
589 static bool is_frontend(int argc, char *argv[]) {
590  for (int i = 0; i < argc; ++i) {
591  if (strcmp(argv[i], "-G") == 0 || strcmp(argv[i], "--onlygui") == 0) {
592  return true;
593  }
594  }
595  return false;
596 }
597 
598 int main(int argc, char *argv[]) {
599 #ifdef DISABLE_NLS
600 // break
601 #elif IS_MACOSX
602 // break
603 #elif ENABLE_NLS
604  bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
605  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
606  textdomain(GETTEXT_PACKAGE);
607 #endif
608 
609  try {
610  // ----------------------- init basic subsystems ----------------------
611 #ifndef G_DISABLE_DEPRECATED
612  if (!g_thread_supported ()) {
613  Glib::thread_init();
614  }
615 #endif
616  if (is_headless(argc, argv)) {
617  mainHeadless(argc, argv);
618  } else if (is_frontend(argc, argv)) {
619  mainFront(argc, argv);
620  } else {
621  mainGtk(argc, argv);
622  }
623  } catch (...) {
624  exception_handler();
625  }
626  return 0;
627 }
void gx_print_info(const char *, const std::string &)
Definition: gx_logging.cpp:183
virtual void start_socket(sigc::slot< void > quit_mainloop, const Glib::ustring &host, int port)
Definition: machine.cpp:352
bool get_address_port(Glib::ustring &address, int &port, Glib::ustring &name, Glib::ustring &host)
int get_idle_thread_timeout() const
Definition: gx_system.h:503
void disable_autosave(bool v)
Definition: gx_preset.h:168
#define RPCPORT_DEFAULT
Definition: gx_system.h:350
void init(int samplingFreq)
void set_ui_thread()
Definition: gx_logging.h:115
sigc::signal< void, std::string > & signal_msg()
Definition: gx_logging.h:117
static void rt_watchdog_set_limit(int limit)
Definition: gx_jack.cpp:146
virtual void on_show()
Definition: gx_main.cpp:323
void set_rpcaddress(const Glib::ustring &address)
Definition: gx_system.h:491
gboolean error_box_expose(GtkWidget *wi, GdkEventExpose *ev, gpointer user_data)
static bool check_settings_dir(gx_system::CmdlineOptions &opt, bool *need_new_preset)
Definition: gx_preset.cpp:1048
void gx_print_fatal(const char *, const std::string &)
Definition: gx_logging.cpp:177
const Glib::ustring & get_rpcaddress()
Definition: gx_system.h:490
std::string get_style_filepath(const std::string &basename) const
Definition: gx_system.h:461
#define RPCPORT_NONE
Definition: gx_system.h:351
static GxLogger & get_logger()
Definition: gx_logging.cpp:50
PosixSignals(bool gui)
Definition: gx_main.cpp:64
void create_default_scratch_preset()
void process(int argc, char **argv)
Definition: gx_system.cpp:870
void on_message(const Glib::ustring &msg, GxLogger::MsgType tp, bool plugged)
Definition: gx_main.cpp:240
const Glib::ustring & get_rcset() const
Definition: gx_system.h:480
static GxExit & get_instance()
Definition: gx_logging.cpp:205
msg_signal & signal_message()
Definition: gx_logging.cpp:77
gboolean splash_expose(GtkWidget *wi, GdkEventExpose *ev, gpointer user_data)
bool get_auto_save_state()
Definition: gx_preset.h:167
virtual void create_default_scratch_preset()
Definition: machine.cpp:502
virtual void loadstate()
Definition: machine.cpp:483
void set_rpcport(int port)
Definition: gx_system.h:489
void gx_print_warning(const char *, const std::string &)
Definition: gx_logging.cpp:161
int get_rpcport() const
Definition: gx_system.h:488
void exit_program(std::string msg="", int errcode=1)
Definition: gx_logging.cpp:196
int main(int argc, char *argv[])
Definition: gx_main.cpp:598
void show_error_msg(const string &msg)
Glib::Dispatcher shutdown
Definition: gx_jack.h:206
bool gx_jack_connection(bool connect, bool startserver, int wait_after_connect, const gx_system::CmdlineOptions &opt)
Definition: gx_jack.cpp:425
GxChildProcs childprocs
virtual const char * what() const
Definition: gx_logging.h:94
virtual gx_jack::GxJack * get_jack()
Definition: machine.cpp:638