Mercurial > audlegacy
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"