/*---------------------------------------------------------------------------------
Name               : music_player.c
Author             : Marvin Raaijmakers
Description        : Plugin for keyTouch that controls various music players.
Date of last change: 26-Jan-2007

    Copyright (C) 2007 Marvin Raaijmakers

    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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    You can contact me at: marvinr(at)users(dot)sf(dot)net
    (replace (at) by @ and (dot) by .)
-----------------------------------------------------------------------------------*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>
#include <string.h>

#include <plugin.h>

#define NUM_PLAYERS            5
#define MAX_STAT_FILENAME_SIZE 20
#define MAX_PROCESS_NAME_SIZE  20

void play_pause (KTPreferences *preferences);
void stop (KTPreferences *preferences);
void next (KTPreferences *preferences);
void previous (KTPreferences *preferences);

typedef enum {
	PCMD_PLAY_PAUSE,
	PCMD_STOP,
	PCMD_PREVIOUS,
	PCMD_NEXT
} PLAYER_CMD;


static void run (char *command);
static char str_only_digits (char *str);
static void do_player_command (PLAYER_CMD cmd);
static void control_rhythmbox (char *rhythmbox_command, char *rhythmbox_client_command);

static void ctrl_xmms (PLAYER_CMD cmd);
static void ctrl_amarok (PLAYER_CMD cmd);
static void ctrl_rhythmbox (PLAYER_CMD cmd);
static void ctrl_audacious (PLAYER_CMD cmd);
static void ctrl_mpd (PLAYER_CMD cmd);


typedef struct {
	const char *name; /* The process name of the player */
	void (*player_ctrl) (PLAYER_CMD); /* The function that controls the player */
} PLAYER;

const PLAYER player[NUM_PLAYERS] = {
	{"xmms", ctrl_xmms},
	{"amarokapp", ctrl_amarok},
	{"rhythmbox", ctrl_rhythmbox},
	{"audacious", ctrl_audacious},
	{"mpd", ctrl_mpd}
};


KeytouchPlugin plugin_struct = {
	{"Music player", "Marvin Raaijmakers", "GPL 2", "1.0",
	 "This plugin controls the following music players:\n"
	 "- XMMS\n"
	 "- Amarok\n"
	 "- Rhythmbox\n"
	 "- Audacious\n"
	 "- MPD"},
	"music_player.so",
	4,
	{{"Play/Pause", KTPluginFunctionType_Function, {.function = play_pause}},
	 {"Stop",       KTPluginFunctionType_Function, {.function = stop}},
	 {"Previous",   KTPluginFunctionType_Function, {.function = previous}},
	 {"Next",       KTPluginFunctionType_Function, {.function = next}},
	}
};


void
run (char *command)
{
	if (fork() == 0)
	{
		execlp ("sh", "sh", "-c", command, NULL);
	}
}



void
ctrl_xmms (PLAYER_CMD cmd)
{
	switch (cmd)
	{
		case PCMD_PLAY_PAUSE:
			run ("xmms --play-pause");
			break;
		case PCMD_STOP:
			run ("xmms --stop");
			break;
		case PCMD_PREVIOUS:
			run ("xmms --rew");
			break;
		case PCMD_NEXT:
			run ("xmms --fwd");
			break;
	}
}

void
ctrl_amarok (PLAYER_CMD cmd)
{
	switch (cmd)
	{
		case PCMD_PLAY_PAUSE:
			run ("amarok --play-pause");
			break;
		case PCMD_STOP:
			run ("amarok --stop");
			break;
		case PCMD_PREVIOUS:
			run ("amarok --previous");
			break;
		case PCMD_NEXT:
			run ("amarok --next");
			break;
	}
}


void
control_rhythmbox (	char	*rhythmbox_command,
			char	*rhythmbox_client_command )
/*
Description:
	This functions creates a client process that runs 'rhythmbox_client_command' when the
	program "rhythmbox-client" is installed and otherwise 'rhythmbox_command'.
*/
{
	static Boolean use_client;
	static Boolean initialized = FALSE;
	
	if (!initialized)
	{
		initialized = TRUE;
		use_client = (system( "which rhythmbox-client" ) == 0);
	}
	if (fork() == 0)
	{
		execlp ("sh", "sh", "-c", use_client ? rhythmbox_client_command : rhythmbox_command, NULL);
		exit (EXIT_SUCCESS);
	}
}

void
ctrl_rhythmbox (PLAYER_CMD cmd)
{
	switch (cmd)
	{
		case PCMD_PLAY_PAUSE:
			control_rhythmbox ("rhythmbox --play-pause", "rhythmbox-client --play-pause");
			break;
		case PCMD_STOP:
			control_rhythmbox ("rhythmbox --stop", "rhythmbox-client --stop");
			break;
		case PCMD_PREVIOUS:
			control_rhythmbox ("rhythmbox --previous", "rhythmbox-client --previous");
			break;
		case PCMD_NEXT:
			control_rhythmbox ("rhythmbox --next", "rhythmbox-client --next");
			break;
	}
}

void
ctrl_audacious (PLAYER_CMD cmd)
{
	switch (cmd)
	{
		case PCMD_PLAY_PAUSE:
			run ("audacious -t");
			break;
		case PCMD_STOP:
			run ("audacious --stop");
			break;
		case PCMD_PREVIOUS:
			run ("audacious --rew");
			break;
		case PCMD_NEXT:
			run ("audacious --fwd");
			break;
	}
}

void
ctrl_mpd (PLAYER_CMD cmd)
{
	switch (cmd)
	{
		case PCMD_PLAY_PAUSE:
			run ("mpc toggle");
			break;
		case PCMD_STOP:
			run ("mpc stop");
			break;
		case PCMD_PREVIOUS:
			run ("mpc prev");
			break;
		case PCMD_NEXT:
			run ("mpc next");
			break;
	}
}


char
str_only_digits (char *str)
{
	while (*str && isdigit(*str))
	{
		str++;
	}
	return *str == '\0';
}


void
do_player_command (PLAYER_CMD cmd)
/*
Input:
  cmd     - The command to be executed
Global:
  player  - Array containing the names of the supported players and a pointer to the
            function for controlling that player.
Description:
  For each music player in the 'player' array do_player_command() checks if that
  player is being runned by the current user. If it is then the 'player_ctrl'
  function of that player is being called with 'cmd' as its argument.
*/
{
	DIR *dp;
	struct dirent *ep;
	struct stat dir_stat;
	FILE *stat_file;
	int  uid,
	     i;
	char *working_dir,
	     stat_file_name[MAX_STAT_FILENAME_SIZE],
	     process_name[MAX_PROCESS_NAME_SIZE],
	     c;
	
	uid = getuid();
	working_dir = get_current_dir_name();
	if (working_dir == NULL)
	{
		/* There is not enough memory */
		return;
	}
	chdir ("/proc");
	dp = opendir (".");
	if (dp != NULL)
	{
		/* We are going the check every process that has a directory in /proc/
		 * and is being executed by the current user.
		 */
		while (ep = readdir (dp))
		{
			if (str_only_digits (ep->d_name))
			{
				stat (ep->d_name, &dir_stat);
				if (dir_stat.st_uid == uid) /* Does the user own this directory? */
				{
					snprintf (stat_file_name, MAX_STAT_FILENAME_SIZE, "%s/stat", ep->d_name);
					stat_file = fopen (stat_file_name, "r");
					if (stat_file)
					{
						/* Read from stat_file until we read a '(' or EOF */
						do {
							c = fgetc (stat_file);
						} while (c != EOF && c != '(');
						if (c != EOF)
						{
							/* Initialise process_name in case we don't read anything */
							process_name[0] = '\0';
							/* Read the process name from stat_file and put
							 * it in process_name */
							for (i = 0;
							     i < MAX_PROCESS_NAME_SIZE-1 &&
							      (c = fgetc (stat_file)) != ')' && c != EOF;
							     i++)
							{
								process_name[i] = c;
							}
							process_name[i] = '\0';
							
							/* OK we've got the name now check if it is a player: */
							for (i = 0; i < NUM_PLAYERS; i++)
							{
								if (strcmp (process_name, player[i].name) == 0)
								{
									/* Do the action */
									(*player[i].player_ctrl) (cmd);
									break;
								}
							}
						}
						fclose (stat_file);
					}
				}
			}
		}
		(void) closedir (dp);
	}
	chdir (working_dir);
	free (working_dir);
}


void
play_pause (KTPreferences *preferences)
{
	do_player_command (PCMD_PLAY_PAUSE);
}

void
stop (KTPreferences *preferences)
{
	do_player_command (PCMD_STOP);
}

void
next (KTPreferences *preferences)
{
	do_player_command (PCMD_NEXT);
}

void
previous (KTPreferences *preferences)
{
	do_player_command (PCMD_PREVIOUS);
}
