Logo Search packages:      
Sourcecode: lashwrap version File versions  Download package

main.cc

/*****************************************************************************
 * Lash-Wrap - A small helper application that serves as a "proxy" between
 * an un-LASH'ified app and the LASH system.
 *
 * Copyright (C) 2007-2008 Florian Paul Schmidt <mista.tapas@gmx.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *****************************************************************************/
#include <lash/lash.h>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
#include <ostream>
#include <sstream>

#include <boost/program_options/cmdline.hpp>
#include <boost/program_options/environment_iterator.hpp>
#include <boost/program_options/eof_iterator.hpp>
#include <boost/program_options/errors.hpp>
#include <boost/program_options/option.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/positional_options.hpp>
#include <boost/program_options/value_semantic.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/version.hpp>

#include <glib.h>
#include <sys/wait.h>
#include <signal.h>

#include <asoundlib.h>
#include <jack/jack.h>

extern int errno;

// some global vars to store the relevant options
std::string lash_client_name = "Lash-Wrap";

std::string jack_client_name;
std::string alsa_client_name;
int         alsa_client_id = -1;

bool        search_alsa_client_name = false;
bool        search_alsa_client_id = false;
bool        search_jack_client_name = false;
bool        probe_client_names = false;

bool        found_alsa_client_name = false;
bool        found_alsa_client_id = false;
bool        found_jack_client_name = false;
bool        sent_jack_client_name = false;

int         search_timeout = 30;
int         shutdown_timeout = 5;
int         jack_name_send_delay = 5;

char* probed_jack_client_name = NULL;

namespace po = boost::program_options;

po::options_description desc("Allowed options");

jack_client_t *jack_client = 0;
snd_seq_t *seq_client = 0;

bool quit = false;

extern "C"
{
      void jack_port_registration_callback(jack_port_id_t port_id, int register, void *arg)
      {
            // std::cout << "[Lash-Wrap]: Port callback" << std::endl;
            jack_port_t *jack_port = jack_port_by_id(jack_client, port_id);
            std::string port_name(jack_port_name(jack_port));

            size_t seperator_pos = port_name.find(":");

            std::string client_name = port_name.substr(0, seperator_pos);

            if (probe_client_names)
            {
                  std::cout << "[Lash-Wrap]: Found Jack port: " << port_name << std::endl;
                  if (probed_jack_client_name != NULL)
                  {
                        delete(probed_jack_client_name);
                  }
                  probed_jack_client_name = new char[client_name.size() + 1];
                  strcpy(probed_jack_client_name, client_name.c_str());
            }
            else if (!found_jack_client_name && std::string(client_name) == jack_client_name)
            {
                  std::cout << "[Lash-Wrap]: Found Jack client: " << client_name << std::endl;
                  found_jack_client_name = true;
            }
      }

      void signal_handler(int signal)
      {
            switch (signal)
            {
                  case SIGTERM:
                        quit = true;
                  break;

#if 0
                  case SIGCHLD:
                        std::cout << "[Lash-Wrap]: Caught SIGCHLD. Exiting.." << std::endl;
                        exit (EXIT_SUCCESS);
                  break;
#endif
                  default:
                  break;
            }
      }
}

void print_usage ()
{
      std::cout << "Usage:" << std::endl;
      std::cout << "lash_wrap [lash_wrap options] -- [commandline to start app]" << std::endl;
      std::cout << "See --help for commandline options" << std::endl;
}

int main (int argc, char *argv[])
{
      std::cout << "Lash-Wrap - a small LASH wrapper for non-LASH apps." << std::endl;
      std::cout << "Copyright 2007 Florian Paul Schmidt (GPL v2)" << std::endl;

      signal(SIGTERM, signal_handler);
      signal(SIGCHLD, SIG_IGN);

      // store & remove all possible LASH args before parsing the actual lash_wrap parameters
      lash_args_t *lash_args = lash_extract_args (&argc, &argv);

      // We strip out all arguments after "--" and concenate them
      // to the app_commandline string
      int old_argc = argc;

      for (int i = 0; i < argc; ++i)
      {
            if (std::string (argv[i]) == "--")
            {
                  argc = i;
                  break;
            }
      }

      desc.add_options()
            ("help,h", "produce this help message")
                ("version,v", "show version")
            ("lash-name,l", po::value<std::string>(&lash_client_name), "Set LASH client name.")
            ("jack-name,j", po::value<std::string>(&jack_client_name), "Set jack client name.")
            ("alsa-name,a", po::value<std::string>(&alsa_client_name), "Set alsa seq client name.")
            ("alsa-id,i", po::value<int>(&alsa_client_id), "Set alsa seq client id.")
            ("jack-name-send-delay,d", po::value<int>(&jack_name_send_delay), "Set the delay time in seconds until the jack client name is sent after lash_wrap found a corresponding port (default 5(s)).")
            ("search-timeout,t", po::value<int>(&search_timeout), "Set the timeout time in seconds until lash_wrap gives up searching for the alsa seq id, alsa client name or jack client name (default 30(s)). Use -1 for no timeout.")
            ("shutdown-timeout,s", po::value<int>(&shutdown_timeout), "Set the timeout time in seconds until lash_wrap gives up waiting for the guest process to stop. First it is sent a SIGTERM, then the shutdown-timeout time is waited, then a SIGKILL is sent (default 5(s)).")
                ("probe-client-names,p", "Try to probe startup command with correct Jack and/or Alsa client names and exit. Uses a search-timeout of 10(s) by default. Use -t option to alter timeout.")
      ;

      po::variables_map vm;

      try
      {
            po::store(po::parse_command_line(argc, argv, desc), vm);
            po::notify(vm);
      }
      catch (boost::program_options::unknown_option u)
      {
            std::cout << "[Lash-Wrap]: Error parsing options: Unknown option. See lash_wrap --help." << std::endl;
            exit (EXIT_FAILURE);
      }

      if (vm.count("help")) {
            print_usage ();
            std::cout << desc << "\n";
            exit (EXIT_SUCCESS);
      }

      if (vm.count("version")) {
            std::cout << "Version 1.0.2" << std::endl;
            exit (EXIT_SUCCESS);
      }

      if (vm.count("probe-client-names"))
      {
            probe_client_names = true;
            if (!vm.count("search-timeout"))
            {
                  search_timeout = 10;
            }
      }

      if (!probe_client_names && vm.count("alsa-name"))
      {
            std::cout << "[Lash-Wrap]: ALSA client name: " << std::endl << "  " << alsa_client_name << std::endl;
            search_alsa_client_name = true;
      }

      if (!probe_client_names && vm.count("alsa-id"))
      {
            std::cout << "[Lash-Wrap]: ALSA client id: " << std::endl << "  " << alsa_client_id << std::endl;
            search_alsa_client_id = true;
            // lash_alsa_client_id (lash_client, alsa_client_id);
      }

      if (vm.count("alsa-id") && vm.count("alsa-name"))
      {
            std::cout << "[Lash-Wrap]: Error: Only one of --alsa-client-id and --alsa-client-name can be specified" << std::endl;
            exit (EXIT_FAILURE);
      }

      if (!probe_client_names && vm.count("jack-name"))
      {
            std::cout << "[Lash-Wrap]: Jack client name: " << std::endl << "  " << jack_client_name << std::endl;
            search_jack_client_name = true;     
      }

      if (argc == old_argc)
      {
            std::cout << "[Lash-Wrap]: Error: Missing separator: \"--\"" << std::endl;
            std::cout << "[Lash-Wrap]: Exiting. See --help." << std::endl;
            exit (EXIT_FAILURE);
      }

      if (argc == old_argc - 1)
      {
            std::cout << "[Lash-Wrap]: Error: Missing commandline." << std::endl;
            std::cout << "[Lash-Wrap]: Exiting. See --help." << std::endl;
            exit (EXIT_FAILURE);
      }

      std::cout << "[Lash-Wrap]: Using LASH client name: " << lash_client_name << std::endl;

      lash_client_t *lash_client = NULL;
      lash_event_t *event = NULL;

      if (!probe_client_names)
      {           
            lash_client = lash_init (lash_args, "lash_wrap", 0, LASH_PROTOCOL (2,0));

            if (!lash_client)
            {
                  std::cout << "[Lash-Wrap]: Failed to become LASH client. Exiting.." << std::endl;
                  return (EXIT_FAILURE);
            }     

            event = lash_event_new_with_type (LASH_Client_Name);
            lash_event_set_string (event, lash_client_name.c_str());
            lash_send_event(lash_client, event);
      }

      if (search_alsa_client_name || search_alsa_client_id || probe_client_names)
      {
            int err;
            err = snd_seq_open(&seq_client, "default", SND_SEQ_OPEN_DUPLEX, 0);
            if (err < 0)
            {
                  std::cout << "[Lash-Wrap]: Warning: Couldn't open ALSA sequencer interface" << std::endl;
                  if (probe_client_names)
                        seq_client = 0;
                  else
                        exit(EXIT_FAILURE);
            }
      }

      if (search_jack_client_name || probe_client_names)
      {
            std::stringstream lash_wrap_jack_client_name;
            lash_wrap_jack_client_name << "Lash-Wrap:" << getpid();
            std::cout << "[Lash-Wrap]: Lash-Wrap using Jack client name: " << lash_wrap_jack_client_name.str() << std::endl;
            jack_client = jack_client_new((lash_wrap_jack_client_name.str().c_str()));
            
            if (jack_client == 0)
            {
                  std::cout << "[Lash-Wrap]: Warning: Couldn't connect to jack" << std::endl;
                  
                  if (!probe_client_names)
                  {
                        // close connected clients & exit
                        if (seq_client)
                              snd_seq_close(seq_client);
                        exit(EXIT_FAILURE);
                  }
            }

            if (jack_client)
            {
                  jack_set_port_registration_callback(jack_client, jack_port_registration_callback, 0);
                  jack_activate(jack_client);
            }
      }

      // Ok, let's try to spawn the app
      GPid child_id;
      GError *error;
      if (!(g_spawn_async (NULL, argv + argc + 1, NULL, (GSpawnFlags)(G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD), 0, 0, &child_id, &error)))
      {
            std::cout << "[Lash-Wrap]: Error: Failed to spawn process" << std::endl;

            if (jack_client)
                  jack_client_close(jack_client);

            if (seq_client)
                  snd_seq_close(seq_client);

            exit (EXIT_FAILURE);
      }

      float time_since_start = 0;
      float time_when_jack_client_was_found = 0;
      // lash_event_t *event;

      std::vector <std::string> alsa_names;

      while (!quit)
      {
            if (time_since_start < search_timeout || search_timeout == -1)
            {
                  if (search_jack_client_name && found_jack_client_name)
                  {
                        time_when_jack_client_was_found = time_since_start;
                        search_jack_client_name = false;
                  }
                  if (!sent_jack_client_name && found_jack_client_name && time_since_start - time_when_jack_client_was_found > jack_name_send_delay)
                  {
                        sent_jack_client_name = true;
                        lash_jack_client_name (lash_client, jack_client_name.c_str());
                  }

                  if (seq_client)
                  {
                        if ((search_alsa_client_name && !found_alsa_client_name) || probe_client_names)
                        {
                              snd_seq_client_info_t *info;
                              snd_seq_client_info_malloc (&info);
                              snd_seq_client_info_set_client (info, 0);
                              while (!snd_seq_query_next_client (seq_client, info))
                              {
                                    if (probe_client_names)
                                    {
                                          // add alsa name to the list if it's not already there
                                          std::string alsa_name = std::string(snd_seq_client_info_get_name(info));
                                          bool name_exists = false;
                                          for(unsigned int i = 0; i < alsa_names.size(); i++)
                                          {
                                                if (alsa_names.at(i) == alsa_name)
                                                {
                                                      name_exists = true;
                                                }
                                          }
                                          if (!name_exists)
                                          {
                                                alsa_names.push_back(alsa_name);
                                          }
                                    }
                                    else if (std::string(snd_seq_client_info_get_name(info)) == alsa_client_name)
                                    {
                                          std::cout << "[Lash-Wrap]: Found ALSA client: " << snd_seq_client_info_get_name(info) << std::endl;
                                          alsa_client_id = snd_seq_client_info_get_client (info);
                                          std::cout << "[Lash-Wrap]: With ID: " << alsa_client_id << std::endl;
                                          lash_alsa_client_id (lash_client, alsa_client_id);
                                          found_alsa_client_name = true;
                                    }
                              }
                              snd_seq_client_info_free(info);
                        }

                        if (search_alsa_client_id && !found_alsa_client_id)
                        {
                              snd_seq_client_info_t *info;
                              snd_seq_client_info_malloc (&info);
                              snd_seq_client_info_set_client (info, 0);
                              while (!snd_seq_query_next_client (seq_client, info))
                              {
                                    if (snd_seq_client_info_get_client(info) == alsa_client_id)
                                    {
                                          std::cout << "[Lash-Wrap]: Found ALSA client: " << snd_seq_client_info_get_name(info) << std::endl;
                                          std::cout << "[Lash-Wrap]: With ID: " << alsa_client_id << std::endl;
                                          lash_alsa_client_id (lash_client, alsa_client_id);
                                          found_alsa_client_name = true;
                                    }
                              }
                              snd_seq_client_info_free(info);
                        }
                  }
            }
            else if (probe_client_names)
            {
                  for(unsigned int i = 0; i < alsa_names.size(); i++)
                  {
                        std::cout << "[Lash-Wrap]: Found ALSA client: " << alsa_names.at(i) << std::endl;
                  }

                  if (probed_jack_client_name == NULL)
                  {
                        std::cout << "[Lash-Wrap]: No Jack clients found! Maybe jackd is not running or the wrapped app doesn't have Jack ports." << std::endl;
                  }

                  std::cout << "[Lash-Wrap]: ******************************************************" << std::endl;
                  std::cout << "[Lash-Wrap]: * probed startup command:" << std::endl;
                  std::cout << "[Lash-Wrap]: * lash_wrap ";

                  // if lash name wasn't given print the app's binary name as lash name
                  std::string probed_lash_name;
                  if (vm.count("lash-name") == 0)
                  {
                        std::string wrapped_cmd = std::string(*(argv + argc + 1));
                        size_t seperator_pos = wrapped_cmd.find(" ");
                        probed_lash_name = wrapped_cmd.substr(0, seperator_pos);
                  }
                  else
                  {
                        probed_lash_name = std::string(lash_client_name);
                  }
                  std::cout << "-l " << probed_lash_name << " ";

                  if (probed_jack_client_name != NULL)
                  {
                        std::cout << "-j \"" << std::string(probed_jack_client_name) << "\" ";
                  }
                  if (alsa_names.size() > 0)
                  {
                        std::cout << "-a \"" << alsa_names.back() << "\" ";
                  }
                  std::cout << "-- " << std::string(*(argv + argc + 1)) << std::endl;

                  std::cout << "[Lash-Wrap]: ******************************************************" << std::endl;

                  quit = true;
            }

            //std::cout << "." << std::endl;
            int status;
            pid_t ret;
            ret = waitpid (child_id, &status, WNOHANG);
            if (ret == child_id)
            {
                  if (WIFEXITED(status) || WIFSIGNALED(status) || WCOREDUMP(status))
                  {
                        // process finished, so we leave too.
                        // TODO: handle segfaults and other signals
                        std::cout << "[Lash-Wrap]: Exiting, because the app exited. Bye.." << std::endl;

                        // close connected clients before exiting
                        if (seq_client)
                              snd_seq_close(seq_client);

                        if (jack_client)
                              jack_client_close(jack_client);

                        exit (EXIT_SUCCESS);
                  }
            }

            if (!probe_client_names && lash_get_pending_event_count (lash_client))
            {
                  while ((event = lash_get_event (lash_client)))
                  {
                        switch (lash_event_get_type (event))
                        {
                              case LASH_Quit:
                                    quit = true;
                              break;
      
                              default:
                                    std::cout << "[Lash-Wrap]: Unexpected Event - Either us or lashd is broken" << std::endl;
                              break;
                        }
                        
                        lash_event_destroy (event);
                  }
            }
            //sleep 100ms
            usleep (300000);
            time_since_start += 0.3;
      }

      if (seq_client)
            snd_seq_close (seq_client);

      if (jack_client)
            jack_client_close(jack_client);

      int status;
      pid_t ret;
      
      // Ok, time to kill the app process as LASH told us to. first send nice TERM signal
      std::cout << "[Lash-Wrap]: Sending child process the TERM signal..." << std::endl;
      kill (child_id, SIGTERM);
      for (int i = 0; i < shutdown_timeout; ++i)
      {
            std::cout << "[Lash-Wrap]: Waiting for child process... " << shutdown_timeout - i << std::endl;
            ret = waitpid (child_id, &status, WNOHANG);
            if (ret == child_id)
            {
                  if (WIFEXITED(status) || WIFSIGNALED(status) || WCOREDUMP(status))
                  {
                        std::cout << "[Lash-Wrap]: Child exited gracefully. Exiting..." << std::endl;
                        exit(EXIT_SUCCESS);
                  }
            }
            if (ret == -1)
            {
                  std::cout << "[Lash-Wrap]: waitpid() returned error code. Exiting..." << std::endl;
                  exit(EXIT_FAILURE);
            }
            sleep(1);
      }

      // Ok, time to kill the app process as LASH told us to. send evil KILL signal
      std::cout << "[Lash-Wrap]: Sending child process the KILL signal..." << std::endl;
      kill (child_id, SIGKILL);

      quit = false;

      while (!quit)
      {
            std::cout << "[Lash-Wrap]: Waiting for child process..." << std::endl;
            ret = waitpid (child_id, &status, WNOHANG);

            // TODO better error handling
            if (ret == -1)
            {
                  std::cout << "[Lash-Wrap]: waitpid() returned error. Bye.." << std::endl;
                  exit (EXIT_SUCCESS);
            }

            if (ret == child_id)
            {
                  if (WIFEXITED(status) || WIFSIGNALED(status) ||WCOREDUMP(status))
                  {
                        // process finished, so we leave too.
                        // TODO: handle segfaults and other signals
                        std::cout << "[Lash-Wrap]: Exiting, because the app exited (on signal SIGKILL). Bye.." << std::endl;
                        exit (EXIT_SUCCESS);
                  }
            }
            sleep (1);
      }
}


Generated by  Doxygen 1.6.0   Back to index