Guitarix
gx_child_process.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  * This is the guitarix module handling child processes spawned from it
21  *
22  * --------------------------------------------------------------------------
23  */
24 
25 #include "guitarix.h"
26 
27 #include <sys/wait.h>
28 #include <glibmm/main.h>
29 #include <fstream>
30 #include <string>
31 #include <list>
32 
33 namespace gx_child_process {
34 
35 /****************************************************************
36  ** GxChild, GxChildProcs
37  */
38 
39 bool GxChild::kill() {
40  if (m_killsignal == SIGKILL) {
41  Glib::signal_timeout().connect_once(
42  sigc::hide_return(sigc::bind(sigc::ptr_fun(::kill), m_pid, SIGKILL)), 100);
43  return ::kill(m_pid, SIGTERM) != -1;
44  } else {
45  return ::kill(m_pid, m_killsignal) != -1;
46  }
47 }
48 
50  for (list<GxChild*>::iterator i = children.begin(); i != children.end(); ++i) {
51  (*i)->kill();
52  delete *i;
53  }
54 }
55 
57  int ret = true;
58  for (list<GxChild*>::iterator i = children.begin(); i != children.end(); ++i) {
59  if (!(*i)->kill()) {
60  ret = false;
61  }
62  }
63  if (!ret) {
64  return ret;
65  }
66  Glib::RefPtr<Glib::MainContext> ctx = Glib::MainContext::get_default();
67  while (children.size() > 0) {
68  ctx->iteration(true);
69  }
70  return ret;
71 }
72 
73 bool GxChildProcs::kill(string name) {
74  GxChild *p = find(name);
75  if (p) {
76  return p->kill();
77  }
78  return true;
79 }
80 
81 GxChild *GxChildProcs::find(string name) {
82  for (list<GxChild*>::iterator i = children.begin(); i != children.end(); ++i) {
83  if ((*i)->hasName(name)) {
84  return *i;
85  }
86  }
87  return 0;
88 }
89 
90 #define EXIT_PGM_NOT_FOUND 127
91 
93  int status;
94  pid_t pid = waitpid(-1, &status, WNOHANG);
95  if (pid == 0 || pid == -1) {
96  return;
97  }
98  bool pgm_found = true;
99  if (WIFEXITED(status)) {
100  if (WEXITSTATUS(status) == EXIT_PGM_NOT_FOUND) {
101  pgm_found = false;
102  }
103  } else if (!WIFSIGNALED(status)) {
104  // process didn't terminate
105  return;
106  }
107  // child pid has terminated
108  list<GxChild*>& cl = childprocs.children;
109  GxChild *p = 0;
110  for (list<GxChild*>::iterator i = cl.begin(); i != cl.end(); ++i) {
111  if ((*i)->hasPid(pid)) {
112  p = *i;
113  cl.erase(i);
114  break;
115  }
116  }
117  if (p) {
118  p->terminated(pgm_found);
119  }
120 }
121 
122 GxChild *GxChildProcs::launch(string name, const char *const args[], int killsignal) {
123  // fork produces about 3ms latency on linux 2.6.31-9-rt
124  // vfork works
125  // FIXME check if its a version-specific bug
126  // changed back to fork //ad 2011-10-25
127  int pid = fork();
128  switch (pid) {
129  case -1: // error, in parent
130  return 0;
131 
132  case 0: // in child
133  sigset_t waitset;
134  sigfillset(&waitset);
135  sigprocmask(SIG_UNBLOCK, &waitset, NULL);
136  execvp(args[0], (char**)args);
137  _exit(EXIT_PGM_NOT_FOUND);
138  /*NOTREACHED*/
139  return 0;
140 
141  default: // in parent
142  GxChild *p = new GxChild(name, killsignal, pid);
143  children.push_back(p);
144  return p;
145  }
146 }
147 
148 GxChild *GxChildProcs::launch(string name, list<string> args, int killsignal) {
149  const char **p = new const char*[args.size()+1];
150  unsigned int i = 0;
151  for (list<string>::iterator j = args.begin(); j != args.end(); ++j) {
152  // cout << *j << endl;
153  p[++i] = j->c_str();
154  }
155  assert(i == args.size());
156  p[i] = 0;
157  GxChild *c = launch(name, p, killsignal);
158  delete p;
159  return c;
160 }
161 
163 
164 /****************************************************************
165  ** Menu Callbacks
166  */
167 
168 #if false // unused
169 
170 //----------------------- jack_capture settings ---------------------------
171 JackCaptureGui::JackCaptureGui(GxChild *p, GtkCheckMenuItem *i)
172  : item(i) {
173  gtk_widget_ref(GTK_WIDGET(item));
174  p->terminated.connect(sigc::mem_fun(*this, &JackCaptureGui::terminated));
175 }
176 
177 void JackCaptureGui::terminated(bool pgm_found) {
178  gtk_check_menu_item_set_active(item, false);
179  if (pgm_found) {
180  gx_print_info("Jack capture gui", "jack_capture_gui2 terminated");
181  } else {
183  " "
184  " ERORR [Jack Capture GUI]\n\n "
185  " jack_capture_gui2 is not installed! "
186  );
187  }
188  gtk_widget_unref(GTK_WIDGET(item));
189  delete this;
190 }
191 
192 void JackCaptureGui::start_stop(GtkCheckMenuItem *menuitem, gpointer) {
193  const char *app_name = "jack_capture_gui2";
194  if (gtk_check_menu_item_get_active(menuitem)) {
195  if (childprocs.find(app_name)) {
196  return;
197  }
198  string sess = string(getenv("HOME")) + "/guitarix_session";
199  const char * const args[] = {
200  app_name, "-o", "yes", "-f", sess.c_str(),
201  "-n", "guitarix", "-p", "/.guitarix/ja_ca_ssetrc", 0 };
202  GxChild *jack_cap_gui = childprocs.launch(app_name, args, SIGTERM);
203  if (jack_cap_gui) {
204  new JackCaptureGui(jack_cap_gui, menuitem);
205  } else {
207  " "
208  "ERROR [Jack capture gui]\n\n "
209  "jack_capture_gui2 could not be launched!"
210  );
211  gx_print_error("Jack capture gui",
212  string("jack_capture_gui2 could not be launched (fork failed)!"));
213  gtk_check_menu_item_set_active(menuitem, FALSE);
214  }
215  } else {
216  childprocs.kill(app_name);
217  }
218 }
219 
220 //---------------------------- Jack Capture -------------------------------
221 
222 JackCapture::JackCapture(GxChild *p, GtkToggleButton *b)
223  : button(b) {
224  gtk_widget_ref(GTK_WIDGET(button));
225  p->terminated.connect(sigc::mem_fun(*this, &JackCapture::terminated));
226 }
227 
228 void JackCapture::terminated(bool pgm_found) {
229  gtk_toggle_button_set_active(button, false);
230  if (pgm_found) {
231  gx_print_info("Jack Capture", "jack_capture terminated");
232  } else {
233  gx_print_warning("Record",
234  " WARNING [jack_capture]\n "
235  " You need jack_capture >= 0.9.30 by Kjetil S. Matheussen \n "
236  " Please look here\n "
237  " http://old.notam02.no/arkiv/src/?M=D\n");
238  }
239  gtk_widget_unref(GTK_WIDGET(button));
240  delete this;
241 }
242 
243 // ---- wav file construction for jack_capture
244 string JackCapture::make_fname(string buf, size_t j, size_t i, int n) {
245  ostringstream str;
246  str << buf.substr(0, j+1) << n << buf.substr(i);
247  return str.str();
248 }
249 
250 list<string> JackCapture::capture_command(int& seq) {
251  // open jack_capture setup file
252  string gfilename = gx_system::sysvar.gx_user_dir + gx_system::sysvar.jcapsetup_file;
253  ifstream f(gfilename.c_str());
254 
255  list<string> l;
256  string buf;
257  while (true) {
258  f >> buf;
259  if (!f.good()) {
260  break;
261  }
262  l.push_back(buf);
263  }
264  f.close();
265  if (l.empty()) {
267  " ERROR [Record]\n\n "
268  " Please run jack capture settings first [alt+j]"
269  );
270  return l;
271  }
272  buf = *l.rbegin(); // last argument should be filename
273  l.pop_back(); // pop last argument (re-add later after processing)
274  l.push_back("--hide-buffer-usage"); // add additional option
275  size_t i = buf.find_last_of(".");
276  if (i == string::npos) {
277  gx_print_error("Record", "could not parse cmd file (internal error)");
278  l.clear();
279  return l;
280  }
281  size_t j = buf.find_last_not_of("0123456789", i-1);
282  int n;
283  string fname;
284  for (n = 1; n < 1000; ++n) {
285  fname = make_fname(buf, j, i, n);
286  if (access(fname.c_str(), F_OK) != 0) {
287  break;
288  }
289  }
290  if (n == 1000) {
291  gx_print_error("Record", "more than 999 capture files in directory?!");
292  l.clear();
293  return l;
294  }
295  seq = n;
296  l.push_back(fname); // add filename
297  return l;
298 }
299 
300 void JackCapture::stop() {
301  childprocs.kill("jack_capture");
302 }
303 
304 void JackCapture::start_stop(GtkWidget *widget, gpointer data) {
305  static int last_seqno;
306  // here, const applies to pointer, not pointed data ;)
307  GtkToggleButton* const cap_button = reinterpret_cast<GtkToggleButton*>(widget);
308  const char *app_name = "jack_capture";
309  if (gtk_toggle_button_get_active(cap_button) == FALSE) {
310  // ---- stop recording
311  GxChild *jack_capture = childprocs.find(app_name);
312  if (jack_capture) {
313  if (jack_capture->kill()) {
315  "Record",
316  boost::format(" Terminated jack_capture, session file #%1%")
317  % last_seqno);
318  } else {
320  "Record", " Sorry, could not stop (Ctrl-C) jack_capture");
321  }
322  }
323  return;
324  }
325 
326  if (gx_gui::GxMainInterface::get_instance().jack.client == NULL) {
327  gtk_toggle_button_set_active(cap_button, FALSE);
329  " WARNING [Record]\n\n "
330  " Reconnect to Jack server first (Shift+C)"
331  );
332  return;
333  }
334 
335  gx_print_info("Record", " Trying to run jack_capture");
336  list<string> capturas = capture_command(last_seqno);
337  if (capturas.empty()) {
338  gtk_toggle_button_set_active(cap_button, FALSE);
339  return; // messages to user already done
340  }
341  GxChild *jack_capture = childprocs.launch(app_name, capturas, SIGINT);
342  if (!jack_capture) {
343  gtk_toggle_button_set_active(cap_button, FALSE);
344  gx_print_error("Record",
345  " WARNING [jack_capture] Sorry, could not start jack_capture");
346  return;
347  }
348  new JackCapture(jack_capture, cap_button);
350  "Record",
351  boost::format("Started jack_capture, session file #%1%") % last_seqno);
352 }
353 #endif
354 
355 //-------------------- meterbridge --------------------------
356 
357 Meterbridge::Meterbridge(GxChild *p, Glib::RefPtr<Gtk::ToggleAction>& a)
358  : action(a) {
359  p->terminated.connect(sigc::mem_fun(*this, &Meterbridge::terminated));
360 }
361 
362 void Meterbridge::terminated(bool pgm_found) {
363  action->set_active(false);
364  if (pgm_found) {
365  gx_print_info("Meterbridge", "meterbridge terminated");
366  } else {
368  " "
369  " WARNING [meterbridge]\n\n "
370  " meterbridge is not installed! "
371  );
372  }
373  delete this;
374 }
375 
377  childprocs.kill("meterbridge");
378 }
379 
380 void Meterbridge::start_stop(Glib::RefPtr<Gtk::ToggleAction>& action, gx_jack::GxJack& jack) {
381  // no need to do all this if jack is not running
382  if (!jack.client) {
384  " WARNING [Meterbridge]\n\n "
385  " Reconnect to Jack server first (Shift+C)"
386  );
387  return;
388  }
389 
390  const char *app_name = "meterbridge";
391  if (action->get_active()) {
392  if (childprocs.find(app_name)) {
393  return;
394  }
395  string s = jack.get_instancename() + "_" + app_name;
396  const char * const args[] = {
397  app_name, "-n", s.c_str(), "-t", "sco", "-c", "3",
398  (jack.client_name+":in_0").c_str(),
399  (jack.client_name+":out_0").c_str(),
400  (jack.client_insert_name+":in_0").c_str(),
401  (jack.client_insert_name+":out_0").c_str(),
402  (jack.client_insert_name+":out_1").c_str(),
403  0 };
404  GxChild *meterbridge = childprocs.launch(app_name, args, SIGKILL);
405  if (meterbridge) {
406  new Meterbridge(meterbridge, action);
407  } else {
409  " "
410  "WARNING [meterbridge]\n\n "
411  "meterbridge could not be launched!"
412  );
413  gx_print_error("Meterbridge",
414  string("meterbridge could not be launched (fork failed)!"));
415  action->set_active(false);
416  }
417  } else { // -- deactivate meterbridge
418  childprocs.kill(app_name);
419  }
420 }
421 } /* end of gx_child_process namespace */
void gx_print_info(const char *, const std::string &)
Definition: gx_logging.cpp:183
jack_client_t * client
Definition: gx_jack.h:171
void gx_print_error(const char *, const std::string &)
Definition: gx_logging.cpp:166
#define EXIT_PGM_NOT_FOUND
GxChild * launch(string name, const char *const args[], int killsignal)
void gx_print_warning(const char *, const std::string &)
Definition: gx_logging.cpp:161
sigc::signal< void, bool > terminated
const string & get_instancename()
Definition: gx_jack.h:202
static void start_stop(Glib::RefPtr< Gtk::ToggleAction > &action, gx_jack::GxJack &jack)
GxChildProcs childprocs
string client_name
Definition: gx_jack.h:203
int gx_message_popup(const char *)
string client_insert_name
Definition: gx_jack.h:204