/* -*-c-*- */
/*
 * Copyright (C) 1994 Mark Boyns (boyns@sdsu.edu) and
 *                    Mark Scott (mscott@mcd.mot.com)
 *               1996-1998 Albrecht Kadlec (albrecht@auto.tuwien.ac.at)
 *
 * FvwmEvent version 1.0
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * This module is based on FvwmModuleDebugger which has the following
 * copyright:
 *
 * This module, and the entire ModuleDebugger program, and the concept for
 * interfacing this module to the Window Manager, are all original work
 * by Robert Nation
 *
 * Copyright 1994, Robert Nation. No guarantees or warantees or anything
 * are provided or implied in any way whatsoever. Use this program at your
 * own risk. Permission to use this program for any purpose is given,
 * as long as the copyright is kept intact.
 */

/* ---------------------------- included header files ----------------------- */

#include "config.h"

#include <stdio.h>
/*
 * rplay includes:
 */
#ifdef HAVE_RPLAY
#include <rplay.h>
#undef M_ERROR /* Solaris fix */
#endif

#include "libs/Module.h"
#include "libs/fvwmlib.h"
#include "libs/Parse.h"
#include "libs/Strings.h"

/*
 * fvwm includes:
 */
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include "libs/ftime.h"
#include <ctype.h>

/* ---------------------------- local definitions --------------------------- */

#define BUILTIN_STARTUP         (MAX_TOTAL_MESSAGES)
#define BUILTIN_SHUTDOWN        (MAX_TOTAL_MESSAGES + 1)
#define BUILTIN_UNKNOWN         (MAX_TOTAL_MESSAGES + 2)
#define MAX_BUILTIN             3
#define MAX_RPLAY_HOSTNAME_LEN  MAX_MODULE_INPUT_TEXT_LEN
#define SYNC_MASK_M             (M_DESTROY_WINDOW | M_LOWER_WINDOW | \
	M_RESTACK | M_CONFIGURE_WINDOW)
#define SYNC_MASK_MX            (M_EXTENDED_MSG)

/* ---------------------------- local macros -------------------------------- */

/* ---------------------------- imports ------------------------------------- */

/* ---------------------------- included code files ------------------------- */

/* ---------------------------- local types --------------------------------- */

typedef struct
{
	char *name;
	int action_arg;
} event_entry;

/* ---------------------------- forward declarations ------------------------ */

void execute_event(short, unsigned long*);
void config(void);
RETSIGTYPE DeadPipe(int);
static RETSIGTYPE TerminateHandler(int);

/* ---------------------------- local variables ----------------------------- */

/* ---------------------------- exported variables (globals) ---------------- */

static char *MyName;
static int MyNameLen;
static int fd[2];
static char *cmd_line = NULL;
static time_t audio_delay = 0; /* seconds */
static time_t last_time = 0;
static time_t now;
static time_t start_audio_delay = 0;
/* don't tag on the windowID by default */
static Bool PassID = False;
static Bool audio_compat = False;
static char *audio_play_dir = NULL;

#define ARG_NO_WINID 1024  /* just a large number */

static event_entry event_table[MAX_TOTAL_MESSAGES+MAX_BUILTIN] =
{
	{ "new_page", -1 },
	{ "new_desk", 0 | ARG_NO_WINID },
	{ "old_add_window", 0 },
	{ "raise_window", 0 },
	{ "lower_window", 0 },
	{ "old_configure_window", 0 },
	{ "focus_change", 0 },
	{ "destroy_window", 0 },
	{ "iconify", 0 },
	{ "deiconify", 0 },
	{ "window_name", 0 },
	{ "icon_name", 0 },
	{ "res_class", 0 },
	{ "res_name", 0 },
	{ "end_windowlist", -1 },
	{ "icon_location", 0 },
	{ "map", 0 },
	{ "error", -1 },
	{ "config_info", -1 },
	{ "end_config_info", -1 },
	{ "icon_file", 0 },
	{ "default_icon", -1 },
	{ "string", -1 },
	{ "mini_icon", 0 },
	{ "windowshade", 0 },
	{ "dewindowshade", 0 },
	{ "visible_name", 0 },
	{ "sendconfig", -1 },
	{ "restack", -1 },
	{ "add_window", 0 },
	{ "configure_window", 0 },
	/* begin of extended message area */
	{ "visible_icon_name", 0 },
	{ "enter_window", 0 },
	{ "leave_window", 0 },
	{ "property_change", 0},
	/* built in events */
#ifdef M_BELL
	{ "beep", -1 },
#endif
#ifdef M_TOGGLEPAGE
	{ "toggle_paging", -1 },
#endif
	/* add builtins here */
	{ "startup", -1 },
	{ "shutdown", -1 },
	{ "unknown", -1}
};

/* define the action table  */
static char *action_table[MAX_TOTAL_MESSAGES+MAX_BUILTIN];

#ifdef HAVE_RPLAY
static int rplay_fd = -1;
static RPLAY *rplay_table[MAX_TOTAL_MESSAGES+MAX_BUILTIN];
#endif

static unsigned int m_selected = 0;
static unsigned int mx_selected = 0;
static unsigned int m_sync = 0;
static unsigned int mx_sync = 0;


static volatile sig_atomic_t isTerminated = False;

/* ---------------------------- local functions ----------------------------- */

void unlock_event(unsigned long evtype)
{
	unsigned int mask;

	if (evtype & M_EXTENDED_MSG)
	{
		mask = mx_sync;
	}
	else
	{
		mask = m_sync;
	}
	if (evtype & mask)
	{
		SendUnlockNotification(fd);
	}

	return;
}

int main(int argc, char **argv)
{
	char *s;
	unsigned long header[FvwmPacketHeaderSize];
	unsigned long body[FvwmPacketBodyMaxSize];
	int total, remaining, count, event;
	int is_extended_msg;

	cmd_line = (char *)safemalloc(1);
	*cmd_line = 0;
	/* Save our program  name - for error events */
	if ((s=strrchr(argv[0], '/')))
	{
		/* strip path */
		s++;
	}
	else
	{
		/* no slash */
		s = argv[0];
	}
	if (argc == 7)
	{
		if (strcmp(argv[6], "-audio") == 0)
		{
			audio_compat = True;
		}
		else
		{
			/* use an alias */
			s = argv[6];
		}
	}

	/* account for '*' */
	MyNameLen=strlen(s)+1;
	/* account for \0 */
	MyName = safemalloc(MyNameLen+1);
	*MyName='*';
	/* append name */
	strcpy(MyName+1, s);
	if (StrEquals("FvwmAudio", s))
	{
                /* catch the FvwmAudio alias */
		audio_compat = True;
	}

	/* Now MyName is defined */
	if ((argc != 6)&&(argc != 7))
	{
		fprintf(stderr, "%s Version "VERSION" should only be "
			"executed by fvwm!\n", MyName+1);
		exit(1);
	}

#ifdef HAVE_SIGACTION
	{
		struct sigaction    sigact;

		sigemptyset(&sigact.sa_mask);
# ifdef SA_INTERRUPT
		sigact.sa_flags = SA_INTERRUPT;
# else
		sigact.sa_flags = 0;
# endif
		sigact.sa_handler = TerminateHandler;

		/* Dead pipe == Fvwm died */
		sigaction(SIGPIPE,&sigact,NULL);
		/* "polite" termination signal */
		sigaction(SIGTERM,&sigact,NULL);
	}
#else
	/* We don't have sigaction(), so fall back to less robust methods. */
	signal(SIGPIPE, TerminateHandler);
	signal(SIGTERM, TerminateHandler);
#endif

	fd[0] = atoi(argv[1]);
	fd[1] = atoi(argv[2]);

	/* configure events */
	config();
	/* Startup event */
	execute_event(BUILTIN_STARTUP, NULL);
	if (start_audio_delay)
	{
		last_time = time(0);
	}
	/* tell fvwm we're running */
	SetMessageMask(fd, m_selected);
	SetMessageMask(fd, mx_selected | M_EXTENDED_MSG);
	m_sync = (m_selected & SYNC_MASK_M);
	mx_sync = (mx_selected & SYNC_MASK_M) | M_EXTENDED_MSG;
	/* migo (19-Aug-2000): synchronize on M_DESTROY_WINDOW
	 * dv (6-Jul-2002: synchronize on a number of events that can hide or
	 * destroy a window */
	if (m_sync)
	{
		SetSyncMask(fd, m_sync);
	}
	if (mx_sync != M_EXTENDED_MSG)
	{
		SetSyncMask(fd, mx_sync);
	}
	mx_sync &= ~M_EXTENDED_MSG;
	SendFinishedStartupNotification(fd);

	/* main loop */
	while (!isTerminated)
	{
		unsigned long msg_bit;

		if ((count = read(fd[1], header, FvwmPacketHeaderSize_byte)) <=
		    0)
		{
			isTerminated = 1;
			continue;
		}
		/* if this read is interrrupted EINTR, the wrong event is
		 * triggered! */

		if (header[0] != START_FLAG)
		{
			/* should find something better for resyncing */
			unlock_event(header[1]);
			continue;
		}

		/* Ignore events that occur during the delay period. */
		now = time(0);

		/* junk the event body */
		total=0;
		remaining = (header[2] - FvwmPacketHeaderSize) *
			sizeof(unsigned long);
		while (remaining)
		{
			if ((count=read(fd[1],&body[total],remaining)) < 0)
			{
				isTerminated = 1;
				unlock_event(header[1]);
				continue;
			}
			remaining -= count;
			total +=count;
		}

		if (now < last_time + audio_delay + start_audio_delay)
		{
			/* quash event */
			unlock_event(header[1]);
			continue;
		}
		else
		{
			start_audio_delay = 0;
		}

		/* event will equal the number of shifts in the base-2
		 * header[1] number.  Could use log here but this should be
		 * fast enough. */
		event = -1;
		msg_bit = header[1];
		is_extended_msg = (msg_bit & M_EXTENDED_MSG);
		msg_bit &= ~M_EXTENDED_MSG;
		while (msg_bit)
		{
			event++;
			msg_bit >>= 1;
		}
		if (event == -1)
		{
			event = BUILTIN_UNKNOWN;
		}
		else if (event >= MAX_MESSAGES && !is_extended_msg)
		{
			event = BUILTIN_UNKNOWN;
		}
		else if (event >= MAX_EXTENDED_MESSAGES && is_extended_msg)
		{
			event = BUILTIN_UNKNOWN;
		}
		else if (is_extended_msg)
		{
			event += MAX_MESSAGES;
		}
		execute_event(event, body);
		unlock_event(header[1]);
	} /* while */
	execute_event(BUILTIN_SHUTDOWN, NULL);

	return 0;
}


/*
 *
 * Procedure:
 *
 *    execute_event - actually executes the actions from lookup table
 *
 */
void execute_event(short event, unsigned long *body)
{
#ifdef HAVE_RPLAY
	/* this is the sign that rplay is used */
	if (rplay_fd != -1 && rplay_table[event])
	{
		if (rplay(rplay_fd, rplay_table[event]) >= 0)
		{
			last_time = now;
		}
		else
		{
			rplay_perror("rplay");
		}
		return;
	}
#endif

	if (action_table[event])
	{
		char *buf = NULL;
		int len = 0;

		len = strlen(cmd_line) + strlen(action_table[event]) + 32;
		if (audio_play_dir)
		{
			len += strlen(audio_play_dir);
		}
		buf = (char *)safemalloc(len);
		if (audio_compat)
		{
			/* Don't use audio_play_dir if it's NULL or if
			 * the sound file is an absolute pathname. */
			if (!audio_play_dir || audio_play_dir[0] == '\0' ||
			    action_table[event][0] == '/')
			{
				sprintf(buf,"%s %s", cmd_line,
					action_table[event]);
			}
			else
			{
				sprintf(buf,"%s %s/%s &", cmd_line,
					audio_play_dir, action_table[event]);
			}
			if (!system(buf))
			{
				last_time = now;
			}
		}
		else
		{
			int action_arg = event_table[event].action_arg;
			int fw = 0;

			if (action_arg != -1 && !(action_arg & ARG_NO_WINID))
			{
				fw = body[action_arg];
			}

			if (PassID && action_arg != -1)
			{
				if (action_arg & ARG_NO_WINID)
				{
					action_arg &= ~ARG_NO_WINID;
					sprintf(buf, "%s %s %ld", cmd_line,
						action_table[event],
						body[action_arg]);
				}
				else
				{
					sprintf(buf, "%s %s 0x%lx", cmd_line,
						action_table[event],
						body[action_arg]);
				}
			}
			else
			{
				sprintf(buf,"%s %s", cmd_line,
					action_table[event]);
			}
			/* let fvwm execute the function */
			SendText(fd, buf, fw);
			last_time = now;
		}
		free(buf);

		return;
	}

	return;
}


/*
 *
 *  Procedure:
 *      config - read the configuration file.
 *
 */

char *table[]=
{
	"Cmd",
	"Delay",
	"Dir",
	"PassID",
	"PlayCmd",
	"RplayHost",
	"RplayPriority",
	"RplayVolume",
	"StartDelay"
}; /* define entries here, if this list becomes unsorted, use FindToken */

void handle_config_line(char *buf, char **phost)
{
	char *event;
	char *action;
	char *p;
	char *token;
	char **e;
	int i;
	int found;
#ifdef HAVE_RPLAY
	int volume   = RPLAY_DEFAULT_VOLUME;
	int priority = RPLAY_DEFAULT_PRIORITY;
#endif

	p = buf + MyNameLen;
	/* config option ? */
	if ((e = FindToken(p, table, char *)))
	{
		/* skip matched token */
		p += strlen(*e);
		token = PeekToken(p, &p);
		switch (e - (char**)table)
		{
		case 4:
			/* PlayCmd */
			if (!audio_compat)
			{
				/* PlayCmd */
				fprintf(stderr,
					"%s: PlayCmd supported only when"
					" invoked as FvwmAudio\n", MyName+1);
				break;
			}
			/* fall through */
		case 0:
			/* Cmd */
			if (cmd_line)
			{
				free(cmd_line);
			}
			if (token)
			{
				cmd_line = safestrdup(token);
			}
			else
			{
				cmd_line = safestrdup("");
			}
			break;

		case 1:
			/* Delay */
			if (token)
			{
				audio_delay = atoi(token);
			}
			break;

		case 2:
			/* Dir */
			if (!audio_compat)
			{
				fprintf(stderr,
					"%s: Dir supported only when invoked as"
					" FvwmAudio\n", MyName+1);
				break;
			}
			if (token)
			{
				if (audio_play_dir)
				{
					free(audio_play_dir);
				}
				audio_play_dir = safestrdup(token);
			}
			break;

		case 3:
			/* PassID */
			PassID = True;
			break;

#ifdef HAVE_RPLAY
		case 5:
			/* RPlayHost */
			if (*phost)
			{
				free(*phost);
				*phost = NULL;
			}
			if (token && (*token == '$'))
			{
				/* Check for $HOSTDISPLAY */
				char *c1 = (char *)getenv(token + 1);
				char *c2;

				*phost = safestrdup(c1);
				c2 = *phost;
				while (c1 && *c1 != ':')
				{
					*c2++ = *c1++;
				}
				*c2 = '\0';
			}
			else
			{
				*phost = safestrdup(token);
			}
			break;

		case 6:
			/* RplayPriority */
			if (token)
			{
				priority = atoi(token);
			}
			break;

		case 7:
			/* RplayVolume */
			if (token)
			{
				volume = atoi(token);
			}
			break;
#endif

		case 8:
			/* StartDelay */
			if (token)
			{
				start_audio_delay = atoi(token);
			}
			break;

		}
	}
	else
	{
		/* test for isspace(*p) ??? */
		p = GetNextSimpleOption(p, &event);
		p = GetNextSimpleOption(p, &action);
		if (!event || !*event || !action || !*action)
		{
			if (event)
			{
				free(event);
			}
			if (action)
			{
				free(action);
			}
			fprintf(stderr, "%s: incomplete event definition %s\n",
				MyName + 1, buf);
			return;
		}
		for (found = 0, i = 0;
		     !found && i < MAX_TOTAL_MESSAGES + MAX_BUILTIN; i++)
		{
			if (MatchToken(event, event_table[i].name))
			{
#ifdef HAVE_RPLAY
				rplay_table[i] = rplay_create(RPLAY_PLAY);
				rplay_set(
					rplay_table[i], RPLAY_APPEND,
					RPLAY_SOUND, action, RPLAY_PRIORITY,
					priority, RPLAY_VOLUME, volume, NULL);
#endif
				if (action_table[i])
				{
					free(action_table[i]);
				}
				action_table[i] = action;
				found = 1;
				if (i < MAX_MESSAGES)
				{
					m_selected |= (1 << i);
				}
				else if (i < MAX_TOTAL_MESSAGES)
				{
					mx_selected |=
						(1 << (i - MAX_MESSAGES));
				}
			}
		}
		if (!found)
		{
			fprintf(stderr, "%s: unknown event type: %s\n",
				MyName+1, event);
			if (action)
			{
				free(action);
			}
		}
		if (event)
		{
			free(event);
		}
	}

	return;
}

void config(void)
{
	char *buf;
	int i;
	char *host = NULL;

#ifdef HAVE_RPLAY
	host = safestrdup(rplay_default_host());
#endif

	/* Intialize all the actions */
	for (i = 0; i < MAX_TOTAL_MESSAGES+MAX_BUILTIN; i++)
	{
		action_table[i] = NULL;
#ifdef HAVE_RPLAY
		rplay_table[i] = NULL;
#endif
	}

	/* get config lines with my name */
	InitGetConfigLine(fd,MyName);
	while (GetConfigLine(fd,&buf), buf != NULL && *buf != 0)
	{
		if (buf[strlen(buf)-1] == '\n')
		{
			/* if line ends with newline, strip it off */
			buf[strlen(buf)-1] = '\0';
		}

		/* Search for MyName (normally *FvwmEvent) */
		if (strncasecmp(buf, MyName, MyNameLen) == 0)
		{
			handle_config_line(buf, &host);
		}
	}

#ifdef HAVE_RPLAY
	/* Builtin rplay support is enabled when
	 * FvwmAudioPlayCmd == builtin-rplay. */
	if (StrEquals(cmd_line, "builtin-rplay"))
	{
		if ((rplay_fd = rplay_open(host)) < 0)
		{
			rplay_perror("rplay_open");
			exit(1);
		}
	}
#endif

	return;
}

/*
 *
 *  Procedure:
 *      SIGPIPE handler - SIGPIPE means fvwm is dying
 *
 */
static RETSIGTYPE
TerminateHandler(int nonsense)
{
	isTerminated = True;

	SIGNAL_RETURN;
}

/*
 *
 *  Procedure:
 *      Externally callable procedure to quit
 *
 */
RETSIGTYPE DeadPipe(int flag)
{
	execute_event(BUILTIN_SHUTDOWN, NULL);
	exit(flag);
	SIGNAL_RETURN;
}


syntax highlighted by Code2HTML, v. 0.9.1