changeset 648:ddec8f9931d9 trunk

[svn] Port inetctl plugin from XMMS.
author chainsaw
date Sun, 19 Feb 2006 15:03:06 -0800
parents 4b67e5300169
children e43b04a4e599
files Plugins/General/Makefile.in Plugins/General/inetctl/Makefile.in Plugins/General/inetctl/config.h.in Plugins/General/inetctl/inetctl.c Plugins/General/inetctl/inetctl.h Plugins/General/inetctl/inetctl_actions.c Plugins/General/inetctl/inetctl_client.c Plugins/General/inetctl/inetctl_command.c Plugins/General/inetctl/inetctl_config.c Plugins/General/inetctl/inetctl_status.c configure.ac
diffstat 11 files changed, 2119 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Plugins/General/Makefile.in	Sun Feb 19 12:39:36 2006 -0800
+++ b/Plugins/General/Makefile.in	Sun Feb 19 15:03:06 2006 -0800
@@ -1,4 +1,6 @@
 include ../../mk/rules.mk
 include ../../mk/objective.mk
 
-SUBDIRS = lirc song_change
+ALL_PLUGINS = lirc song_change inetctl
+SUBDIRS = @GENERAL_PLUGINS@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/Makefile.in	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,12 @@
+include ../../../mk/rules.mk
+include ../../../mk/objective.mk
+
+OBJECTIVE_LIBS = libinetctl.so
+
+LIBDIR = $(plugindir)/$(GENERAL_PLUGIN_DIR)
+
+SOURCES = inetctl.c inetctl_actions.c inetctl_client.c inetctl_command.c inetctl_config.c inetctl_status.c
+
+CFLAGS += -fPIC -DPIC $(GTK_CFLAGS) -I../../../intl -I../../..
+
+OBJECTS = ${SOURCES:.c=.o}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/config.h.in	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,61 @@
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+
+/* Define if you have the xmms_remote_get_balance function.  */
+#undef HAVE_XMMS_REMOTE_GET_BALANCE
+
+/* Define if you have the xmms_remote_get_info function.  */
+#undef HAVE_XMMS_REMOTE_GET_INFO
+
+/* Define if you have the xmms_remote_get_main_volume function.  */
+#undef HAVE_XMMS_REMOTE_GET_MAIN_VOLUME
+
+/* Define if you have the xmms_remote_get_output_time function.  */
+#undef HAVE_XMMS_REMOTE_GET_OUTPUT_TIME
+
+/* Define if you have the xmms_remote_get_playlist_pos function.  */
+#undef HAVE_XMMS_REMOTE_GET_PLAYLIST_POS
+
+/* Define if you have the xmms_remote_get_playlist_time function.  */
+#undef HAVE_XMMS_REMOTE_GET_PLAYLIST_TIME
+
+/* Define if you have the xmms_remote_get_playlist_title function.  */
+#undef HAVE_XMMS_REMOTE_GET_PLAYLIST_TITLE
+
+/* Define if you have the xmms_remote_is_paused function.  */
+#undef HAVE_XMMS_REMOTE_IS_PAUSED
+
+/* Define if you have the xmms_remote_is_playing function.  */
+#undef HAVE_XMMS_REMOTE_IS_PLAYING
+
+/* Define if you have the xmms_remote_is_repeat function.  */
+#undef HAVE_XMMS_REMOTE_IS_REPEAT
+
+/* Define if you have the xmms_remote_is_shuffle function.  */
+#undef HAVE_XMMS_REMOTE_IS_SHUFFLE
+
+/* Define if you have the xmms_remote_pause function.  */
+#undef HAVE_XMMS_REMOTE_PAUSE
+
+/* Define if you have the xmms_remote_play function.  */
+#undef HAVE_XMMS_REMOTE_PLAY
+
+/* Define if you have the xmms_remote_playlist_next function.  */
+#undef HAVE_XMMS_REMOTE_PLAYLIST_NEXT
+
+/* Define if you have the xmms_remote_playlist_prev function.  */
+#undef HAVE_XMMS_REMOTE_PLAYLIST_PREV
+
+/* Define if you have the xmms_remote_set_balance function.  */
+#undef HAVE_XMMS_REMOTE_SET_BALANCE
+
+/* Define if you have the xmms_remote_set_main_volume function.  */
+#undef HAVE_XMMS_REMOTE_SET_MAIN_VOLUME
+
+/* Define if you have the xmms_remote_stop function.  */
+#undef HAVE_XMMS_REMOTE_STOP
+
+/* Define if you have the xmms_remote_toggle_repeat function.  */
+#undef HAVE_XMMS_REMOTE_TOGGLE_REPEAT
+
+/* Define if you have the xmms_remote_toggle_shuffle function.  */
+#undef HAVE_XMMS_REMOTE_TOGGLE_SHUFFLE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl.c	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,498 @@
+/*
+        xmms_inetctl - Network control port for XMMS 
+        Copyright (C) 2002 Gerald Duprey <gerry@cdp1802.org>
+	  based on xmms-mpcp by Vadim "Kryptolus" Berezniker <vadim@berezniker.com>
+          in turn, based on work from xmms-goodnight
+
+	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.
+*/
+
+/* The protocol looks much like the FTP protocol.  All commands and responses */
+/* are line oriented text.  Commands are in upper case.  Responses have a     */
+/* numeric code indentifying the response, followed by parameters. Each token */
+/* is seperated by a space.  Parameters can be enclosed by single or double   */
+/* quotes, especially important when the parameter has embedded spaces.  The  */
+/* line must be terminated with a CR and LF (ASCII 0x0d, then 0x0a).  Extra   */
+/* spaces between parameters are ignored.  Blank lines or lines begining with */
+/* a # are ignored by the server.  Responses can be solicited (i.e. in reply  */
+/* to a REQUEST command) or unsolicited (i.e. in response to the server       */
+/* moving on to a new track).  There is no guarantee that the first response  */
+/* the client receives after sending a command is in anyway related to that   */
+/* command (i.e. you can't send a REQUEST CUR_TRACK and assume the next reply */
+/* the server sends is the track info).  Stateless is the way to go here.     */
+
+/* Commands:
+ * USER name PASSWORD word       - Authenticate user
+ * PLAY                          - Resume playing (after pause or stop)
+ * PLAY_FILE path/filename.mp3   - Load and play the file.  There can be multiple
+ *                                 files and the files can be MP3s and M3Us
+ *                                 Any existing playlist is cleared first.
+ * LOAD_FILE path/filename.m3u   - Load the file.  This is the same as PLAY_FILE
+ *                                 except it won't automatically start playing
+ *                                 after loading.
+ * PAUSE                         - Pauses playing
+ * RESUME                        - Result playing (same as PLAY)
+ * NEXT_TRACK                    - Move on to next track
+ * PREV_TRACK                    - Move back to previous track
+ * STOP                          - Stops player
+ * EXIT                          - Closes this connection
+ * QUIT                          - Closes connection and shutdown XMMS
+ * VOLUME n                      - Sets volume to n% (0-100, 0=Mute)
+ * SHUFFLE on/off                - Sets shuffle mode on or off
+ * REPEAT on/off                 - Sets play repeat mode on or off
+ *
+ * Per Client commands (these affect only your connection)
+ * TIME_UPDATES on/off           - Sets progress in time messages are sent during play (default on)
+ * FORMATTED_TIME on/off         - Sets whether time reports are formatted (h:mm:ss) or
+ *                                 just raw seconds.  Default is on.
+ * REQUEST STATUS                - Returns player status
+ * REQUEST TRACK                 - Returns current track info via 011
+ * REQUEST PLAYLIST              - Returns current playlist info via 012
+ * REQUEST POSITION              - Returns current position in song via 013
+ * REQUEST ALL                   - Causes all status (010-019) to be returned in order
+ */
+
+/* Responses:
+ *
+ * NOTE: Future versions will not change the data being sent, but MAY
+ * add additional parameters on to the end of the status.  Don't assume
+ * that the number of parameters will never change.  It won't get 
+ * smaller, but it may grow.  As long as you can handle that (and
+ * ignore it until your code is reved to support the new stuff), you'll
+ * be fine.
+ *
+ * Times are always represented in hours:minutes:seconds.  Minutes
+ * and seconds will always be two digitis.  Hours will be as
+ * many digits as necessary, but always at least one (for 0).
+
+ * 000 - Welcome/connection message
+ *    000 hostname XMMS_NETCTL server (Version theversion) ready.
+ *    000 homer.cdp1802.org XMMS_NETCTL server (Version V0.2.0) ready.
+ * 001 - Authentication needed/failure indicator
+ *    001 NOT AUTHENTICATED
+ * 002 - Unrecognized command or parameters 
+ *    002 UNKNOWN COMMAND
+ * 003 - Too few/many arguments
+ *    003 TOO FEW ARGUMENTS
+ * 004 - Unrecognized Argument/invalid argument
+ *    004 INVALID ARGUMENT
+ *
+ * 010 - Current player status
+ *    010 state volume shuffle_mode repeat_mode
+ *    010 PLAYING 100 SHUFFLE REPEAT
+ * 011 - Current track info
+ *    011 title filename length rate freq chans
+ *    011 "They Might Be Giants - Twisting" "/usr/opt/mp3/tmbg/flood/twisting.mp3" 0:1:57 128 44 2
+ * 012 - Current Playlist info
+ *    012 curtrack totaltracks 
+ *    012 5 104
+ * 013 - Track playing status update
+ *    013 timeintrack totaltracktime
+ *    013 0:01:20 0:04:31
+ */
+
+/* Include commons */
+#include "inetctl.h"
+
+/* Define client managment structures */
+struct _clientDef {
+  pid_t clientPID;
+  int clientSocket;
+};
+typedef struct _clientDef clientDef;
+typedef struct _clientDef * clientDefPtr;
+
+/* Port we wait for connections on */
+int listenPort = 1586;
+int listenSocket = -1;
+
+/* Status flags */
+Bool debugMode = FALSE;
+Bool inetctlEnabled = TRUE;
+pid_t serverPID = -1;
+
+/* Plugin definition structure */
+GeneralPlugin inetctl = {
+  NULL,
+  NULL,
+  -1,
+  "iNetControl " INETCTLVERSION,
+  inetctl_init,
+  inetctl_about,
+  inetctl_config,
+  inetctl_cleanup,
+};
+
+/* Define client tracking */
+#define CLIENT_MAX 64
+int clientCount = 0;
+clientDef clientInfo[CLIENT_MAX];
+
+/* Define formatting buffer */
+#define FORMAT_BUFFER_MAX 1024
+char formatBuffer[FORMAT_BUFFER_MAX];
+
+/* Forward declarations */
+Bool removeClient(int);
+Bool acceptNewClients(int);
+Bool configureServer();
+
+/* Shutdown the server */
+void shutdownServer() {
+  int clientIndex;
+
+  writeDebug("Shutting down server\n");
+
+  /* Shutdown all children first */
+  for (clientIndex = clientCount - 1; clientIndex >= 0; clientIndex--) {
+    removeClient(clientInfo[clientIndex].clientPID);
+  }
+
+  /* Close socket */
+  if (listenSocket != -1) {
+    close(listenSocket);
+    listenSocket = -1;
+  }
+
+  /* Now we stop ourselves/server */
+  if (serverPID != -1) {
+    writeDebug("Killing server process %d\n", getpid());
+    kill(getpid(), SIGKILL);
+    serverPID = -1;
+  }
+}
+
+/* Remove an existing client from the client list */
+Bool removeClient(int theClientPID) {
+  int clientIndex;
+  int purgeIndex;
+
+  for (clientIndex = 0; clientIndex < clientCount; clientIndex++) {
+    /* See if this is our client */
+    if (clientInfo[clientIndex].clientPID == theClientPID) {
+      /* Close the connection */
+      close(clientInfo[clientIndex].clientSocket);
+      
+      /* Kill the client */
+      kill(clientInfo[clientIndex].clientPID, SIGKILL);
+
+      /* Shift all other clients down */
+      for (purgeIndex = clientIndex; purgeIndex < (clientCount - 1); purgeIndex++) 
+	clientInfo[purgeIndex] = clientInfo[purgeIndex + 1];
+
+      /* Down the client count & we ar done */
+      clientCount--;
+      writeDebug("Removed client %d, %d clients remaining\n", theClientPID, clientCount);
+      return TRUE;
+    }
+  }
+
+  /* If we got here, there was no match */
+  return FALSE;
+}
+
+/* Add the passed client to the list */
+Bool addClient(pid_t theClientPID, int theClientSocket) {
+  /* Make sure there is room */
+  if (clientCount == CLIENT_MAX) return FALSE;
+
+  /* Add in info */
+  clientInfo[clientCount].clientPID = theClientPID;
+  clientInfo[clientCount].clientSocket = theClientSocket;
+  clientCount++;
+
+  writeDebug("Spawned child %d to handle client, now %d clients\n", theClientPID, clientCount);
+  
+  return TRUE;
+}
+
+/* Handle termination signal */
+void termHandler(int sigNum) {
+  shutdownServer();
+  return;
+}
+
+/* Handle children ending/dying */
+void mournChildren(int sigNum) {
+  int childPID, childStatus;
+
+  /* Check for any child processes that have "silently" died */
+  while ((childPID = waitpid(-1, &childStatus, WNOHANG))) {
+    /* No children means just get the heck out (quietly) */
+    if ((childPID < 0) && (errno == 10)) break;
+
+    /* Handle an error */
+    if (childPID < 0) {
+      writeError("Error while mourning children, %s (%d)\n", strerror(errno), errno);
+    } else {
+      /* We are done */
+      removeClient(childPID);
+      writeDebug("Recognized that child %d died\n", childPID);
+    }
+  }
+
+  /* Resinstall handler */
+  signal(SIGCHLD, mournChildren);
+}
+
+/* Write a message when in debug mode */
+void writeDebug(String theFormat, ...) {
+  va_list theParms;
+
+  /* Skip out if not debugging */
+  if (!debugMode) return;
+
+  /* Format and print message */
+  va_start(theParms, theFormat);
+  fprintf(stderr, "inetctl DEBUG [%6d]: ", getpid());
+  vfprintf(stderr, theFormat, theParms);
+  va_end(theParms);
+}
+
+/* Write an error message */
+void writeError(String theFormat, ...) {
+  va_list theParms;
+
+  /* Format and print message */
+  va_start(theParms, theFormat);
+  fprintf(stderr, "inetctl ERROR [%6d]: ", getpid());
+  vfprintf(stderr, theFormat, theParms);
+  va_end(theParms);
+}
+
+GeneralPlugin * get_gplugin_info () {
+  writeDebug("XMMS Asked for inetctl info\n");
+  return (&inetctl);
+}
+
+void inetctl_cleanup() {
+  int serverStatus = -1;
+
+  writeDebug("Cleanup module started\n");
+
+  /* Clear enabled indicator */
+  inetctlEnabled = FALSE;
+
+  /* Shutdown the server and free resources */
+  if (serverPID != -1) {
+    writeDebug("Shuttingdown inetctl server\n");
+    kill(serverPID, SIGTERM);
+    waitpid(serverPID, &serverStatus, 0);
+  }
+
+  writeDebug("Module cleanup complete\n");
+}
+
+/* Initialize this module */
+void inetctl_init() {
+  int errval;
+  int reuseFlag = 1;
+  struct sockaddr_in server;
+  struct protoent * tcpInfo;
+
+  writeDebug("Begining module initialization\n");
+
+  /* lookup tcp protocol info */
+  tcpInfo = getprotobyname("tcp");
+  if (tcpInfo == NULL) {
+    writeError("Unable lookup info on TCP protocol\n");
+    exit(1);
+  }
+
+  /* Read the configuration for this module */
+  read_config();
+
+  /* create the main socket */
+  listenSocket = socket(PF_INET, SOCK_STREAM, tcpInfo->p_proto);
+  setsockopt(listenSocket,  SOL_SOCKET, SO_REUSEADDR, &reuseFlag, sizeof(reuseFlag));
+  server.sin_family = AF_INET;
+  server.sin_addr.s_addr = htonl(INADDR_ANY);
+  server.sin_port = htons(listenPort);
+  
+  /* bind the socket */
+  if ((errval = bind(listenSocket, (struct sockaddr *) &server, sizeof(server))) == -1) {
+    writeError("Attempt to bind to port %d failed - %s (%d)\n",
+	       listenPort, strerror(errno), errno);
+    close(listenSocket);
+    listenSocket = -1;
+    return;
+  }
+
+  /* Initiate active listening on our port */
+  if ((errval = listen(listenSocket, 10)) == -1) {
+    writeError("Attempt to listen on port %d failed - %s (%d)\n", 
+	       listenPort, strerror(errno), errno); 
+    close(listenSocket);
+    listenSocket = -1;
+    exit(1);
+  }
+
+  /* Install a child death handler */
+  signal(SIGCHLD, mournChildren);
+
+  /* Fork off server */
+  switch(serverPID = fork()) {
+  case -1:
+    writeError("Attempt to fork failed - %s (%d)\n", strerror(errno), errno);
+    close(listenSocket);
+    listenSocket = -1;
+    exit(1);
+    break;
+
+  case 0:
+    /* Configure the server */
+    configureServer();
+
+    /* Process client connections */
+    writeDebug("Now ready to accept client connections\n");
+    acceptNewClients(listenSocket);
+    shutdownServer();
+    break;
+
+  default:
+    writeDebug("Server installed as PID %d\n", serverPID);
+    close(listenSocket);
+    listenSocket = -1;
+    break;
+  }
+
+  /* Flag ourselves (re)initialized */
+  inetctlEnabled = TRUE;
+
+  writeDebug("Intialization complete\n");
+}
+
+/* Do one-shot server initialization */
+Bool configureServer() {
+  /* Get our PID */
+  serverPID = getpid();
+
+  /* Install us as the process group leader */
+  setpgrp();
+
+  /* Catch requests for us to shut down */  
+  signal(SIGTERM, termHandler);
+
+  /* Initialize status tracking */
+  if (!initStatusTracking()) return FALSE;
+
+  /* Server ready to rock and roll */
+  return TRUE;
+}
+
+/* Check if the recently connected client, described by clientInfo, */
+/* is valid (i.e. matches any security we have installed).  If so,  */
+/* return TRUE.  Otherwise, return FALSE.                           */
+Bool isValidClient(struct sockaddr_in * clientInfo) {
+  /* For now, we are disabling authentication */
+  userAuthenticated = TRUE;
+  userName = "TEST";
+
+  /* IP Security and such would go here */
+  return TRUE;
+}
+
+/* Client connection handlers inherit lots of stuff we */
+/* not need or want from the server.  Here we drop that*/
+/* which isn't needed for a client                     */
+void closeInheritedResources() {
+  int clientIndex;
+
+  /* Close servers listen socket, if open */
+  if (listenSocket != -1) close(listenSocket);
+  listenSocket = -1;
+
+  /* Close any clients too */
+  for (clientIndex = 0; clientIndex < clientCount; clientIndex++) {
+    close(clientInfo[clientIndex].clientSocket);
+    clientInfo[clientIndex].clientSocket = -1;
+    clientInfo[clientIndex].clientPID = -1;
+  }
+  clientCount = 0;
+}
+
+/* Process incoming client connections.  Each incoming connection */
+/* gets it own process to handle the connection.                  */
+Bool acceptNewClients(int mainSocket) {
+  int errval;
+  pid_t clientPID;
+  int clientSocket;
+  socklen_t len;
+  struct pollfd connWait;
+  struct sockaddr_in client;
+
+  /* Initialize polling wait structure */
+  connWait.fd = mainSocket;
+  connWait.events = POLLIN;
+
+  /* We repeatedly process incomming connections */
+  while(inetctlEnabled) {
+    /* Wait for a new connection */
+    SYSCALL(errval = poll(&connWait, 1, -1));
+    if (errval == -1) {
+      writeError("Poll waiting for new connections failed - %s (%d)\n",
+		 strerror(errno), errno);
+      return FALSE;
+    }
+
+    /* See if we got a timeout */
+    if (errval == 0) {
+      updatePlayerStatus(-1);
+      continue;
+    }
+
+    /* Accept a new connection */
+    if ((clientSocket = accept(mainSocket, (struct sockaddr *) &client, &len)) == -1) {
+      writeError("Unable to accept new client connection - %s (%d)\n",
+		 strerror(errno), errno);
+      continue;
+    }
+
+    /* Do security checks on this connection */
+    if (!isValidClient(&client)) {
+      writeDebug("Closing client connection - failed security check\n");
+      close(clientSocket);
+      clientSocket = -1;
+      continue;
+    }
+
+    /* Fork off a child to handle this client connection */
+    switch(clientPID = fork()) {
+    case -1:
+      writeError("Attempt to form new client connection failed - %s (%d)\n",
+		 strerror(errno), errno);
+      return FALSE;
+      break;
+
+    case 0:
+      /* Close off inherited resources */
+      closeInheritedResources();
+
+      /* Handle this clients traffic */
+      processClientRequests(getpid(), clientSocket);
+      removeClient(clientPID);
+      exit(0);
+      break;
+
+    default:
+      /* We've handed that client off to the child - close ours */
+      addClient(clientPID, clientSocket);
+      break;
+    }
+  }
+
+  /* Processing completed */
+  return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl.h	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,133 @@
+/* inetctl.h - common includes for inetctl */
+
+#include <gtk/gtk.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
+#include <asm/errno.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <unistd.h>
+#include <gtk/gtk.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <time.h>
+#include <glib.h>
+#include <string.h>
+#include <ctype.h>
+#include "libaudacious/beepctrl.h"
+#include "libaudacious/configdb.h"
+#include "audacious/plugin.h"
+
+#include "config.h"
+
+#define INETCTLVERSION "1.2"
+#define INETCTLVERSION_MAJOR	1
+#define INETCTLVERSION_MINOR	2
+#define INETCTLVERSION_MICRO	0
+
+#define CONFIGFILE "/.xmms/inetctl"
+
+#define SYSCALL(call) while(((int) (call) == -1) && (errno == EINTR))
+
+typedef char * String;
+
+typedef int Bool;
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+/* Port we wait for connections on */
+extern int listenPort;
+extern pid_t serverPID;
+
+/* Status flags */
+extern Bool debugMode;
+extern Bool inetctlEnabled;
+
+/* User flags */
+extern Bool userAuthenticated;
+extern String userName;
+
+/* Command dispatch table */
+typedef Bool (* _cmdFunc)(int, String[]);
+struct _cmdTable {
+  char commandName[32];
+  _cmdFunc commandFunc;
+  Bool alwaysAllowed;
+};
+typedef struct _cmdTable cmdTable;
+typedef struct _cmdTable * cmdTablePtr;
+
+extern cmdTable commandTable[];
+
+/* Player status info */
+#define PLAYER_TEXT_MAX 512
+struct _playerStatDef {
+  Bool infoValid;
+
+  /* Playlist info */
+  gint curTrackInPlaylist;
+  gint totalTracksInPlaylist;
+
+  /* Track Info */
+  gint curTrackLength;
+  gint curTrackTime;
+  char trackTitle[PLAYER_TEXT_MAX];
+  char trackFile[PLAYER_TEXT_MAX];
+
+  /* Player info */
+  gint volume, balance;
+  gboolean playing, paused, repeat, shuffle;
+};
+typedef struct _playerStatDef playerStatus;
+typedef struct _playerStatDef * playerStatusPtr;
+
+extern GeneralPlugin inetctl;
+
+/* Public methods invoked by xmms (from inetctl.c) */
+extern void inetctl_init();
+extern void inetctl_about();
+extern void inetctl_config();
+extern void inetctl_cleanup();
+
+/* Support functions in inetctl.c */
+extern void writeDebug(String, ...);
+extern void writeError(String, ...);
+
+/* Command parse in inetctl_command.c */
+extern void upString(String);
+extern void trimString(String);
+extern Bool dispatchCommand(String);
+extern void endThisClient();
+
+/* Configuration support in inetctl_config.c */
+extern void read_config();
+
+/* Client support in inetctl_client.c */
+extern Bool sendResponse(String, String, ...);
+extern void processClientRequests(pid_t, int);
+
+/* Status support in inetctl_status.c */
+extern playerStatus lastPlayerStatus;
+extern Bool initStatusTracking();
+extern Bool updatePlayerStatus(int);
+extern Bool sendNewClientStatus(int);
+extern void setFormattedTime(Bool);
+extern Bool isFormattedTime();
+extern void setTimeUpdates(Bool);
+extern void setPlaylistUpdates(Bool);
+extern Bool freshenClientStatus();
+extern Bool sendPlayerStatus(playerStatusPtr);
+extern Bool sendPlaylistStatus(playerStatusPtr);
+extern Bool sendPlaylistTracks(playerStatusPtr);
+extern Bool sendTrackInfo(playerStatusPtr);
+extern Bool sendTrackStatus(playerStatusPtr);
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl_actions.c	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,444 @@
+/* inetctl_actions.c - This is where commands are interpreted and acted on */
+
+/* For simplciity of installation, the actual command dispatch table */
+/* is defined at the end of the source file.  This is done because other */
+/* wise we'd have to have lots of forward references.                    */
+
+/* I really wanted the COMMAND macro to not only define the function, but */
+/* also add the function to the command dispatch table, but I couldn't    */
+/* find a way to build up a list that would later be expanded.            */
+
+/* As a result, a new command requires two definitions.  The COMMMAND def */
+/* defines the name of the command and the function header for the code   */
+/* executed for that command.  The REGISTER macro, located at the end of  */
+/* the file, adds the command to the dispatch table.  The names in the    */
+/* COMMAND macros must match the names in the REGISTER macros or you'll   */
+/* either get undefined symbols or functions that are never invoked.      */
+
+#include "inetctl.h"
+
+/* Exported from inetctl_client.c */
+extern pid_t clientPID;
+extern int clientSocket;
+
+#define COMMAND(NAME) Bool cmd_##NAME(int argc, String argv[])
+#define REGISTER(NAME, PUBLIC) { #NAME, cmd_##NAME, PUBLIC }
+
+COMMAND(DEBUG) {
+  Bool debugFlag = TRUE;
+
+  /* If there is a parameter, process it */
+  if (argc >= 2) {
+    upString(argv[1]);
+    if (!strcmp(argv[1], "OFF")) debugFlag = FALSE;
+  }
+
+  debugMode = debugFlag;
+  return TRUE;
+}
+
+COMMAND(USER) {
+  return TRUE;
+}
+
+COMMAND(PLAY) {
+  xmms_remote_play(inetctl.xmms_session);  
+  return TRUE;
+}
+
+COMMAND(PLAY_FILE) {
+  int argIndex;
+  GList *theList = NULL;
+
+  /* Add all files to the list */
+  for (argIndex = 1; argIndex < argc; argIndex++) {
+    theList = g_list_append(theList, argv[argIndex]);
+  }
+
+  /* Insure there is at least one file */
+  if (theList == NULL) {
+    sendResponse("003", "TOO FEW ARGUMENTS");
+    return FALSE;
+  }
+
+  /* Clear the current list and add these file(s) to it */
+  xmms_remote_stop(inetctl.xmms_session);
+  xmms_remote_playlist_clear(inetctl.xmms_session);
+  xmms_remote_playlist_add(inetctl.xmms_session, theList);
+  xmms_remote_play(inetctl.xmms_session);
+
+  /* And we are off to the races */
+  return TRUE;
+}
+
+COMMAND(LOAD_FILE) {
+  int argIndex;
+  GList *theList = NULL;
+
+  /* Add all files to the list */
+  for (argIndex = 1; argIndex < argc; argIndex++) {
+    theList = g_list_append(theList, argv[argIndex]);
+  }
+
+  /* Insure there is at least one file */
+  if (theList == NULL) {
+    sendResponse("003", "TOO FEW ARGUMENTS");
+    return FALSE;
+  }
+
+  /* Clear the current list and add these file(s) to it */
+  xmms_remote_stop(inetctl.xmms_session);
+  xmms_remote_playlist_clear(inetctl.xmms_session);
+  xmms_remote_playlist_add(inetctl.xmms_session, theList);
+
+  /* And we are off to the races */
+  return TRUE;
+}
+
+COMMAND(CLEAR_PLAYLIST) {
+  int trackIndex;
+
+  /* Stop the player (if playing) */
+  xmms_remote_stop(inetctl.xmms_session);
+
+  /* Clear the current list and add these file(s) to it */
+  for (trackIndex = lastPlayerStatus.totalTracksInPlaylist - 1; trackIndex >= 0; trackIndex--) {
+    xmms_remote_playlist_delete(inetctl.xmms_session, trackIndex);
+  }
+
+  return TRUE;
+} 
+
+COMMAND(APPEND_FILE) {
+  int argIndex;
+  GList *theList = NULL;
+
+  /* Add all files to the list */
+  for (argIndex = 1; argIndex < argc; argIndex++) {
+    theList = g_list_append(theList, argv[argIndex]);
+  }
+
+  /* Insure there is at least one file */
+  if (theList == NULL) {
+    sendResponse("003", "TOO FEW ARGUMENTS");
+    return FALSE;
+  }
+
+  /* Append this to the current list */
+  xmms_remote_playlist_add(inetctl.xmms_session, theList);
+  return TRUE;
+}
+
+COMMAND(REMOVE_TRACK) {
+  int theTrackNum;
+  String theEndChar;
+
+  /* Convert integer to number */
+  theTrackNum = strtol(argv[1], &theEndChar, 10);
+  if ((*theEndChar != '\0') 
+      || (theEndChar == argv[1]) 
+      || (theTrackNum < 0) 
+      || (theTrackNum >= lastPlayerStatus.totalTracksInPlaylist)) {
+    sendResponse("004", "INVALID ARGUMENT");
+    return FALSE;
+  }
+
+  xmms_remote_playlist_delete(inetctl.xmms_session, theTrackNum);
+  return TRUE;
+}
+
+COMMAND(PAUSE) {
+  if (xmms_remote_is_playing(inetctl.xmms_session) 
+      && !xmms_remote_is_paused(inetctl.xmms_session)) {
+    xmms_remote_pause(inetctl.xmms_session);
+  }
+
+  return TRUE;
+}
+
+COMMAND(RESUME) {
+  if (xmms_remote_is_playing(inetctl.xmms_session) 
+      && xmms_remote_is_paused(inetctl.xmms_session)) {
+    xmms_remote_pause(inetctl.xmms_session);
+  }
+
+  return TRUE;
+}
+
+COMMAND(STOP) {
+  xmms_remote_stop(inetctl.xmms_session);
+  return TRUE;
+}
+
+COMMAND(NEXT_TRACK) {
+  int theRepeatCount = 1, theCount = 0;
+  String theEndChar;
+
+  /* Convert integer to number */
+  if (argc == 2) {
+    theRepeatCount = strtol(argv[1], &theEndChar, 10);
+    if ((*theEndChar != '\0') 
+        || (theEndChar == argv[1]) 
+        || (theRepeatCount < 1)) {
+      sendResponse("004", "INVALID ARGUMENT");
+      return FALSE;
+    }
+  }
+
+  for (theCount = 0; theCount < theRepeatCount; theCount++) 
+    xmms_remote_playlist_next(inetctl.xmms_session);
+
+  return TRUE;
+}
+
+COMMAND(PREV_TRACK) {
+  int theRepeatCount = 1, theCount = 0;
+  String theEndChar;
+
+  /* Convert integer to number */
+  if (argc == 2) {
+    theRepeatCount = strtol(argv[1], &theEndChar, 10);
+    if ((*theEndChar != '\0') 
+        || (theEndChar == argv[1]) 
+        || (theRepeatCount < 1)) {
+      sendResponse("004", "INVALID ARGUMENT");
+      return FALSE;
+    }
+  }
+
+  for (theCount = 0; theCount < theRepeatCount; theCount++) 
+    xmms_remote_playlist_prev(inetctl.xmms_session);
+
+  return TRUE;
+}
+
+COMMAND(JUMP_TO_TRACK) {
+  int theTrackNum;
+  String theEndChar;
+
+  /* Convert integer to number */
+  theTrackNum = strtol(argv[1], &theEndChar, 10);
+  if ((*theEndChar != '\0') 
+      || (theEndChar == argv[1]) 
+      || (theTrackNum < 0) 
+      || (theTrackNum >= lastPlayerStatus.totalTracksInPlaylist)) {
+    sendResponse("004", "INVALID ARGUMENT");
+    return FALSE;
+  }
+
+  xmms_remote_set_playlist_pos(inetctl.xmms_session, theTrackNum);
+  return TRUE;
+}
+
+COMMAND(JUMP_TO_TIME) {
+  int theTimeIndex;
+  String theEndChar;
+
+  /* Convert integer to number */
+  theTimeIndex = strtol(argv[1], &theEndChar, 10);
+  if ((*theEndChar != '\0') 
+      || (theEndChar == argv[1]) 
+      || (theTimeIndex < 0) 
+      || (theTimeIndex >= lastPlayerStatus.curTrackLength)) {
+    sendResponse("004", "INVALID ARGUMENT");
+    return FALSE;
+  }
+
+  if (isFormattedTime())
+    xmms_remote_jump_to_time(inetctl.xmms_session, theTimeIndex * 1000);
+  else
+    xmms_remote_jump_to_time(inetctl.xmms_session, theTimeIndex);
+
+  return TRUE;
+}
+
+COMMAND(EXIT) {
+  endThisClient();
+  return FALSE;
+}
+
+COMMAND(QUIT) {
+  xmms_remote_quit(inetctl.xmms_session);
+  kill(serverPID, SIGTERM);
+  return FALSE;
+}
+
+COMMAND(REPEAT) {
+  Bool repeatMode = TRUE;
+
+  /* If there is a parameter, process it */
+  if (argc >= 2) {
+    upString(argv[1]);
+    if (!strcmp(argv[1], "OFF")) repeatMode = FALSE;
+  }
+
+  /* See if we need to change repeat mode */
+  if (xmms_remote_is_repeat(inetctl.xmms_session) == repeatMode) 
+    return TRUE;
+
+  /* Toggle repeat mode */
+  xmms_remote_toggle_repeat(inetctl.xmms_session);
+  return TRUE;
+}
+
+COMMAND(SHUFFLE) {
+  Bool shuffleMode = TRUE;
+
+  /* If there is a parameter, process it */
+  if (argc >= 2) {
+    upString(argv[1]);
+    if (!strcmp(argv[1], "OFF")) shuffleMode = FALSE;
+  }
+
+  /* See if we need to change shuffle mode */
+  if (xmms_remote_is_shuffle(inetctl.xmms_session) == shuffleMode) 
+    return TRUE;
+
+  /* Toggle shuffle mode */
+  xmms_remote_toggle_shuffle(inetctl.xmms_session);
+  return TRUE;
+}
+
+COMMAND(VOLUME) {
+  int newVolume;
+  String endChar;
+
+  /* There must be an additional parameter */
+  if (argc != 2) {
+    sendResponse("003", "TOO FEW ARGUMENTS");
+    return FALSE;
+  }
+
+  /* Convert to volume */
+  trimString(argv[1]);
+  newVolume = strtol(argv[1], &endChar, 10);
+  if ((endChar == argv[1]) || (*endChar != '\0') || (newVolume < 0) || (newVolume > 100)) {
+    sendResponse("004", "INVALID ARGUMENT");
+    return FALSE;
+  }
+
+  /* Install new volume */
+  xmms_remote_set_main_volume(inetctl.xmms_session, newVolume);
+  return TRUE;
+}
+
+COMMAND(HIDE_PLAYER) {
+  /* Set Players Visibility */
+  xmms_remote_pl_win_toggle(inetctl.xmms_session, FALSE);
+  xmms_remote_eq_win_toggle(inetctl.xmms_session, FALSE);
+  xmms_remote_main_win_toggle(inetctl.xmms_session, FALSE);
+  return TRUE;
+}
+
+COMMAND(TIME_UPDATES) {
+  Bool timeUpdates = TRUE;
+
+  /* If there is a parameter, process it */
+  if (argc >= 2) {
+    upString(argv[1]);
+    if (!strcmp(argv[1], "OFF")) timeUpdates = FALSE;
+  }
+
+  /* See if we need to change flag */
+  setTimeUpdates(timeUpdates);
+  return TRUE;
+}
+
+COMMAND(PLAYLIST_UPDATES) {
+  Bool playlistUpdates = TRUE;
+
+  /* If there is a parameter, process it */
+  if (argc >= 2) {
+    upString(argv[1]);
+    if (!strcmp(argv[1], "OFF")) playlistUpdates = FALSE;
+  }
+
+  /* See if we need to change flag */
+  setPlaylistUpdates(playlistUpdates);
+  return TRUE;
+}
+
+
+COMMAND(FORMATTED_TIME) {
+  Bool formattedTime = TRUE;
+
+  /* If there is a parameter, process it */
+  if (argc >= 2) {
+    upString(argv[1]);
+    if (!strcmp(argv[1], "OFF")) formattedTime = FALSE;
+  }
+
+  /* See if we need to change flag */
+  writeDebug("Setting formatted time to %d\n", formattedTime);
+  setFormattedTime(formattedTime);
+  return TRUE;
+}
+
+COMMAND(REQUEST) {
+  /* There must be an additional parameter */
+  if (argc != 2) {
+    sendResponse("003", "TOO FEW ARGUMENTS");
+    return FALSE;
+  }
+
+  /* Upcase our option */
+  upString(argv[1]);
+
+  /* Freshen the clients stats */
+  freshenClientStatus();
+
+  /* Dispatch it */
+  if (!strcmp(argv[1], "STATUS")) 
+    return sendPlayerStatus(NULL);
+  else if (!strcmp(argv[1], "TRACK"))
+    return sendTrackInfo(NULL);
+  else if (!strcmp(argv[1], "PLAYLIST"))
+    return sendPlaylistStatus(NULL);
+  else if (!strcmp(argv[1], "POSITION"))
+    return sendTrackStatus(NULL);
+  else if (!strcmp(argv[1], "ALL")) {
+    sendPlayerStatus(NULL);
+    sendTrackInfo(NULL);
+    sendPlaylistStatus(NULL);
+    sendTrackStatus(NULL);
+  } else if (!strcmp(argv[1], "PLAYLIST_TRACKS"))
+    return sendPlaylistTracks(NULL);
+  else {
+    sendResponse("004", "UNRECOGNIZED ARGUMENT");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Build command table */
+cmdTable commandTable[] = {
+  REGISTER(DEBUG, FALSE),
+  REGISTER(USER, TRUE),
+  REGISTER(PLAY, FALSE),
+  REGISTER(PLAY_FILE, FALSE),
+  REGISTER(LOAD_FILE, FALSE),
+  REGISTER(APPEND_FILE, FALSE),
+  REGISTER(REMOVE_TRACK, FALSE),
+  REGISTER(CLEAR_PLAYLIST, FALSE),
+  REGISTER(PAUSE, FALSE),
+  REGISTER(RESUME, FALSE),
+  REGISTER(NEXT_TRACK, FALSE),
+  REGISTER(PREV_TRACK, FALSE),
+  REGISTER(JUMP_TO_TRACK, FALSE),
+  REGISTER(JUMP_TO_TIME, FALSE),
+  REGISTER(STOP, FALSE),
+  REGISTER(VOLUME, FALSE),
+  REGISTER(SHUFFLE, FALSE),
+  REGISTER(REPEAT, FALSE),
+  REGISTER(HIDE_PLAYER, FALSE),
+  REGISTER(TIME_UPDATES, FALSE),
+  REGISTER(FORMATTED_TIME, FALSE),
+  REGISTER(PLAYLIST_UPDATES, FALSE),
+  REGISTER(REQUEST, FALSE),
+  REGISTER(EXIT, TRUE),
+  REGISTER(QUIT, FALSE),
+  { "", NULL }  /* This must be the LAST line of the table */
+};
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl_client.c	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,227 @@
+/* inetctl_client.c - client connection handler */
+
+/* All code in here is related only to the client processing */
+/* done for a particular client session.  Overall client     */
+/* management is done in inetctl.c                            */
+
+/* NOTE: All code running in this module is in the process   */
+/* space of the child servicing a particular connection.  It */
+/* does not have access to data in inetctl.                   */
+
+#include "inetctl.h"
+
+/* Current client socket (for client handler) */
+int clientSocket = -1;
+pid_t clientPID = -1;
+
+/* Buffers */
+#define TEXT_BUFFER_SIZE 1024
+#define READ_BUFFER_SIZE 32768
+char textBuffer[TEXT_BUFFER_SIZE];
+char readBuffer[READ_BUFFER_SIZE];
+int readBufferLength = 0;
+int readBufferPtr = 0;
+
+/* Shuts us down.  We don't return */
+void endThisClient() {
+  if (clientSocket != -1) {
+    close(clientSocket);
+  }
+
+  kill(clientPID, SIGKILL);
+}
+
+/* Read in next block of available bytes from client.  If there */
+/* are no available bytes, block until there are.               */
+int readBytesFromClient() {
+  int errval, bytesRead;
+
+  struct pollfd clientWait;
+  clientWait.fd = clientSocket;
+  clientWait.events = POLLIN;
+
+  /* sleep until events */
+  SYSCALL(errval = poll(&clientWait, 1, -1));
+  if (errval <= 0) return errval;
+    
+  /* Attempt to read as many bytes as are available */
+  SYSCALL(bytesRead = read(clientSocket, readBuffer, READ_BUFFER_SIZE));
+  switch(bytesRead) {
+  case 0:
+    writeDebug("Client closed connection\n");
+    endThisClient();
+    break;
+
+  case -1:
+    writeDebug("Error when reading data from client - %s (%d)\n", strerror(errno), errno);
+    return 0;
+    break;
+
+  default:
+    writeDebug("Read %d bytes from client\n", bytesRead);
+    break;
+  }
+
+  /* Set # of bytes available and last byte used */
+  readBufferLength = bytesRead;
+  readBufferPtr = 0;
+  return bytesRead;
+}
+
+/* Read from client until we reach an EOL or an error.  If */
+/* a line was read, return # of bytes (not including the   */
+/* terminating null) in string.  If there is an error,     */
+/* return -1.  bufferSize is the Max # of chars to write   */
+/* into the passed client buffer                           */
+int readStringFromClient(String clientBuffer, int bufferSize) {
+  int charPtr = 0;
+
+  for (;;) {
+    /* See if we need to fetch more bytes from the client */
+    if (readBufferPtr == readBufferLength) {
+      if (readBytesFromClient() <= 0) return -1;
+    }
+
+    /* See if this byte should be copied */
+    if (readBuffer[readBufferPtr] == '\015') {
+      readBufferPtr++;
+      continue;
+    }
+
+    /* See if this byte terminates the string */
+    if (readBuffer[readBufferPtr] == '\012') {
+      readBufferPtr++;
+      clientBuffer[charPtr] = '\0';
+      return charPtr;
+    }
+
+    /* Copy over the character */
+    clientBuffer[charPtr++] = readBuffer[readBufferPtr++];
+    if (charPtr == bufferSize) return -2;
+  }
+
+  /* Should never happen */
+  return -3;
+}
+
+/* Writes the passed # of bytes to client.  if there is an */
+/* error, FALSE is returned.                               */
+Bool writeBytes(void *buffer, int byteCount) {
+  int bytesWritten;
+
+  /* Attempt to write passed data to client */
+  SYSCALL(bytesWritten = write(clientSocket, buffer, byteCount));
+  if (bytesWritten != byteCount) return FALSE;
+  return TRUE;
+}
+
+/* Write the passed null-terminated string */
+Bool writeText(String theFormat, ...) {
+  int bytesToWrite;
+  va_list theParms;
+
+  /* Format the message */
+  va_start(theParms, theFormat);
+  bytesToWrite = vsnprintf(textBuffer, TEXT_BUFFER_SIZE, theFormat, theParms);
+  va_end(theParms);
+
+  /* See if there was an error */
+  if (bytesToWrite <= 0) return FALSE;
+
+  /* Format and print message */
+  return writeBytes(textBuffer, bytesToWrite);
+}
+
+/* Send a response code/message to the client */
+Bool sendResponse(String theResponseCode, String theResponse, ...) {
+  int bytesToWrite;
+  va_list theParms;
+ 
+  /* Insure the response code is valid */
+  if (theResponseCode == NULL) return FALSE;
+  if (strlen(theResponseCode) != 3) return FALSE;
+
+  /* Install the response code */
+  strcpy(textBuffer, theResponseCode);
+  strcat(textBuffer, " ");
+
+  /* Format the message */
+  va_start(theParms, theResponse);
+  bytesToWrite = vsnprintf(&textBuffer[4], TEXT_BUFFER_SIZE - 6, theResponse, theParms);
+  va_end(theParms);
+
+  /* See if there was an error */
+  if (bytesToWrite <= 0) return FALSE;
+
+  /* Tack on standard line terminators */
+  strcat(textBuffer, "\r\n");
+
+  /* Format and print message */
+  return writeBytes(textBuffer, bytesToWrite + 6);
+}  
+
+/* Write initial client welcome message */
+Bool sendWelcome() {
+  char hostName[256];
+
+  /* Get host name */
+  if (gethostname(hostName, 256) != 0) 
+    strcpy(hostName, "unknown_host");
+
+  /* Write header out */
+  return sendResponse("000", "%s XMMS_INETCTL server (Version %s) ready.", hostName, INETCTLVERSION);
+}
+
+/* Read in client requests and dispatch as possible */
+void processClientRequests(pid_t theClientPID, int theClientSocket) {
+  int errval = 0;
+  char commandBuffer[1024];
+  int bytesRead;
+  struct pollfd clientWait;
+
+  /* Install specifics for this client */
+  clientPID = theClientPID;
+  clientSocket = theClientSocket;
+
+  /* Setup for polling */
+  clientWait.fd = clientSocket;
+  clientWait.events = POLLIN;
+  
+  /* Welcome the client */
+  if (!sendWelcome()) {
+    writeError("Client appears to be dead - stopping client handler\n");
+    endThisClient();
+  }
+
+  /* Send current status */
+  if (!sendNewClientStatus(theClientSocket)) {
+    writeError("Client appears to be dead - stopping client handler\n");
+    endThisClient();
+  }
+
+  for(;;) {
+    /* Wait for something to come in from the client */
+    if ((errval = poll(&clientWait, 1, 300)) < 0) {
+      writeDebug("Error polling client %s (%d) - terminating\n", strerror(errno), errno);
+      break;
+    }
+
+    /* If this is a timeout, update stats on the clients connection */
+    if (errval == 0) {
+      updatePlayerStatus(clientSocket);
+      continue;
+    }
+
+    /* Read a string from the client */
+    if ((bytesRead = readStringFromClient(commandBuffer, 1024)) == -1) {
+      writeError("Error trying to read from client - closing connection\n");
+      return;
+    }
+
+    /* Just in case we are watching */
+    writeDebug("Read command from client [%s]\n", commandBuffer);
+
+    /* Attempt to dispatch this command */
+    dispatchCommand(commandBuffer);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl_command.c	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,265 @@
+/* inetctl_command.c - Command parser and dispatcher */
+
+/* Include commons */
+#include "inetctl.h"
+
+/* Private dynamic parse buffer */
+String parseBuffer = NULL;
+int parseBufferSize = 0;
+
+/* Is the user authenticated? */
+String userName = NULL;
+Bool userAuthenticated = FALSE;
+
+/* Private parameter store */
+#define MAX_PARMS 64
+String cmdParms[MAX_PARMS];
+int cmdParmCount = -1;
+
+/* upString will convert a string to upper case */
+void upString(String target) {
+  int charPtr = 0;
+
+  /* Convert to upper case */
+  while (target[charPtr] != '\0') {
+    /* See if we should convert */
+    if ((target[charPtr] >= 'a') && (target[charPtr] <= 'z'))
+      target[charPtr] -= ('a' - 'A');
+    charPtr++;
+  }
+}
+
+/* trimString will trim leading/trailing spaces/tabs/new-lines */
+/* from a passed string.                                       */
+void trimString(String theText) {
+  int srcPtr = 0, destPtr = 0;
+
+  /* Skip empty strings */
+  if (theText[0] == '\0') return;
+
+  /* Strip trailing characters */
+  destPtr = strlen(theText) - 1;
+  while (isspace(theText[destPtr])) theText[destPtr--] = '\0';
+
+  /* If the front part of string is okay, we're done */
+  if (!isspace(theText[0])) return;
+
+  /* Process each character */
+  destPtr = 0;
+  while (theText[srcPtr] != '\0') {
+    /* See if we should skip this */
+    if ((destPtr == 0) && isspace(theText[srcPtr]))
+      srcPtr++;
+    else
+      theText[destPtr++] = theText[srcPtr++];
+  }
+
+  /* Set terminator in place */
+  theText[destPtr] = '\0';
+}
+
+/* Abrev will return TRUE if the passed string is a valid abreviation */
+/* of the passed string.  You pass the string to test as 'source', the*/
+/* full command name as 'command' and the minimum # of chars that can */
+/* be matches as 'minchars'.  If 'minchars' is passed as 0, then there*/
+/* can be no abreviation.  The command must match exactly. If the     */
+/* command is longer than the min # of chars, all chars after that    */
+/* are checked as well.  We stop when we hit the end of the line or   */
+/* the chars don't match.                                             */
+Bool abrevCheck(String source, String command, int minchars) {
+  int maxchar = strlen(source);
+  int maxmatch = strlen(command);
+  int charptr;
+
+  /* Set the minimum # of chars if none specified */
+  if (!minchars) minchars = maxmatch;
+
+  /* Make sure we have at least as many characters as we need */
+  /* and no more than we expected to handle                   */
+  if ((maxchar < minchars) || (maxchar > maxmatch)) return FALSE;
+
+  /* Test each character */
+  for (charptr = 0; charptr < maxchar; charptr++)
+    /* If we fail to match, exit in shame */
+    if (source[charptr] != command[charptr]) return FALSE;
+
+  /* All our chars matched */
+  return TRUE;
+}
+
+/* ParseArgs will take a command and break it into a number of    */
+/* arguments/tokens.  'command' is the command to process.  'argv'*/
+/* is the returned token list array.  argc will be returned with  */
+/* the # of args parsed.  'maxargs' is the max # of args that we  */
+/* can deal with.  If there are too many arguments or quoting is  */
+/* goofed up or the command line is screwed up in any way, FALSE  */
+/* is returned.  Otherwise, TRUE.                                 */
+Bool parseArgs(String command, String argv[], int *argc, int maxargs) {
+  int  cmdLength = strlen(command);
+  char quoteChar = '\0';
+  Bool quoteMode = FALSE;
+  Bool inParm = FALSE;
+  Bool quoteNextChar = FALSE;
+  String destChar;
+  int  charPtr;
+
+  /* Make sure we have enough room for a copy */
+  if (parseBuffer == NULL) {
+    parseBufferSize = cmdLength + 1;
+    parseBuffer = (String) malloc(parseBufferSize);
+    writeDebug("Allocated %d bytes for parse buffer @ %p\n", parseBufferSize, parseBuffer);
+  } else if (cmdLength + 1 > parseBufferSize) {
+    parseBufferSize = cmdLength + 1;
+    parseBuffer = realloc(parseBuffer, parseBufferSize);
+    writeDebug("Expanded parse buffer to %d bytes @ %p\n", parseBufferSize, parseBuffer);
+  }
+
+  /* Init for new parsing */
+  *argc = 0;
+  destChar = parseBuffer;
+
+  /* Run through each char, until we hit EOL */
+  for (charPtr = 0; command[charPtr] != '\0'; charPtr++) {
+    /* See if we are dealing with a quoted character */
+    if (!quoteNextChar) {
+      /* Copy everything in quote mode (or notice we're done quoting) */
+      if (quoteMode) {
+	/* Check for End of Quote mode */
+	if (command[charPtr] == quoteChar) {
+	  *destChar++ = '\0';
+	  quoteMode = FALSE;
+	  inParm = FALSE;
+	  continue;
+	}
+
+	/* Handle character quotes */
+	if (command[charPtr] == '\\') {
+	  quoteNextChar = TRUE;
+	  continue;
+	}
+
+	/* Anything else is copied verbatim */
+	*destChar++ = command[charPtr];
+	continue;
+      }
+      
+      /* See if we have a 'start of quote' flag */
+      if (((command[charPtr] == '"') || (command[charPtr] == '\'')) && (!inParm)) {
+	/* Set quote mode */
+	quoteChar = command[charPtr];
+	quoteMode = TRUE;
+
+	/* Start a new parm */
+	if (*argc == maxargs) return FALSE;
+	argv[*argc] = destChar;
+	(*argc)++;
+	inParm = TRUE;
+	
+	continue;
+      }
+      
+      /* Handle spaces as terminators and/or junk */
+      if (isspace(command[charPtr])) {
+	/* If we were in a parm, then end the parm here */
+	if (inParm) {
+	  *destChar++ = '\0';
+	  inParm = FALSE;
+	}
+
+	continue;
+      }
+    }
+
+    /* If we weren't in a parm on a regular char, start one */
+    if (!inParm) {
+      inParm = TRUE;
+
+      /* Start a new parm */
+      if (*argc == maxargs) return FALSE;
+      argv[*argc] = destChar;
+      (*argc)++;
+    }
+    
+    /* Handle the character quoting character */
+    if ((command[charPtr] == '\\' && !quoteNextChar)) {
+      quoteNextChar = TRUE;
+      continue;
+    }
+
+    /* Copy a character over */
+    *destChar++ = command[charPtr];
+    quoteNextChar = FALSE;
+  }
+
+  /* If we were in a parm, wrap things up */
+  if (inParm) {
+    *destChar++ = '\0';
+    inParm = FALSE;
+  }
+
+  /* At EOL, make sure we are not in quote mode */
+  return (!quoteMode);
+}
+
+/* Take the passed command and parse it.  Once parsed,     */
+/* attempt to dispatch it to the appropriate handler.      */
+/* If the command is processed correctly, TRUE is returned */
+/* and if there is an error (command not recognized, error */
+/* executing the command, etc).                            */
+Bool dispatchCommand(String theCommand) {
+  int commandIndex;
+
+  /* Make sure we're not doomed */
+  if (theCommand == NULL) return FALSE;
+
+  /* Trim the string & exit on empty string or comment */
+  trimString(theCommand);
+  if (theCommand[0] == '\0') return TRUE;
+  if (theCommand[0] == '#') return TRUE;
+  writeDebug("Begining dispatch on command [%s]\n", theCommand);
+
+  /* Parse the command into parts */
+  if (!parseArgs(theCommand, cmdParms, &cmdParmCount, MAX_PARMS)) {
+    sendResponse("002", "COMMAND FORMAT ERROR");
+    return FALSE;
+  }
+
+  /* Make sure the command isn't missing */
+  if (cmdParmCount == 0) {
+    sendResponse("002", "MISSING COMMAND");
+    return FALSE;
+  }
+
+  /* Upcase the command and make sure we have one */
+  trimString(cmdParms[0]);
+  upString(cmdParms[0]);
+  if (cmdParms[0][0] == '\0') {
+    sendResponse("002", "MISSING COMMAND");
+    return FALSE;
+  }
+
+  /* Try to find the command */
+  for (commandIndex = 0; commandTable[commandIndex].commandFunc != NULL; commandIndex++) {
+    /* If command doesn't match, skip to next */
+    if (strcmp(cmdParms[0], commandTable[commandIndex].commandName)) continue;
+
+    /* If they are not authenticated and this command isn't always allowed, stop it */
+    if (!userAuthenticated && !commandTable[commandIndex].alwaysAllowed) {
+      writeDebug("Attempt to execute %d before authentication - rejected\n", cmdParms[0]);
+      sendResponse("001", "NOT AUTHENTICATED");
+      return FALSE;
+    }
+
+    /* Dispatch to this command */
+    writeDebug("Matched command %s - dispatching\n", commandTable[commandIndex].commandName);
+    if (commandTable[commandIndex].commandFunc(cmdParmCount, cmdParms)) {
+      sendResponse("005", theCommand);
+      return TRUE;
+    } else
+      return FALSE;
+  }
+
+  /* If we get here, there was no match */
+  sendResponse("002", "UNKNOWN COMMAND");
+  return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl_config.c	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,141 @@
+/* inetctl_config.c - Configuration services */
+
+#include "inetctl.h"
+
+/* U/I defines */
+GtkWidget *conf_dialog;
+GtkObject *hour_w, *minute_w, *fadespeed_o;
+GtkWidget *port_field;
+gint hour, minute, fadespeed;
+gchar *dummy;
+
+
+void alert(gchar *message) {
+   GtkWidget *dialog, *label, *okay_button;
+   
+   /* Create the widgets */
+   dialog = gtk_dialog_new();
+   label = gtk_label_new (message);
+   okay_button = gtk_button_new_with_label("Okay");
+   
+   /* Ensure that the dialog box is destroyed when the user clicks ok. */
+   gtk_signal_connect_object (GTK_OBJECT (okay_button), "clicked",
+                              GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT(dialog));
+   gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->action_area),
+                      okay_button);
+   
+   /* Add the label, and show everything we've added to the dialog. */
+   gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label);
+   gtk_widget_show_all (dialog);
+}
+
+/* Write current configuration dialog values to config file */
+void write_config() {
+  ConfigDb *db = bmp_cfg_db_open();
+
+  /* Extract current values from U/I */
+  listenPort = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(port_field));
+
+  /* Write values to config and close it */
+  bmp_cfg_db_set_int(db, "inetctl", "port", listenPort);
+  bmp_cfg_db_close(db);
+}
+
+/* Read values from config file and install.  If no config file */
+/* then install sensible default values                         */
+void read_config() {
+  ConfigDb *db = bmp_cfg_db_open();
+  bmp_cfg_db_get_int(db, "inetctl", "port", &listenPort);
+  bmp_cfg_db_close(db);
+}
+
+void inetctl_config_ok (GtkWidget * wid, gpointer data) {
+  write_config();
+  gtk_widget_destroy (conf_dialog);
+  conf_dialog = NULL;
+  return;
+}
+
+void inetctl_config () {
+  GtkWidget *ok_button, *apply_button, *cancel_button;
+  GtkWidget *timebox, *port_w;
+  GtkWidget *bigbox, *buttonbox;
+  GtkWidget *timeframe;
+
+
+  if (conf_dialog) return;
+
+  conf_dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  
+  gtk_window_set_title (GTK_WINDOW (conf_dialog), ("iNetControl Configuration"));
+  gtk_window_set_policy (GTK_WINDOW (conf_dialog), FALSE, FALSE, FALSE);
+  gtk_window_set_position (GTK_WINDOW (conf_dialog), GTK_WIN_POS_MOUSE);
+  
+  gtk_container_set_border_width (GTK_CONTAINER (conf_dialog), 5);
+  
+  gtk_signal_connect (GTK_OBJECT (conf_dialog), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroyed),
+		      &conf_dialog);
+  
+  bigbox = gtk_vbox_new (FALSE, 5);
+  gtk_container_add (GTK_CONTAINER (GTK_WINDOW (conf_dialog)), bigbox);
+  
+  timeframe = gtk_frame_new ("Server options:");
+  gtk_container_add (GTK_CONTAINER (bigbox), timeframe);
+  
+  timebox = gtk_hbox_new (FALSE, 5);
+  gtk_container_set_border_width (GTK_CONTAINER (timebox), 5);
+  gtk_container_add (GTK_CONTAINER (timeframe), timebox);
+  
+  port_w = (GtkWidget *) gtk_adjustment_new (listenPort, 1, 256 * 256, 1, 1, 1);
+  port_field = gtk_spin_button_new (GTK_ADJUSTMENT (port_w), 1.0, 0);
+  
+  gtk_box_pack_start (GTK_BOX (timebox), gtk_label_new ("Port: "), FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (timebox), port_field, TRUE, TRUE, 0);
+  
+  buttonbox = gtk_hbutton_box_new ();
+  gtk_button_box_set_layout (GTK_BUTTON_BOX (buttonbox), GTK_BUTTONBOX_END);
+  gtk_button_box_set_spacing (GTK_BUTTON_BOX (buttonbox), 5);
+  gtk_box_pack_start (GTK_BOX (bigbox), buttonbox, FALSE, FALSE, 0);
+  
+  ok_button = gtk_button_new_with_label ("Ok");
+  apply_button = gtk_button_new_with_label ("Apply");
+  cancel_button = gtk_button_new_with_label ("Cancel");
+  
+  gtk_signal_connect_object (GTK_OBJECT (cancel_button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
+			     (gpointer) conf_dialog);
+  
+  gtk_signal_connect_object (GTK_OBJECT (apply_button), "clicked", GTK_SIGNAL_FUNC (write_config), NULL);
+  gtk_signal_connect_object (GTK_OBJECT (ok_button), "clicked", GTK_SIGNAL_FUNC (inetctl_config_ok), NULL);
+  
+  GTK_WIDGET_SET_FLAGS (ok_button, GTK_CAN_DEFAULT);
+  GTK_WIDGET_SET_FLAGS (cancel_button, GTK_CAN_DEFAULT);
+  GTK_WIDGET_SET_FLAGS (apply_button, GTK_CAN_DEFAULT);
+  
+  gtk_box_pack_start (GTK_BOX (buttonbox), ok_button, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (buttonbox), cancel_button, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (buttonbox), apply_button, TRUE, TRUE, 0);
+  
+  gtk_widget_show_all (conf_dialog);
+}
+
+void inetctl_about () {
+  GtkWindow *about_box = (GtkWindow *) gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  GtkVBox *container = (GtkVBox *) gtk_vbox_new(FALSE, 10);
+  GtkLabel *copyright = (GtkLabel *) gtk_label_new("(c) 2001 Gerry Duprey");
+  GtkLabel *email = (GtkLabel *) gtk_label_new("gerry@cdp1802.org");
+  GtkLabel *url = (GtkLabel *) gtk_label_new("http://www.cdp1802.org");
+  
+  gtk_container_set_border_width(GTK_CONTAINER(container), 10);
+  gtk_window_set_title(about_box, "iNetControl");
+  
+  gtk_container_add(GTK_CONTAINER(container), GTK_WIDGET(copyright));
+  gtk_container_add(GTK_CONTAINER(container), GTK_WIDGET(email));
+  gtk_container_add(GTK_CONTAINER(container), GTK_WIDGET(url));
+  gtk_container_add(GTK_CONTAINER(about_box), GTK_WIDGET(container));
+  
+  gtk_widget_show(GTK_WIDGET(copyright));
+  gtk_widget_show(GTK_WIDGET(container));
+  gtk_widget_show(GTK_WIDGET(url));
+  gtk_widget_show(GTK_WIDGET(email));
+  gtk_widget_show(GTK_WIDGET(about_box));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/inetctl/inetctl_status.c	Sun Feb 19 15:03:06 2006 -0800
@@ -0,0 +1,332 @@
+/* inetctl_status.c - status tracking and reporting module */
+
+#include "inetctl.h"
+
+/* Static "previous state" of player */
+playerStatus lastPlayerStatus = { FALSE, 0 };
+
+/* Option/controls */
+Bool formattedTime = TRUE;
+Bool timeStatusUpdates = TRUE;
+Bool playlistUpdates = FALSE;
+
+/* Status buffer, used for formatting status messages */
+#define STATUS_BUFF_MAX 1024
+char statusBuffer[STATUS_BUFF_MAX];
+
+/* Fill the passed player status structure with the players current status */
+Bool getPlayerStatus(playerStatusPtr theStatus) {
+  /* Playlist data */
+  theStatus->curTrackInPlaylist = xmms_remote_get_playlist_pos(inetctl.xmms_session);
+  theStatus->totalTracksInPlaylist = xmms_remote_get_playlist_length(inetctl.xmms_session);
+
+  /* Track data */
+  theStatus->curTrackLength = xmms_remote_get_playlist_time(inetctl.xmms_session, theStatus->curTrackInPlaylist);
+  theStatus->curTrackTime = xmms_remote_get_output_time(inetctl.xmms_session);
+  strcpy(theStatus->trackTitle, xmms_remote_get_playlist_title(inetctl.xmms_session, theStatus->curTrackInPlaylist));
+  strcpy(theStatus->trackFile, xmms_remote_get_playlist_file(inetctl.xmms_session, theStatus->curTrackInPlaylist));
+
+  /* Player data */
+  theStatus->playing = xmms_remote_is_playing(inetctl.xmms_session);
+  theStatus->paused = xmms_remote_is_paused(inetctl.xmms_session);
+  theStatus->shuffle = xmms_remote_is_shuffle(inetctl.xmms_session);
+  theStatus->repeat = xmms_remote_is_repeat(inetctl.xmms_session);
+  theStatus->volume = xmms_remote_get_main_volume(inetctl.xmms_session);
+  theStatus->balance = xmms_remote_get_balance(inetctl.xmms_session);
+
+  /* Mark valid and we are done */
+  theStatus->infoValid = TRUE;
+  return TRUE;
+}
+
+/* Set status of formatted times */
+void setFormattedTime(Bool doFormattedTime) {
+  formattedTime = doFormattedTime;
+}
+
+/* Return TRUE if doing formatted time */
+Bool isFormattedTime() {
+  return formattedTime;
+}
+
+/* Set status of time/progress updates */
+void setTimeUpdates(Bool doTimeUpdates) {
+  timeStatusUpdates = doTimeUpdates;
+}
+
+/* Set status of playlist updates */
+void setPlaylistUpdates(Bool doPlaylistUpdates) {
+  playlistUpdates = doPlaylistUpdates;
+}
+
+/* Write a quoted string out the the passed buffer */
+void writeQuotedString(String destBuffer, String theText) {
+  strcat(destBuffer, "\"");
+  strcat(destBuffer, theText);
+  strcat(destBuffer, "\"");
+}
+
+/* Format a number into a string */
+void writeNumberString(String destBuffer, int theNumber) {
+  sprintf(destBuffer, "%d", theNumber);
+}
+
+/* Format a passed # of seconds into a time */
+void writeTimeString(String destBuffer, int theTime) {
+  int hours, minutes, seconds;
+
+  /* Handle unformatted times */
+  if (!formattedTime) {
+    writeNumberString(destBuffer, theTime);
+    return;
+  }
+
+  /* Decode into "english" time */
+  theTime /= 1000;
+  hours = theTime / 3600;
+  minutes = (theTime % 3600) / 60;
+  seconds = theTime % 60;
+
+  /* Format hours, if any */
+  sprintf(destBuffer, "%d:%02d:%02d", hours, minutes, seconds);
+}
+
+/* Initialize status tracking */
+Bool initStatusTracking() {
+  return getPlayerStatus(&lastPlayerStatus);
+}
+
+/* Send track progress status */
+Bool sendTrackStatus(playerStatusPtr theStatus) {
+  /* Default the status, if needed */
+  if (theStatus == NULL) theStatus = &lastPlayerStatus;
+
+  /* Init the response buffer */
+  statusBuffer[0] = '\0';
+
+  /* Format the info */
+  writeTimeString(statusBuffer, theStatus->curTrackTime);
+  strcat(statusBuffer, " ");
+  writeTimeString(&statusBuffer[strlen(statusBuffer)], theStatus->curTrackLength);
+
+  /* Send Status to client */
+  return sendResponse("013", statusBuffer);
+}
+
+/* Send playlist info */
+Bool sendPlaylistStatus(playerStatusPtr theStatus) {
+  /* Default the status, if needed */
+  if (theStatus == NULL) theStatus = &lastPlayerStatus;
+
+  /* Init the response buffer */
+  statusBuffer[0] = '\0';
+
+  /* Format the info */
+  writeNumberString(statusBuffer, theStatus->curTrackInPlaylist);
+  strcat(statusBuffer, " ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], theStatus->totalTracksInPlaylist);
+
+  /* Send Status to client */
+  return sendResponse("012", statusBuffer);
+}
+
+/* Send current playlist info */
+Bool sendPlaylistTracks(playerStatusPtr theStatus) {
+  int playListIndex;
+
+  /* Default the status, if needed */
+  if (theStatus == NULL) theStatus = &lastPlayerStatus;
+
+  /* Send playlist header */
+  strcpy(statusBuffer, "START ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], theStatus->totalTracksInPlaylist);
+  strcat(statusBuffer, " ");
+  writeQuotedString(&statusBuffer[strlen(statusBuffer)], "Unknown");  /* Title */
+  strcat(statusBuffer, " ");\
+  writeQuotedString(&statusBuffer[strlen(statusBuffer)], "Unknown");  /* File path/name */
+  if (!sendResponse("014", statusBuffer)) return FALSE;
+
+  /* Iterate over all tracks in the playlist */
+  for (playListIndex = 0; playListIndex < theStatus->totalTracksInPlaylist; playListIndex++) {
+    /* Format this tracks info */
+    writeNumberString(statusBuffer, playListIndex);
+    strcat(statusBuffer, " ");
+    writeQuotedString(&statusBuffer[strlen(statusBuffer)], xmms_remote_get_playlist_title(inetctl.xmms_session, playListIndex));
+    strcat(statusBuffer, " ");
+    writeQuotedString(&statusBuffer[strlen(statusBuffer)], xmms_remote_get_playlist_file(inetctl.xmms_session, playListIndex));
+    strcat(statusBuffer, " ");
+    writeTimeString(&statusBuffer[strlen(statusBuffer)], xmms_remote_get_playlist_time(inetctl.xmms_session, playListIndex));
+    if (!sendResponse("014", statusBuffer)) return FALSE;
+  }
+
+  /* Write the terminator */
+  strcpy(statusBuffer, "END ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], theStatus->totalTracksInPlaylist);
+  return sendResponse("014", statusBuffer);
+}
+
+/* Send info on current track */
+Bool sendTrackInfo(playerStatusPtr theStatus) {
+  int bitRate, sampleFreq, numChannels;
+
+  /* Default the status, if needed */
+  if (theStatus == NULL) theStatus = &lastPlayerStatus;
+
+  /* Init the response buffer */
+  statusBuffer[0] = '\0';
+
+  /* Format track data */
+  writeQuotedString(statusBuffer, theStatus->trackTitle);
+  strcat(statusBuffer, " ");
+  writeQuotedString(&statusBuffer[strlen(statusBuffer)], theStatus->trackFile);
+  strcat(statusBuffer, " ");
+  writeTimeString(&statusBuffer[strlen(statusBuffer)], theStatus->curTrackLength);
+
+  /* Format track stats */
+  xmms_remote_get_info(inetctl.xmms_session, &bitRate, &sampleFreq, &numChannels);
+  strcat(statusBuffer, " ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], bitRate);
+  strcat(statusBuffer, " ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], sampleFreq);
+  strcat(statusBuffer, " ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], numChannels);
+
+  /* Send info to client */
+  return sendResponse("011", statusBuffer);
+}
+
+/* Format a player status message and dispatch it to the passed */
+/* client.  If the passed client is -1, then all clients will   */
+/* receive it.  If the passed status pointer is NULL, the last  */
+/* known players stats are used.                                */
+Bool sendPlayerStatus(playerStatusPtr theStatus) {
+  /* Default the status, if needed */
+  if (theStatus == NULL) theStatus = &lastPlayerStatus;
+
+  /* Init the response buffer */
+  statusBuffer[0] = '\0';
+
+  /* Put in player state */
+  if (theStatus->playing) {
+    if (theStatus->paused)
+      strcat(statusBuffer, "PAUSED");
+    else
+      strcat(statusBuffer, "PLAYING");
+  } else
+    strcat(statusBuffer, "STOPPED");
+
+  /* Put in volume */
+  strcat(statusBuffer, " ");
+  writeNumberString(&statusBuffer[strlen(statusBuffer)], theStatus->volume);
+
+  /* Put in shuffle mode flag */
+  if (theStatus->shuffle) 
+    strcat(statusBuffer, " SHUFFLE");
+  else
+    strcat(statusBuffer, " NORMAL");
+
+  /* Put in repeat mode flag */
+  if (theStatus->repeat)
+    strcat(statusBuffer, " REPEAT");
+  else
+    strcat(statusBuffer, " SINGLE");
+
+  /* Send Status to client */
+  return sendResponse("010", statusBuffer);
+}
+
+/* Send new client status messages */
+Bool sendNewClientStatus(int clientSocket) {
+  /* Set initial status */
+  initStatusTracking();
+
+  /* Now send each status */
+  if (!sendPlayerStatus(NULL)) return FALSE;
+  if (!sendPlaylistStatus(NULL)) return FALSE;
+  if (!sendTrackInfo(NULL)) return FALSE;
+  if (!sendTrackStatus(NULL)) return FALSE;
+
+  /* And we are done */
+  return TRUE;
+}
+
+/* Used by the clients to update their idea of the */
+/* players status                                  */
+Bool freshenClientStatus() {
+  return !getPlayerStatus(&lastPlayerStatus);
+}
+
+/* Check the player status.  If anything has changed since we */
+/* last checked it's status, we issue an appropriate status   */
+/* update to all connected clients                            */
+Bool updatePlayerStatus(int clientSocket) {
+  playerStatus curStatus;
+  Bool playerIsStopped = FALSE;
+  Bool statusChange = FALSE;
+  Bool playListChanged = FALSE;
+  Bool trackChanged = FALSE;
+
+  /* Get player status */
+  if (!getPlayerStatus(&curStatus)) return FALSE;
+
+  /* See if the player is stopped */
+  playerIsStopped = !curStatus.playing;
+
+  /* See if we should create a 010 - player status change */
+  if ((curStatus.playing != lastPlayerStatus.playing)
+      || (curStatus.paused != lastPlayerStatus.paused)
+      || (curStatus.volume != lastPlayerStatus.volume)
+      || (curStatus.balance != lastPlayerStatus.balance)
+      || (curStatus.repeat != lastPlayerStatus.repeat)
+      || (curStatus.shuffle != lastPlayerStatus.shuffle)) {
+    statusChange = TRUE;
+    writeDebug("Change in player status (010)\n");
+    sendPlayerStatus(&curStatus);
+  }
+
+  /* See if we should send 012 - playlist status change */
+  if (((curStatus.curTrackInPlaylist != lastPlayerStatus.curTrackInPlaylist)
+    || (curStatus.totalTracksInPlaylist != lastPlayerStatus.totalTracksInPlaylist))) {
+    statusChange = TRUE;
+    playListChanged = TRUE;
+    writeDebug("Change in playlist status (012)\n");
+    sendPlaylistStatus(&curStatus);
+
+    /* If actual playlist changed (vs just our position), send that */
+    if ((curStatus.totalTracksInPlaylist != lastPlayerStatus.totalTracksInPlaylist) && playlistUpdates) 
+      sendPlaylistTracks(&curStatus);
+  }
+
+  /* See if we should send a 011 - track info change */
+  if (!playerIsStopped 
+      && (playListChanged
+	  || (curStatus.curTrackLength != lastPlayerStatus.curTrackLength)
+	  || strcmp(curStatus.trackTitle, lastPlayerStatus.trackTitle)
+	  || strcmp(curStatus.trackFile, lastPlayerStatus.trackFile))) {
+    statusChange = TRUE;
+    trackChanged = TRUE;
+    writeDebug("Change in track info (011)\n");
+    sendTrackInfo(&curStatus);
+  }
+
+  /* See if we should send a 013 - track status updates */
+  if (!playerIsStopped
+      && timeStatusUpdates
+      && (playListChanged
+	  || trackChanged
+	  || (curStatus.curTrackLength != lastPlayerStatus.curTrackLength)
+	  || (curStatus.curTrackTime != lastPlayerStatus.curTrackTime))) {
+    statusChange = TRUE;
+    writeDebug("Change in track status (013)\n");
+    sendTrackStatus(&curStatus);
+  }
+
+  /* If there was a change, copy it over */
+  if (statusChange) {
+    memcpy(&lastPlayerStatus, &curStatus, sizeof(playerStatus));
+    writeDebug("Copied off current status for later\n");
+  }
+
+  /* And we are done */
+  return TRUE;
+}
--- a/configure.ac	Sun Feb 19 12:39:36 2006 -0800
+++ b/configure.ac	Sun Feb 19 15:03:06 2006 -0800
@@ -125,7 +125,7 @@
 
 INPUT_PLUGINS="cdaudio tonegen console sexypsf wav"
 EFFECT_PLUGINS="ladspa"
-GENERAL_PLUGINS="song_change"
+GENERAL_PLUGINS="song_change inetctl"
 VISUALIZATION_PLUGINS="blur_scope"
 
 dnl Option to change equalizer to the old XMMS one which only works with the mpg123
@@ -878,6 +878,7 @@
 	Plugins/General/Makefile
 	Plugins/General/song_change/Makefile
 	Plugins/General/lirc/Makefile
+	Plugins/General/inetctl/Makefile
 	Plugins/Effect/Makefile
 	Plugins/Effect/ladspa/Makefile
         po/Makefile.in
@@ -946,6 +947,7 @@
 echo "  General"
 echo "  -------"
 echo "  Song Change:                            yes"
+echo "  Internet Control:                       yes"
 echo "  LIRC:                                   $have_lirc"
 echo
 echo "  Effect"