diff --git a/ChangeLog b/ChangeLog
index 1cdb340d..11091714 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -14757,3 +14757,8 @@
 	'Description' fields in 'rlm/rlm_padpoint.c'.
 	* Fixed a bug when generating the 'Year' field in RLM updates
 	in 'rdairplay/rlmhost.cpp'.
+2015-02-18 Fred Gleason <fredg@paravelsystems.com>
+	* Added 'RDMatrix::SoftwareAuthority' to the 'RDMatrix::Type'
+	enumeration in 'lib/rdmatrix.cpp' and 'lib/rdmatrix.h'.
+	* Implemented a switcher driver for systems using Software Authority
+	Protocol in 'ripcd/swauthority.cpp' and 'ripcd/swauthority.h'.
diff --git a/docs/SWITCHERS.txt b/docs/SWITCHERS.txt
index 0425301f..620984e9 100644
--- a/docs/SWITCHERS.txt
+++ b/docs/SWITCHERS.txt
@@ -27,6 +27,7 @@ Sierra Automated Systems 32000 Audio Router
 Sierra Automated Systems 64000 Audio Router
 Sierra Automated Systems Universal Serial Interface (USI)
 Sine Systems ACU-1 (Prophet version)
+Software Authority Protocol
 StarGuide III Satellite Receiver
 Wegener Unity4000 DVB Satellite Receiver
 
@@ -632,6 +633,22 @@ by simultaneously pressing buttons 1 and 8 on the front panel of the unit
 for at least one second.
 
 
+----------------------------------------------------------------------------
+SOFTWARE AUTHORITY PROTOCOL
+
+Driver Name: Software Authority Protocol
+
+Supported RML Commands:
+  Switch Take ('ST')
+
+GENERAL NOTES:
+Used to control devices (such as the Axia 'PathFinder' system) by means of
+the 'Software Authority' protocol.  Control is by means of a
+TCP/IP connection (typically to port 9500) on the controled system.  Most
+parameters, including the number of inputs and outputs as well as the
+endpoint names, are autodetected by the driver.
+
+
 ----------------------------------------------------------------------------
 STARGUIDE III Satellite Receiver
 
diff --git a/lib/rdmatrix.cpp b/lib/rdmatrix.cpp
index 0f21d1a8..341af834 100644
--- a/lib/rdmatrix.cpp
+++ b/lib/rdmatrix.cpp
@@ -63,7 +63,8 @@ bool __mx_primary_controls[RDMatrix::LastType][RDMatrix::LastControl]=
     {0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0},  // LiveWire LWRP GPIO
     {0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0},  // BT Sentinel 4 Web
     {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0},  // BT GPI-16
-    {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}   // Modem Lines
+    {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0},  // Modem Lines
+    {0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0}   // Software Authority
   };
 bool __mx_backup_controls[RDMatrix::LastType][RDMatrix::LastControl]=
   {
@@ -101,7 +102,8 @@ bool __mx_backup_controls[RDMatrix::LastType][RDMatrix::LastControl]=
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  // LiveWire LWRP GPIO
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  // BT Sentinel 4 Web
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  // BT GPI-16
-    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}  // Modem Lines
+    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  // Modem Lines
+    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}   // Software Authority
   };
 
 int __mx_default_values[RDMatrix::LastType][RDMatrix::LastControl]=
@@ -140,7 +142,8 @@ int __mx_default_values[RDMatrix::LastType][RDMatrix::LastControl]=
     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0},    // LiveWire  LWRP GPIO
     {1,0,0,0,0,0,0,0,0,0,0,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0},   // BT Sentinel 4 Web
     {0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0},  // BT GPI-16
-    {0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0}  // Modem Lines
+    {0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0},   // Modem Lines
+    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}    // Software Authority
   };
 
 RDMatrix::RDMatrix(const QString &station,int matrix)
@@ -684,6 +687,10 @@ QString RDMatrix::typeString(RDMatrix::Type type)
 	return QString("Serial Port Modem Control Lines");
 	break;
 
+      case RDMatrix::SoftwareAuthority:
+	return QString("Software Authority Protocol");
+	break;
+
       default:
 	return QString("Unknown Type");
 	break;
diff --git a/lib/rdmatrix.h b/lib/rdmatrix.h
index 6dcae61c..b9909cbb 100644
--- a/lib/rdmatrix.h
+++ b/lib/rdmatrix.h
@@ -38,7 +38,7 @@ class RDMatrix
 	     LiveWireLwrpAudio=20,Quartz1=21,BtSs44=22,BtSrc8III=23,BtSrc16=24,
 	     Harlond=25,Acu1p=26,LiveWireMcastGpio=27,Am16=28,
 	     LiveWireLwrpGpio=29,BtSentinel4Web=30,BtGpi16=31,ModemLines=32,
-	     LastType=33};
+	     SoftwareAuthority=33,LastType=34};
   enum Endpoint {Input=0,Output=1};
   enum Mode {Stereo=0,Left=1,Right=2};
   enum VguestAttribute {VguestEngine=0,VguestDevice=1,VguestSurface=2,
diff --git a/rdadmin/view_node_info.cpp b/rdadmin/view_node_info.cpp
index d2e4382f..106a968b 100644
--- a/rdadmin/view_node_info.cpp
+++ b/rdadmin/view_node_info.cpp
@@ -188,6 +188,7 @@ ViewNodeInfo::ViewNodeInfo(QWidget *parent,const char *name)
   view_sources_view->setItemMargin(5);
   view_sources_view->addColumn(tr("#"));
   view_sources_view->setColumnAlignment(0,Qt::AlignCenter);
+  view_sources_view->setColumnSortType(0,RDListView::LineSort);
   view_sources_view->addColumn(tr("CHAN"));
   view_sources_view->setColumnAlignment(1,Qt::AlignCenter);
   view_sources_view->addColumn(tr("NAME"));
@@ -325,6 +326,7 @@ void ViewNodeInfo::closeData()
 
 void ViewNodeInfo::WriteSourceItem(RDLiveWireSource *src,RDListViewItem *item)
 {
+
   item->setText(1,QString().sprintf("%05d",src->channelNumber()));
   item->setText(2,src->primaryName());
   if(src->rtpEnabled()) {
diff --git a/rdairplay/log_play.cpp b/rdairplay/log_play.cpp
index 68e20fdc..1a7f57da 100644
--- a/rdairplay/log_play.cpp
+++ b/rdairplay/log_play.cpp
@@ -1445,9 +1445,9 @@ void LogPlay::graceTimerData()
 
 void LogPlay::playStateChangedData(int id,RDPlayDeck::State state)
 {
-#ifdef SHOW_SLOTS
-  printf("playStateChangedData(%d)\n",id);
-#endif
+  //#ifdef SHOW_SLOTS
+  printf("playStateChangedData(%d,%d), log: %s\n",id,state,(const char *)logName());
+  //#endif
   switch(state) {
       case RDPlayDeck::Playing:
 	Playing(id);
@@ -1842,6 +1842,11 @@ bool LogPlay::StartEvent(int line,RDLogLine::TransType trans_type,
 	  rdevent_player->
 	    exec(logline->resolveWildcards(play_start_rml[aport]));
 	}
+
+	printf("channelStarted(%d,%d,%d,%d)\n",
+	       play_id,playdeck->channel(),
+	       playdeck->card(),playdeck->port());
+
 	emit channelStarted(play_id,playdeck->channel(),
 			    playdeck->card(),playdeck->port());
 	LogLine(RDConfig::LogInfo,QString().sprintf(
@@ -2752,6 +2757,12 @@ void LogPlay::ClearChannel(int deckid)
 
   if(play_deck[deckid]->channel()>=0) {
     rdevent_player->exec(play_stop_rml[play_deck[deckid]->channel()]);
+
+    printf("Deck: %d  channelStopped(%d,%d,%d,%d\n",deckid,
+	   play_id,play_deck[deckid]->channel(),
+	   play_deck[deckid]->card(),
+	   play_deck[deckid]->port());
+
     emit channelStopped(play_id,play_deck[deckid]->channel(),
 			play_deck[deckid]->card(),
 			play_deck[deckid]->port());
diff --git a/ripcd/Makefile.am b/ripcd/Makefile.am
index c297896f..294912cd 100644
--- a/ripcd/Makefile.am
+++ b/ripcd/Makefile.am
@@ -66,6 +66,7 @@ dist_ripcd_SOURCES = acu1p.cpp acu1p.h\
                      sasusi.cpp sasusi.h\
                      starguide3.cpp starguide3.h\
                      starguide_feed.cpp starguide_feed.h\
+                     swauthority.cpp swauthority.h\
                      switcher.cpp switcher.h\
                      unity4000.cpp unity4000.h\
                      unity_feed.cpp unity_feed.h\
@@ -102,6 +103,7 @@ nodist_ripcd_SOURCES = moc_am16.cpp\
                        moc_sas64000gpi.cpp\
                        moc_sasusi.cpp\
                        moc_starguide3.cpp\
+                       moc_swauthority.cpp\
                        moc_switcher.cpp\
                        moc_unity4000.cpp\
                        moc_vguest.cpp
diff --git a/ripcd/loaddrivers.cpp b/ripcd/loaddrivers.cpp
index 2ab907b4..407c88d8 100644
--- a/ripcd/loaddrivers.cpp
+++ b/ripcd/loaddrivers.cpp
@@ -54,6 +54,7 @@
 #include <sas64000gpi.h>
 #include <sasusi.h>
 #include <starguide3.h>
+#include <swauthority.h>
 #include <unity4000.h>
 #include <vguest.h>
 
@@ -178,6 +179,10 @@ bool MainObject::LoadSwitchDriver(int matrix_num)
     ripcd_switcher[matrix_num]=new SasUsi(matrix,this);
     break;
 
+  case RDMatrix::SoftwareAuthority:
+    ripcd_switcher[matrix_num]=new SoftwareAuthority(matrix,this);
+    break;
+
   case RDMatrix::StarGuideIII:
     ripcd_switcher[matrix_num]=new StarGuide3(matrix,this);
     break;
diff --git a/ripcd/swauthority.cpp b/ripcd/swauthority.cpp
new file mode 100644
index 00000000..148f35e1
--- /dev/null
+++ b/ripcd/swauthority.cpp
@@ -0,0 +1,446 @@
+// swauthority.cpp
+//
+// A Rivendell switcher driver for systems using Software Authority Protocol
+//
+//   (C) Copyright 2002-2015 Fred Gleason <fredg@paravelsystems.com>
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License version 2 as
+//   published by the Free Software Foundation.
+//
+//   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+
+#include <stdlib.h>
+
+#include <qstringlist.h>
+
+#include <rdescape_string.h>
+#include <rddb.h>
+#include <globals.h>
+#include <swauthority.h>
+
+SoftwareAuthority::SoftwareAuthority(RDMatrix *matrix,QObject *parent,const char *name)
+  : Switcher(matrix,parent,name)
+{
+  swa_matrix=matrix->matrix();
+  swa_ptr=0;
+  swa_istate=0;
+
+  //
+  // Get Matrix Parameters
+  //
+  swa_username=matrix->username(RDMatrix::Primary);
+  swa_password=matrix->password(RDMatrix::Primary);
+  swa_porttype=matrix->portType(RDMatrix::Primary);
+  swa_ipaddress=matrix->ipAddress(RDMatrix::Primary);
+  swa_ipport=matrix->ipPort(RDMatrix::Primary);
+  swa_inputs=0;
+  swa_outputs=0;
+  swa_gpis=matrix->gpis();
+  swa_gpos=matrix->gpos();
+  swa_start_cart=matrix->startCart(RDMatrix::Primary);
+  swa_stop_cart=matrix->stopCart(RDMatrix::Primary);
+
+  //
+  // Reconnection Timer
+  //
+  swa_reconnect_timer=new QTimer(this,"swa_reconnect_timer");
+  connect(swa_reconnect_timer,SIGNAL(timeout()),this,SLOT(ipConnect()));
+
+  //
+  // Initialize the connection
+  //
+  swa_socket=new QSocket(this,"swa_socket");
+  connect(swa_socket,SIGNAL(connected()),this,SLOT(connectedData()));
+  connect(swa_socket,SIGNAL(connectionClosed()),
+	  this,SLOT(connectionClosedData()));
+  connect(swa_socket,SIGNAL(readyRead()),
+	  this,SLOT(readyReadData()));
+  connect(swa_socket,SIGNAL(error(int)),this,SLOT(errorData(int)));
+  ipConnect();
+}
+
+
+RDMatrix::Type SoftwareAuthority::type()
+{
+  return RDMatrix::SoftwareAuthority;
+}
+
+
+unsigned SoftwareAuthority::gpiQuantity()
+{
+  return swa_gpis;
+}
+
+
+unsigned SoftwareAuthority::gpoQuantity()
+{
+  return swa_gpos;
+}
+
+
+bool SoftwareAuthority::primaryTtyActive()
+{
+  return false;
+}
+
+
+bool SoftwareAuthority::secondaryTtyActive()
+{
+  return false;
+}
+
+
+void SoftwareAuthority::processCommand(RDMacro *cmd)
+{
+  switch(cmd->command()) {
+    /*
+      case RDMacro::FS:
+	if((cmd->arg(1).toInt()<1)||(cmd->arg(1).toInt()>256)||
+	   (swa_porttype!=RDMatrix::TcpPort)) {
+	  cmd->acknowledge(false);
+	  emit rmlEcho(cmd);
+	  return;
+	}
+	snprintf(str,256,"%c1%03d\x0D\x0A",0x13,cmd->arg(1).toInt());
+	SendCommand(str);
+	cmd->acknowledge(true);
+	emit rmlEcho(cmd);
+	break;
+
+    */
+      case RDMacro::ST:
+	if((cmd->arg(1).toInt()<0)||(cmd->arg(1).toInt()>swa_inputs)||
+	   (cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>swa_outputs)) {
+	  cmd->acknowledge(false);
+	  emit rmlEcho(cmd);
+	  return;
+	}
+	SendCommand(QString().sprintf("activateroute 1 %d %d",
+				      cmd->arg(2).toInt(),cmd->arg(1).toInt()));
+	cmd->acknowledge(true);
+	emit rmlEcho(cmd);
+	break;
+	/*
+      case RDMacro::GO:
+	if((cmd->arg(1).toString().lower()!="o")||
+	   (cmd->arg(2).toInt()<1)||(cmd->arg(2).toInt()>swa_gpos)) {
+	  cmd->acknowledge(false);
+	  emit rmlEcho(cmd);
+	  return;
+	}
+	if(cmd->arg(4).toInt()==0) {   // Latch
+	  if(cmd->arg(3).toInt()==0) {   // Off
+	    cmd_byte=0x03;
+	  }
+	  else {
+	    cmd_byte=0x02;
+	  }
+	}
+	else {
+	  if(cmd->arg(3).toInt()==0) {
+	    cmd->acknowledge(false);
+	    emit rmlEcho(cmd);
+	    return;
+	  }
+	  cmd_byte=0x01;
+	}
+	if(cmd->arg(2).toUInt()<swa_relay_numbers.size()) {
+	  if(swa_relay_numbers[cmd->arg(2).toUInt()-1]>=0) {
+	    snprintf(str,256,"\x05R%d%04d\x0D\x0A",cmd_byte,
+		    swa_relay_numbers[cmd->arg(2).toUInt()-1]);
+	    SendCommand(str);
+	    cmd->acknowledge(true);
+	    emit rmlEcho(cmd);
+	  }
+	  else {
+	    if((swa_console_numbers[cmd->arg(2).toUInt()-1]>=0)&&
+	       (swa_source_numbers[cmd->arg(2).toUInt()-1]>=0)) {
+	      if(cmd->arg(3).toInt()==0) {   // Off
+		cmd_byte=0;
+	      }
+	      else {
+		cmd_byte=1;
+	      }
+	      snprintf(str,256,"\x1A%s%d%03d%04d\x0D\x0A","20",cmd_byte,
+		      swa_console_numbers[cmd->arg(2).toUInt()-1],
+		      swa_source_numbers[cmd->arg(2).toUInt()-1]);
+	      SendCommand(str);
+	      cmd->acknowledge(true);
+	      emit rmlEcho(cmd);
+	    }
+	  }
+	}
+	break;
+    */
+      default:
+	cmd->acknowledge(false);
+	emit rmlEcho(cmd);
+	break;
+  }
+}
+
+
+void SoftwareAuthority::ipConnect()
+{
+  swa_socket->connectToHost(swa_ipaddress.toString(),swa_ipport);
+}
+
+
+void SoftwareAuthority::connectedData()
+{
+  SendCommand(QString("login")+" "+swa_username+" "+swa_password);
+}
+
+
+void SoftwareAuthority::connectionClosedData()
+{
+  LogLine(RDConfig::LogNotice,QString().
+	  sprintf("Connection to SoftwareAuthority device at %s:%d closed unexpectedly, attempting reconnect",
+		  (const char *)swa_ipaddress.toString(),
+		  swa_ipport));
+  if(swa_stop_cart>0) {
+    ExecuteMacroCart(swa_stop_cart);
+  }
+  swa_reconnect_timer->start(SWAUTHORITY_RECONNECT_INTERVAL,true);
+}
+
+
+void SoftwareAuthority::readyReadData()
+{
+  char buffer[256];
+  unsigned n;
+
+  while((n=swa_socket->readBlock(buffer,255))>0) {
+    buffer[n]=0;
+    ///    printf("RECV: %s\n",buffer);
+    for(unsigned i=0;i<n;i++) {
+      if(buffer[i]==10) {  // End of line
+	swa_buffer[--swa_ptr]=0;
+	DispatchCommand();
+	swa_ptr=0;
+      }
+      else {
+	if(swa_ptr==SWAUTHORITY_MAX_LENGTH) {  // Buffer overflow
+	  swa_ptr=0;
+	}
+	swa_buffer[swa_ptr++]=buffer[i];
+      }
+    }
+  }
+}
+
+
+void SoftwareAuthority::errorData(int err)
+{
+  switch((QSocket::Error)err) {
+      case QSocket::ErrConnectionRefused:
+	LogLine(RDConfig::LogNotice,QString().sprintf(
+	  "Connection to SoftwareAuthority device at %s:%d refused, attempting reconnect",
+		  (const char *)swa_ipaddress.toString(),
+		  swa_ipport));
+	swa_reconnect_timer->start(SWAUTHORITY_RECONNECT_INTERVAL,true);
+	break;
+
+      case QSocket::ErrHostNotFound:
+	LogLine(RDConfig::LogWarning,QString().sprintf(
+	  "Error on connection to SoftwareAuthority device at %s:%d: Host Not Found",
+		  (const char *)swa_ipaddress.toString(),
+		  swa_ipport));
+	break;
+
+      case QSocket::ErrSocketRead:
+	LogLine(RDConfig::LogWarning,QString().sprintf(
+	  "Error on connection to SoftwareAuthority device at %s:%d: Socket Read Error",
+				  (const char *)swa_ipaddress.toString(),
+				  swa_ipport));
+	break;
+  }
+}
+
+
+void SoftwareAuthority::SendCommand(const char *str)
+{
+  //  LogLine(RDConfig::LogDebug,QString().sprintf("sending SA cmd: %s",(const char *)PrettifyCommand(str)));
+  QString cmd=QString().sprintf("%s\x0d\x0a",(const char *)str);
+  swa_socket->writeBlock((const char *)cmd,strlen(cmd));
+}
+
+
+void SoftwareAuthority::DispatchCommand()
+{
+  char buffer[SWAUTHORITY_MAX_LENGTH];
+  QString cmd;
+  QString label;
+  QString sql;
+  RDSqlQuery *q;
+  QStringList f0;
+  QString name;
+
+  //  LogLine(RDConfig::LogNotice,QString().sprintf("RECEIVED: %s",(const char *)swa_buffer));
+
+  QString line_in=swa_buffer;
+  QString section=line_in.lower();
+
+  //
+  // Startup Sequence.  Get the input and output lists.
+  //
+  if(section=="login successful") {
+    sprintf(buffer,"sourcenames 1\x0D\x0A");  // Request Input List
+    SendCommand(buffer);
+    sprintf(buffer,"destnames 1\x0D\x0A");    // Request Output List
+    SendCommand(buffer);
+    return;
+  }
+  if(section=="login failure") {
+    LogLine(RDConfig::LogWarning,QString().sprintf(
+	    "Error on connection to SoftwareAuthority device at %s:%d: Login Failure",
+	    (const char *)swa_ipaddress.toString(),
+	    swa_ipport));
+    swa_socket->close();
+    return;
+  }
+
+  switch(swa_istate) {
+  case 0:   // No section selected
+    if(section==">>begin sourcenames - 1") {
+      swa_istate=1;
+      swa_inputs=0;
+      return;
+    }
+    if(section==">>begin destnames - 1") {
+      swa_istate=2;
+      swa_outputs=0;
+      return;
+    }
+    break;
+
+  case 1:   // Source List
+    if(section=="end sourcenames - 1") {
+      swa_istate=0;
+      sql=QString("update MATRICES set ")+
+	QString().sprintf("INPUTS=%d ",swa_inputs)+
+	"where (STATION_NAME=\""+RDEscapeString(rdstation->name())+"\")&&"+
+	QString().sprintf("(MATRIX=%d)",swa_matrix);
+      q=new RDSqlQuery(sql);
+      delete q;
+      return;
+    }
+    swa_inputs++;
+    f0=f0.split("\t",line_in);
+    name=f0[1];
+    if(f0.size()>=7) {
+      name=f0[6]+": "+f0[2];
+    }
+    sql=QString().sprintf("select NUMBER from INPUTS where \
+                           (STATION_NAME=\"%s\")&&	   \
+                           (MATRIX=%d)&&(NUMBER=%d)",
+			  (const char *)rdstation->name(),
+			  swa_matrix,f0[0].toInt());
+    q=new RDSqlQuery(sql);
+    if(q->first()) {
+      sql=QString().sprintf("update INPUTS set NAME=\"%s\" where \
+                            (STATION_NAME=\"%s\")&&\
+                            (MATRIX=%d)&&(NUMBER=%d)",
+			    (const char *)name,
+			    (const char *)rdstation->name(),
+			    swa_matrix,f0[0].toInt());
+    }
+    else {
+      sql=QString().sprintf("insert into INPUTS set NAME=\"%s\",\
+                            STATION_NAME=\"%s\",MATRIX=%d,NUMBER=%d",
+			    (const char *)name,
+			    (const char *)rdstation->name(),
+			    swa_matrix,f0[0].toInt());
+    }
+    delete q;
+    q=new RDSqlQuery(sql);
+    delete q;
+    break;
+
+  case 2:   // Destinations List
+    if(section=="end destnames - 1") {
+      swa_istate=0;
+      sql=QString("update MATRICES set ")+
+	QString().sprintf("OUTPUTS=%d ",swa_outputs)+
+	"where (STATION_NAME=\""+RDEscapeString(rdstation->name())+"\")&&"+
+	QString().sprintf("(MATRIX=%d)",swa_matrix);
+      q=new RDSqlQuery(sql);
+      delete q;
+
+      LogLine(RDConfig::LogInfo,QString().
+	      sprintf("Connection to SoftwareAuthority device at %s:%d established",
+		      (const char *)swa_ipaddress.toString(),
+		      swa_ipport));
+      if(swa_start_cart>0) {
+	ExecuteMacroCart(swa_start_cart);
+      }
+      return;
+    }
+    swa_outputs++;
+    f0=f0.split("\t",line_in);
+    name=f0[1];
+    if(f0.size()>=6) {
+      name=f0[3]+"/"+f0[5]+": "+f0[2];
+    }
+    sql=QString().sprintf("select NUMBER from OUTPUTS where \
+                           (STATION_NAME=\"%s\")&&\
+                           (MATRIX=%d)&&(NUMBER=%d)",
+			  (const char *)rdstation->name(),
+			  swa_matrix,f0[0].toInt());
+    q=new RDSqlQuery(sql);
+    if(q->first()) {
+      sql=QString().sprintf("update OUTPUTS set NAME=\"%s\" where \
+                             (STATION_NAME=\"%s\")&&\
+                             (MATRIX=%d)&&(NUMBER=%d)",
+			    (const char *)name,
+			    (const char *)rdstation->name(),
+			    swa_matrix,f0[0].toInt());
+    }
+    else {
+      sql=QString().sprintf("insert into OUTPUTS set NAME=\"%s\",\
+                             STATION_NAME=\"%s\",MATRIX=%d,NUMBER=%d",
+			    (const char *)name,
+			    (const char *)rdstation->name(),
+			    swa_matrix,f0[0].toInt());
+    }
+    delete q;
+    q=new RDSqlQuery(sql);
+    delete q;
+    break;
+  }
+}
+
+
+void SoftwareAuthority::ExecuteMacroCart(unsigned cartnum)
+{
+  RDMacro rml;
+  rml.setRole(RDMacro::Cmd);
+  rml.setCommand(RDMacro::EX);
+  rml.setAddress(rdstation->address());
+  rml.setEchoRequested(false);
+  rml.setArgQuantity(1);
+  rml.setArg(0,cartnum);
+  emit rmlEcho(&rml);
+}
+
+
+QString SoftwareAuthority::PrettifyCommand(const char *cmd) const
+{
+  QString ret;
+  if(cmd[0]<26) {
+    ret=QString().sprintf("^%c%s",'@'+cmd[0],cmd+1);
+  }
+  else {
+    ret=cmd;
+  }
+  return ret;
+}
diff --git a/ripcd/swauthority.h b/ripcd/swauthority.h
new file mode 100644
index 00000000..f8f3db5c
--- /dev/null
+++ b/ripcd/swauthority.h
@@ -0,0 +1,83 @@
+// swauthority.h
+//
+// A Rivendell switcher driver for systems using Software Authority Protocol
+//
+//   (C) Copyright 2002-2015 Fred Gleason <fredg@paravelsystems.com>
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License version 2 as
+//   published by the Free Software Foundation.
+//
+//   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+
+#ifndef SWAUTHORITY_H
+#define SWAUTHORITY_H
+
+#include <vector>
+
+#include <qsocket.h>
+#include <qhostaddress.h>
+#include <qtimer.h>
+
+#include <rd.h>
+#include <rdmatrix.h>
+#include <rdmacro.h>
+
+#include <switcher.h>
+
+#define SWAUTHORITY_RECONNECT_INTERVAL 10000
+#define SWAUTHORITY_MAX_LENGTH 256
+
+class SoftwareAuthority : public Switcher
+{
+ Q_OBJECT
+ public:
+  SoftwareAuthority(RDMatrix *matrix,QObject *parent=0,const char *name=0);
+  RDMatrix::Type type();
+  unsigned gpiQuantity();
+  unsigned gpoQuantity();
+  bool primaryTtyActive();
+  bool secondaryTtyActive();
+  void processCommand(RDMacro *cmd);
+
+ private slots:
+  void ipConnect();
+  void connectedData();
+  void connectionClosedData();
+  void readyReadData();
+  void errorData(int err);
+
+ private:
+  void SendCommand(const char *str);
+  void DispatchCommand();
+  void ExecuteMacroCart(unsigned cartnum);
+  QString PrettifyCommand(const char *cmd) const;
+  QSocket *swa_socket;
+  char swa_buffer[SWAUTHORITY_MAX_LENGTH];
+  unsigned swa_ptr;
+  QHostAddress swa_ipaddress;
+  QString swa_username;
+  QString swa_password;
+  int swa_matrix;
+  int swa_ipport;
+  int swa_inputs;
+  int swa_outputs;
+  int swa_gpis;
+  int swa_gpos;
+  QTimer *swa_reconnect_timer;
+  unsigned swa_start_cart;
+  unsigned swa_stop_cart;
+  int swa_istate;
+  RDMatrix::PortType swa_porttype;
+};
+
+
+#endif  // SWAUTHORITY_H