Logo Search packages:      
Sourcecode: jack-tools version File versions  Download package

jack.plumbing.c

/***** jack.plumbing.c - (c) rohan drape, 2003-2004 *****/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <semaphore.h>

#define MAX_RULES    512
#define MAX_STRING   512
#define MAX_SUBEXP   4
#define SYS_RULESET  "/etc/jack.plumbing"

#include "file.c"
#include "jack.c"
#include "regex.c"
#include "time.c"

enum plumb_command {
  connect ,
  connect_exclusive ,
  also_connect ,
} ;

typedef struct 
{
  enum plumb_command command ;
  char left[MAX_STRING] ;
  regex_t left_c ;
  char right[MAX_STRING] ;
}
rule_t ;
  
typedef struct 
{
  rule_t r[MAX_RULES] ;       /* The rule set. */
  int n ;               /* Number of rules. */
  char i[MAX_STRING] ;        /* User rule set filename (~/.jack.plumbing). */
  jack_client_t *j ;          /* JACK client. */
  pthread_t t ;               /* Plumbing thread. */
  sem_t s ;             /* Wakeup semaphore. */
  int w ;               /* Do not send wakeup unless TRUE. */
  time_t i_m ;                /* Last user rule set modification time. */
  time_t s_m ;                /* Last system rule set modification time. */
  unsigned long u ;           /* The number of usecs to defer for connections. */
}
plumber_t ;

/* Send message to stderr. */

#define inform(s, ...) fprintf (stderr, s"\n", __VA_ARGS__)

/* Parse a plumbing command name.  If the command is not recognised
   inform user and return -1. */

static int
parse_command ( const char *s )
{
  if ( strcmp ( s , "connect" ) == 0 ) {
    return connect ;
  } else if ( strcmp ( s , "connect-exclusive" ) == 0 ) {
    return connect_exclusive ;
  } if ( strcmp ( s , "also-connect" ) == 0 ) {
    return also_connect ;
  } else {
    inform ( "Illegal command: `%s'." , s ) ;
    return -1 ;
  }
}

/* Add a rule to the rule set.  If appropriate the left hand side is
   pre-compiled to a regex_t. */

static void
add_rule_to_set ( plumber_t *p , enum plumb_command command , const char *left , const char *right )
{
  inform ( "Add rule: '%d' , '%s' - '%s'." , command , left , right ) ;
  p->r[p->n].command = command ;
  snprintf ( p->r[p->n].left , MAX_STRING , "^%s$" , left ) ;
  if ( p->r[p->n].command == connect || p->r[p->n].command == connect_exclusive ) {
    xregcomp ( &(p->r[p->n].left_c) , p->r[p->n].left , REG_EXTENDED ) ;
  }
  snprintf ( p->r[p->n].right , MAX_STRING , "^%s$" , right ) ;
  p->n += 1 ;
}

/* Clear all rules.  Where required compiled regex_t types are
   reclaimed. */

static void
clear_rule_set ( plumber_t *p )
{
  int i ;
  for ( i = 0 ; i < p->n ; i++ ) {
    if ( p->r[i].command == connect || p->r[i].command == connect_exclusive ) {
      regfree ( &(p->r[i].left_c) ) ;
    }
  }
  p->n = 0 ;
}

/* Parse the rule at `s' to `p'.  This is a pretend parser, only feed
   it well made input... */

static void
acquire_rule ( plumber_t *p , const char *s )
{
  if ( s[0] == ';' || s[0] == '\0' ) {
    return ;
  }
  if ( p->n >= MAX_RULES ) {
    inform ( "Rule lost, too many rules: '%s'." , s ) ;
    return ;
  }
  char s_command[MAX_STRING] , s_left[MAX_STRING] , s_right[MAX_STRING] ;
  int err = sscanf ( s , "(%s \"%[^\"]\" \"%[^\"]\")" , s_command , s_left , s_right ) ;
  if ( err != 3 ) { 
    inform ( "Rule lost, scan failed: '%s'." , s ) ;
    return ;
  }
  inform ( "Rule accepted: '%s' , '%s' - '%s'." , s_command , s_left , s_right ) ;
  add_rule_to_set ( p , parse_command ( s_command ) , s_left , s_right ) ;
}

/* Returns one if the file `f' is modified more recently that `m',
   else zero.  If the file is modified the modification time is
   written to `m'.  */

static int
rule_set_is_modified_p ( const char *f , time_t *m )
{
  if ( ! file_exists_p ( f ) ) {
    inform ( "Rule file does not exist: '%s'." , f ) ;
    return 0 ;
  }
  time_t mtime = stat_mtime ( f ) ;
  if ( mtime <= *m ) {
    inform ( "Rule file not modified: '%s'." , f ) ;
    return 0 ;
  }
  *m = mtime ;
  return 1 ;
}

/* Read in the rule set from `f'. */

static void
acquire_rule_set ( plumber_t *p , const char *f )
{
  FILE *fp = fopen ( f , "r" ) ;
  if ( ! fp ) {
    inform ( "Rule file inaccessible: '%s'." , f ) ;
    return ;
  }
  char s[MAX_STRING] ;
  while ( fgets ( s , MAX_STRING , fp ) ) {
    s[strlen(s)-1] = '\0' ;
    acquire_rule ( p , s ) ;
  }
  fclose ( fp ) ;
  return ;
}

/* Add all connect rules implied by the 'also-connect' rules.  */

static void
process_also_connect_rules ( plumber_t *p )
{
  int i ;
  for ( i = 0 ; i < p->n ; i++ ) {
    if ( p->r[i].command == also_connect ) {
      rule_t a = p->r[i] ;
      int j ;
      for ( j = 0 ; j < p->n ; j++ ) {
      if ( p->r[j].command == connect ) {
        rule_t c = p->r[j] ;
        if ( strcmp ( a.left , c.right ) == 0 ) {
          add_rule_to_set ( p , connect , c.left , a.right ) ;
        }
        if ( strcmp ( a.left , c.left ) == 0 ) {
          add_rule_to_set ( p , connect , a.right , c.right ) ;
        }
      }
      }
    }
  }
}

/* Consult both the system wide and the user rule set files.  If
   either is modified clear the rule set and re-read both files. */

static void
acquire_all_rule_sets ( plumber_t *p )
{
  int s_modified = rule_set_is_modified_p ( SYS_RULESET , &(p->s_m) ) ;
  int i_modified = rule_set_is_modified_p ( p->i , &(p->i_m) ) ;
  if ( s_modified || i_modified ) {
    clear_rule_set ( p ) ;
    acquire_rule_set ( p , SYS_RULESET ) ;
    acquire_rule_set ( p , p->i ) ;
    process_also_connect_rules ( p ) ;
  }
}

/* Make the right hand side (rhs) regular expression by replacing the
   escape sequence '\1' at `right' with the submatch at `left'
   indicated by `a' and `b'.  */

static void
make_rhs ( const char *left , int a , int b , const char *right , char *rhs )
{
  int copy_n = strchr ( right , '\\' ) - right ;
  int after_n = strlen ( right ) - copy_n - 2 ;
  int insert_n = ( b - a ) ;
  memcpy ( rhs , right , copy_n ) ;
  memcpy ( rhs + copy_n , left + a , insert_n ) ;
  memcpy ( rhs + copy_n + insert_n , right + copy_n + 2 , after_n ) ;
  rhs[ copy_n + insert_n + after_n ] = '\0' ;
}

/* Return TRUE iff the ports `p_l' and `p_r' match the regular
   patterns `l' and `r'. */

inline static int 
rule_applies_p ( regex_t *l , const char *p_l , const char *r , const char *p_r )
{
  regmatch_t subexp[MAX_SUBEXP] ;
  int err = xregexec ( l , p_l , MAX_SUBEXP , subexp , 0 ) ;
  if ( err != 0 ) {
    return 0 ;
  }
  char rhs[MAX_STRING] ;
  if ( subexp[1].rm_so >= 0 ) {
    make_rhs ( p_l , subexp[1].rm_so , subexp[1].rm_eo , r , rhs ) ;
  } else {
    strcpy ( rhs , r ) ;
  }
  regex_t rr ;
  xregcomp ( &rr , rhs , REG_NOSUB | REG_EXTENDED ) ;
  err = xregexec ( &rr , p_r , 0 , NULL , 0 ) ;
  regfree ( &rr ) ;  
  return ( err == 0 ) ;
}

/* Run the a connect rule. The connect and connect-exclusive rules
   have the same shape. */

static void 
do_connect_rule ( plumber_t *p , rule_t *r , const char **p_left , const char **p_right )
{
  int i ;
  for ( i = 0 ; p_left[i] ; i++ ) {
    int j ;
    for ( j = 0 ; p_right[j] ; j++ ) {
      if ( rule_applies_p ( &(r->left_c) , p_left[i] , r->right , p_right[j] ) ) {
      if ( r->command == connect ) {
        if ( ! jack_port_is_connected_p ( p->j , p_left[i] , p_right[j] ) ) {
          inform ( "%s: '%s' -> '%s'." , "Connect" , p_left[i] , p_right[j] ) ;
          xjack_connect ( p->j , p_left[i] , p_right[j] ) ;
        }
      } else if ( r->command == connect_exclusive ) {
        jack_port_clear_all_connections ( p->j , p_left[i] ) ;
        inform ( "Connect-exclusive: '%s' -> '%s'." , p_left[i] , p_right[j] ) ;
        xjack_connect ( p->j , p_left[i] , p_right[j] ) ;
      }
      }
    }
  }
}

/* Run the set of plumbing rules. */

static void
do_rule_set ( plumber_t *p )
{
  const char **p_left = jack_get_ports ( p->j , NULL , NULL , JackPortIsOutput ) ;
  const char **p_right = jack_get_ports ( p->j , NULL , NULL , JackPortIsInput ) ;
  if ( p_left && p_right ) {
    int i ;
    for ( i = 0 ; i < p->n ; i++ ) {
      if ( p->r[i].command == connect || p->r[i].command == connect_exclusive ) {
      do_connect_rule ( p , &(p->r[i]) , p_left , p_right ) ;
      }
    }
    if ( p_left ) {
      free ( p_left ) ;
    }
    if ( p_right ) {
      free ( p_right ) ;
    }
  }
}

/* Since port registrations tend to arrive in sets, when one is
   signalled the plumber asks not to receive any further requests,
   waits some small interval, runs the current rule set, then requests
   registration notification. This should sort rules, but does not,
   write rule sets in sequence. */

static void *
do_plumbing ( void *PTR )
{
  plumber_t *p = PTR ;
  while ( 1 ) {
    sem_wait ( &(p->s) ) ;
    acquire_all_rule_sets ( p ) ;
    if ( p->n > 0 ) {
      p->w = 0 ;
      xusleep ( p->u ) ;
      do_rule_set ( p ) ;
      p->w = 1 ;
    }
  } 
  return NULL ;
}

static void
plumber_usage ( void )
{
  printf ( "Usage: jack.plumbing\n" ) ;
  exit ( 1 ) ;
}

#define SEND_WAKEUP                       \
  plumber_t *p = PTR ;                    \
  if ( p->w ) {                           \
    sem_post ( &(p->s) ) ;                \
  }

static void
on_registration ( jack_port_id_t a , int b , void *PTR )
{
  SEND_WAKEUP ;
}

static int
on_reorder ( void *PTR )
{
  SEND_WAKEUP ;
  return 0 ;
}

static void
start_plumber ( void )
{
  plumber_t p ;
  snprintf ( p.i , MAX_STRING , "%s/.jack.plumbing" , getenv ( "HOME" ) ) ;
  p.n = 0 ;
  p.w = 1 ;
  p.s_m = 0 ;
  p.i_m = 0 ;
  p.u = 200000 ;
  sem_init ( &(p.s) , 0 , 0 ) ;
  acquire_all_rule_sets ( &p ) ;
  p.j = xjack_client_new ( "jack.plumbing" ) ;
  jack_set_port_registration_callback ( p.j , on_registration , &p ) ;
  jack_set_graph_order_callback ( p.j , on_reorder , &p ) ;
  jack_activate( p.j ) ;
  pthread_create ( &(p.t) , NULL , do_plumbing , &p ) ;
  pthread_join ( p.t , NULL ) ;
}

int
main ( int argc , char *argv[] )
{
  argc != 1 ? plumber_usage () : start_plumber () ;
  return 0 ;
}

Generated by  Doxygen 1.6.0   Back to index