/*
 * Copyright (C) 1998 Guylhem Aznar
 * Copyright (C) 1993 Robert Nation
 *
 * 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.
 *
 */

/***********************************************************************
 *
 * code for launching afterstep modules.
 *
 ***********************************************************************/

#include "../../configure.h"

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <X11/keysym.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>

#include "../../include/afterstep.h"
#include "../../include/menus.h"
#include "../../include/parse.h"
#include "../../include/misc.h"
#include "../../include/screen.h"
#include "../../include/module.h"

int npipes;
int *readPipes;
int *writePipes;
int *pipeOn;
char **pipeName;

unsigned long *PipeMask;
struct queue_buff_struct **pipeQueue;
extern char *global_base_file;

AFTER_INLINE int PositiveWrite (int module, unsigned long *ptr, int size);
void DeleteQueueBuff (int module);
void AddToQueue (int module, unsigned long *ptr, int size, int done);

void
initModules (void)
{
  int i;

  npipes = GetFdWidth ();

  writePipes = (int *) safemalloc (sizeof (int) * npipes);
  readPipes = (int *) safemalloc (sizeof (int) * npipes);
  pipeOn = (int *) safemalloc (sizeof (int) * npipes);
  PipeMask = (unsigned long *) safemalloc (sizeof (unsigned long) * npipes);
  pipeName = (char **) safemalloc (sizeof (char *) * npipes);
  pipeQueue = (struct queue_buff_struct **)
    safemalloc (sizeof (struct queue_buff_struct *) * npipes);

  for (i = 0; i < npipes; i++)
    {
      writePipes[i] = -1;
      readPipes[i] = -1;
      pipeOn[i] = -1;
      PipeMask[i] = MAX_MASK;
      pipeQueue[i] = (struct queue_buff_struct *) NULL;
      pipeName[i] = NULL;
    }
}

void
ClosePipes (void)
{
  int i;
  for (i = 0; i < npipes; i++)
    {
      if (writePipes[i] > 0)
	{
	  close (writePipes[i]);
	  close (readPipes[i]);
	}
      if (pipeName[i] != NULL)
	{
	  free (pipeName[i]);
	  pipeName[i] = 0;
	}
      while (pipeQueue[i] != NULL)
	{
	  DeleteQueueBuff (i);
	}
    }
}

void
executeModule (char *action, FILE * fd, char **win, int *context)
{
  int afterstep_to_app[2], app_to_afterstep[2];
  int i, val;
  char command[256];
  char *cptr;
  char *aptr;
  char *args[10];
  char *arg1 = NULL;
  char arg2[40];
  char arg3[40];
  char arg5[40];
  char arg6[40];
  char *end;
  extern char *ModulePath;
  extern FILE *config_fd;

  if (action == NULL)
    return;
  strcpy (command, action);

  cptr = command;
  while ((isspace ((unsigned char) *cptr)) && (*cptr != '\n') && (*cptr != 0))
    cptr++;

  end = cptr;

  while ((!(isspace ((unsigned char) *end)) && (*end != '\n')) && (*end != 0) && (end < (command + 256)))
    end++;

  if ((*end == 0) || (end >= command + 256))
    aptr = NULL;
  else
    aptr = end + 1;
  *end = 0;

  if (aptr)
    {
      while ((isspace ((unsigned char) *aptr) || (*aptr == '\n')) && (*aptr != 0) && (aptr < (command + 256)))
	aptr++;
      if ((*aptr == 0) || (*aptr == '\n'))
	aptr = NULL;
    }
  arg1 = findIconFile (cptr, ModulePath, X_OK);
  if (arg1 == NULL)
    {
      fprintf (stderr, "AfterStep: No such module %s %s\n", ModulePath, cptr);
      return;
    }
  /* Look for an available pipe slot */
  i = 0;
  while ((i < npipes) && (writePipes[i] >= 0))
    i++;
  if (i >= npipes)
    {
      fprintf (stderr, "afterstep: Too many Accessories!\n");
      return;
    }
  /* I want one-ended pipes, so I open two two-ended pipes,
   * and close one end of each. I need one ended pipes so that
   * I can detect when the module crashes/malfunctions */
  if (pipe (afterstep_to_app) != 0)
    {
      fprintf (stderr, "AfterStep: Failed to open pipe\n");
      return;
    }
  if (pipe (app_to_afterstep) != 0)
    {
      fprintf (stderr, "AfterStep: Failed to open pipe2\n");
      close (afterstep_to_app[0]);
      close (afterstep_to_app[1]);
      return;
    }
  val = fork ();
  if (val > 0)
    {
      /* This fork remains running afterstep */
      /* close appropriate descriptors from each pipe so
       * that afterstep will be able to tell when the app dies */
      close (app_to_afterstep[1]);
      close (afterstep_to_app[0]);

      /* add these pipes to afterstep's active pipe list */
      pipeName[i] = stripcpy (cptr);
      writePipes[i] = afterstep_to_app[1];
      readPipes[i] = app_to_afterstep[0];
      pipeOn[i] = -1;
      PipeMask[i] = MAX_MASK;
      free (arg1);
      pipeQueue[i] = NULL;

      /* make the PositiveWrite pipe non-blocking. Don't want to jam up
         afterstep because of an uncooperative module */

#if 0				/* ßßß */
      fcntl (writePipes[i], F_SETFL, O_NONBLOCK);	/* POSIX, better behavior */
#else
      fcntl (writePipes[i], F_SETFL, O_NDELAY);		/* early SYSV, bad behaviour */
#endif

      /* Mark the pipes close-on exec so other programs
       * won`t inherit them */
      if (fcntl (readPipes[i], F_SETFD, 1) == -1)
	afterstep_err ("module close-on-exec failed", NULL, NULL, NULL);
      if (fcntl (writePipes[i], F_SETFD, 1) == -1)
	afterstep_err ("module close-on-exec failed", NULL, NULL, NULL);
    }
  else if (val == 0)
    {
      /* this is  the child */
      /* need to close config_fd if its still open! */
      if (config_fd != (FILE *) NULL)
	/* Fixes some funny stuff with svr4 and stream IO */
	/* fclose(config_fd) */
	close (fileno (config_fd));

      /* this fork execs the module */
      close (afterstep_to_app[1]);
      close (app_to_afterstep[0]);

      sprintf (arg2, "%d", app_to_afterstep[1]);
      sprintf (arg3, "%d", afterstep_to_app[0]);
      sprintf (arg5, "%lx", (unsigned long) win);
      sprintf (arg6, "%lx", (unsigned long) context);

      args[0] = arg1;
      args[1] = arg2;
      args[2] = arg3;
      args[3] = global_base_file;
      args[4] = arg5;
      args[5] = arg6;
      if (aptr != NULL)
	{
	  args[6] = aptr;
	  args[7] = 0;
	}
      else
	args[6] = 0;
      execvp (arg1, args);
      fprintf (stderr, "Execution of module failed: %s", arg1);
      perror ("");
      close (app_to_afterstep[1]);
      close (afterstep_to_app[0]);
      exit (1);
    }
  else
    {
      fprintf (stderr, "Fork failed\n");
      free (arg1);
    }
  return;
}

int
HandleModuleInput (Window w, int channel)
{
  char text[256];
  int size;
  int cont, n;
  char *newaction = NULL;

  /* Already read a (possibly NULL) window id from the pipe,
   * Now read an afterstep bultin command line */
  n = read (readPipes[channel], &size, sizeof (int));
  if (n < sizeof (int))
    {
      KillModule (channel, 1);
      return -1;
    }
  if (size > 255)
    {
      fprintf (stderr, "Module command is too big (%d)\n", size);
      size = 255;
    }
  pipeOn[channel] = 1;

  n = read (readPipes[channel], text, size);
  if (n < size)
    {
      KillModule (channel, 2);
      return -1;
    }
  text[n] = 0;
  n = read (readPipes[channel], &cont, sizeof (int));
  if (n < sizeof (int))
    {
      KillModule (channel, 3);
      return -1;
    }
  if (!cont)
    KillModule (channel, 4);

  if (strlen (text))
    {
      char function[256], *ptr;
      MenuRoot *mr = 0;
      char *item = NULL;
      extern int func_val_1, func_val_2, func, Context;
      extern struct config func_config[];
      ASWindow *tmp_win;
      extern char *orig_tline;
      int n, unit_val_1, unit_val_2;
      char unit_1, unit_2;


      orig_tline = text;
      Event.xany.type = ButtonRelease;
      Event.xany.window = w;

      func_val_1 = 0;
      func_val_2 = 0;
      unit_1 = 's';
      unit_2 = 's';
      n = sscanf (text, "%s %d %d", function, &func_val_1, &func_val_2);
      if (n != 3)
	n = sscanf (text, "%s %d%c %d%c", function, &func_val_1, &unit_1, &func_val_2, &unit_2);

      if (mystrcasecmp (function, "SET_MASK") == 0)
	{
	  PipeMask[channel] = func_val_1;
	  return 0;
	}
      if (mystrcasecmp (function, "UNLOCK") == 0)
	return 66;

      func = F_NOP;
      match_string (func_config, function, "bad module function:", NULL);
      if ((func == F_POPUP) || (func == F_FUNCTION))
	{
	  ptr = stripcpy2 (text, 0);
	  if (ptr != NULL)
	    for (mr = Scr.first_menu; mr != NULL; mr = (*mr).next)
	      if (mystrcasecmp ((*mr).name, ptr) == 0)
		break;
	  if (!mr)
	    {
	      no_popup (ptr);
	      func = F_NOP;
	    }
	}
      else if ((func == F_EXEC) || (func == F_RESTART) ||
	       (func == F_CIRCULATE_UP) || (func == F_CIRCULATE_DOWN) ||
	       (func == F_WARP_F) || (func == F_WARP_B) ||
	       (func == F_MODULE) || (func == F_CHANGE_BACKGROUND))
	{
	  if ((func == F_EXEC) || (func == F_RESTART) || (func == F_MODULE) || (func == F_CHANGE_BACKGROUND))
	    {
	      item = stripcpy2 (text, 0);
	      newaction = stripcpy3 (text, True);
	      free (item);
	    }
	  else
	    {
	      item = stripcpy2 (text, 0);
	      newaction = stripcpy3 (text, False);
	      free (item);
	    }
	}
      if (XFindContext (dpy, w, ASContext, (caddr_t *) & tmp_win) == XCNOENT)
	{
	  tmp_win = NULL;
	  w = None;
	}
      if (tmp_win)
	{
	  Event.xbutton.button = 1;
	  Event.xbutton.x_root = tmp_win->frame_x;
	  Event.xbutton.y_root = tmp_win->frame_y;
	  Event.xbutton.x = 0;
	  Event.xbutton.y = 0;
	  Event.xbutton.subwindow = None;
	}
      else
	{
	  Event.xbutton.button = 1;
	  Event.xbutton.x_root = 0;
	  Event.xbutton.y_root = 0;
	  Event.xbutton.x = 0;
	  Event.xbutton.y = 0;
	  Event.xbutton.subwindow = None;
	}
      if (unit_1 == 'p')
	unit_val_1 = 100;
      else
	unit_val_1 = Scr.MyDisplayWidth;
      if (unit_2 == 'p')
	unit_val_2 = 100;
      else
	unit_val_2 = Scr.MyDisplayHeight;

      Context = GetContext (tmp_win, &Event, &w);
      ExecuteFunction (func, newaction, w, tmp_win, &Event, Context,
	       func_val_1, func_val_2, unit_val_1, unit_val_2, mr, channel);

      if (newaction != NULL)
	{
	  free (newaction);
	  newaction = NULL;
	}
    }
  return 0;
}


void
DeadPipe (int nonsense)
{
  signal (SIGPIPE, DeadPipe);
}


void
KillModule (int channel, int place)
{
  close (readPipes[channel]);
  close (writePipes[channel]);

  readPipes[channel] = -1;
  writePipes[channel] = -1;
  pipeOn[channel] = -1;
  while (pipeQueue[channel] != NULL)
    {
      DeleteQueueBuff (channel);
    }
  if (pipeName[channel] != NULL)
    {
      free (pipeName[channel]);
      pipeName[channel] = NULL;
    }
  return;
}

void
KillModuleByName (char *name)
{
  int i;

  if (name == NULL)
    return;

  for (i = 0; i < npipes; i++)
    {
      if (writePipes[i] > 0)
	{
	  if ((pipeName[i] != NULL) && (strcmp (name, pipeName[i]) == 0))
	    KillModule (i, 20);
	}
    }
  return;
}

void
Broadcast (unsigned long event_type, unsigned long num_datum,
	   unsigned long data1, unsigned long data2, unsigned long data3,
	   unsigned long data4, unsigned long data5, unsigned long data6,
	   unsigned long data7)
{
  int i;
  unsigned long body[10];

  body[0] = START_FLAG;
  body[1] = event_type;
  body[2] = num_datum + 3;

  if (num_datum > 0)
    body[3] = data1;
  if (num_datum > 1)
    body[4] = data2;
  if (num_datum > 2)
    body[5] = data3;
  if (num_datum > 3)
    body[6] = data4;
  if (num_datum > 4)
    body[7] = data5;
  if (num_datum > 5)
    body[8] = data6;
  if (num_datum > 6)
    body[9] = data7;

  for (i = 0; i < npipes; i++)
    PositiveWrite (i, body, (num_datum + 3) * sizeof (unsigned long));

}


void
SendPacket (int module, unsigned long event_type, unsigned long num_datum,
	    unsigned long data1, unsigned long data2, unsigned long data3,
	    unsigned long data4, unsigned long data5, unsigned long data6,
	    unsigned long data7)
{
  unsigned long body[10];

  body[0] = START_FLAG;
  body[1] = event_type;
  body[2] = num_datum + 3;

  if (num_datum > 0)
    body[3] = data1;
  if (num_datum > 1)
    body[4] = data2;
  if (num_datum > 2)
    body[5] = data3;
  if (num_datum > 3)
    body[6] = data4;
  if (num_datum > 4)
    body[7] = data5;
  if (num_datum > 5)
    body[8] = data6;
  if (num_datum > 6)
    body[9] = data7;
  PositiveWrite (module, body, (num_datum + 3) * sizeof (unsigned long));
}

void
SendConfig (int module, unsigned long event_type, ASWindow * t)
{
  unsigned long body[MAX_BODY_SIZE + HEADER_SIZE];

  body[0] = START_FLAG;
  body[1] = event_type;
  body[2] = 27;
  body[3] = t->w;
  body[4] = t->frame;
  body[5] = (unsigned long) t;
  body[6] = t->frame_x;
  body[7] = t->frame_y;
  body[8] = t->frame_width;
  body[9] = t->frame_height;
  body[10] = t->Desk;
  body[11] = t->flags;
  body[12] = t->title_height;
  body[13] = t->boundary_width;
  body[14] = t->hints.base_width;
  body[15] = t->hints.base_height;
  body[16] = t->hints.width_inc;
  body[17] = t->hints.height_inc;
  body[18] = t->hints.min_width;
  body[19] = t->hints.min_height;
  body[20] = t->hints.max_width;
  body[21] = t->hints.max_height;
#ifdef NO_ICON_BACKGROUND
  body[22] = t->icon_w;
#else
  body[22] = 0;
#endif
  body[23] = t->icon_pixmap_w;
  body[24] = t->hints.win_gravity;
  body[25] = t->TextPixel;
  body[26] = t->BackPixel;

  PositiveWrite (module, body, 27 * sizeof (unsigned long));
}


void
BroadcastConfig (unsigned long event_type, ASWindow * t)
{
  unsigned long body[MAX_BODY_SIZE + HEADER_SIZE];
  int i;

  body[0] = START_FLAG;
  body[1] = event_type;
  body[2] = 27;
  body[3] = t->w;
  body[4] = t->frame;
  body[5] = (unsigned long) t;
  body[6] = t->frame_x;
  body[7] = t->frame_y;
  body[8] = t->frame_width;
  body[9] = t->frame_height;
  body[10] = t->Desk;
  body[11] = t->flags;
  body[12] = t->title_height;
  body[13] = t->boundary_width;
  body[14] = t->hints.base_width;
  body[15] = t->hints.base_height;
  body[16] = t->hints.width_inc;
  body[17] = t->hints.height_inc;
  body[18] = t->hints.min_width;
  body[19] = t->hints.min_height;
  body[20] = t->hints.max_width;
  body[21] = t->hints.max_height;
#ifdef NO_ICON_BACKGROUND
  body[22] = t->icon_w;
#else
  body[22] = 0;
#endif
  body[23] = t->icon_pixmap_w;
  body[24] = t->hints.win_gravity;
  body[25] = t->TextPixel;
  body[26] = t->BackPixel;

  for (i = 0; i < npipes; i++)
    {
      PositiveWrite (i, body, 27 * sizeof (unsigned long));
    }
}

void
BroadcastName (unsigned long event_type, unsigned long data1,
	       unsigned long data2, unsigned long data3, char *name)
{
  int l, i;
  unsigned long *body;


  if (name == NULL)
    return;
  l = (strlen (name) + 1) / (sizeof (unsigned long)) + 7;

  body = (unsigned long *) safemalloc (l * sizeof (unsigned long));

  body[0] = START_FLAG;
  body[1] = event_type;
  body[2] = l;

  body[3] = data1;
  body[4] = data2;
  body[5] = data3;
  strcpy ((char *) &body[6], name);


  for (i = 0; i < npipes; i++)
    PositiveWrite (i, (unsigned long *) body, l * sizeof (unsigned long));

  free (body);

}


void
SendName (int module, unsigned long event_type,
	  unsigned long data1, unsigned long data2,
	  unsigned long data3, char *name)
{
  int l;
  unsigned long *body;

  if (name == NULL)
    return;
  l = strlen (name) / (sizeof (unsigned long)) + 7;
  body = (unsigned long *) safemalloc (l * sizeof (unsigned long));

  body[0] = START_FLAG;
  body[1] = event_type;
  body[2] = l;

  body[3] = data1;
  body[4] = data2;
  body[5] = data3;
  strcpy ((char *) &body[6], name);

  PositiveWrite (module, (unsigned long *) body, l * sizeof (unsigned long));

  free (body);
}



#include <sys/errno.h>
AFTER_INLINE int
PositiveWrite (int module, unsigned long *ptr, int size)
{
  if ((pipeOn[module] < 0) || (!((PipeMask[module]) & ptr[1])))
    return -1;

  AddToQueue (module, ptr, size, 0);

  if (PipeMask[module] & M_LOCKONSEND)
    {
      Window targetWindow;
      int e;


      FlushQueue (module);

      fcntl (readPipes[module], F_SETFL, 0);

      while ((e = read (readPipes[module], &targetWindow, sizeof (Window))) > 0)
	{
	  if (HandleModuleInput (targetWindow, module) == 66)
	    break;
	}
      if (e <= 0)
	KillModule (module, 10);

      fcntl (readPipes[module], F_SETFL, O_NDELAY);
    }
  return size;
}

void
AddToQueue (int module, unsigned long *ptr, int size, int done)
{
  struct queue_buff_struct *c, *e;
  unsigned long *d;

  c = (struct queue_buff_struct *) safemalloc (sizeof (struct queue_buff_struct));
  c->next = NULL;
  c->size = size;
  c->done = done;
  d = (unsigned long *) safemalloc (size);
  c->data = d;
  memcpy (d, ptr, size);

  e = pipeQueue[module];
  if (e == NULL)
    {
      pipeQueue[module] = c;
      return;
    }
  while (e->next != NULL)
    e = e->next;
  e->next = c;
}

void
DeleteQueueBuff (int module)
{
  struct queue_buff_struct *a;

  if (pipeQueue[module] == NULL)
    return;
  a = pipeQueue[module];
  pipeQueue[module] = a->next;
  free (a->data);
  free (a);
  return;
}

void
FlushQueue (int module)
{
  char *dptr;
  struct queue_buff_struct *d;
  int a;
  extern int errno;

  if ((pipeOn[module] <= 0) || (pipeQueue[module] == NULL))
    return;

  while (pipeQueue[module] != NULL)
    {
      d = pipeQueue[module];
      dptr = (char *) d->data;
      while (d->done < d->size)
	{
	  a = write (writePipes[module], &dptr[d->done], d->size - d->done);
	  if (a >= 0)
	    d->done += a;
	  /* the write returns EWOULDBLOCK or EAGAIN if the pipe is full.
	   * (This is non-blocking I/O). SunOS returns EWOULDBLOCK, OSF/1
	   * returns EAGAIN under these conditions. Hopefully other OSes
	   * return one of these values too. Solaris 2 doesn't seem to have
	   * a man page for write(2) (!) */
	  else if ((errno == EWOULDBLOCK) || (errno == EAGAIN) || (errno == EINTR))
	    {
	      return;
	    }
	  else
	    {
	      KillModule (module, 123);
	      return;
	    }
	}
      DeleteQueueBuff (module);
    }
}
