/* 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
 */

/*
** Module.c: code for modules to communicate with fvwm
*/
#include "config.h"
#include <stdio.h>
#include <ctype.h>
#include "Module.h"
#include "safemalloc.h"


/*
 * Loop until count bytes are read, unless an error or end-of-file
 * condition occurs.
 */
inline
static int positive_read(int fd, char* buf, int count )
{
  while ( count > 0 ) {
    int n_read = read( fd, buf, count );
    if ( n_read <= 0 )
      return -1;
    buf += n_read;
    count -= n_read;
  }
  return 0;
}


/**
 * Reads a single packet of info from FVWM.
 * The packet is stored in static memory that is reused during
 * the next call.
 **/

FvwmPacket* ReadFvwmPacket( int fd )
{
  static unsigned long buffer[FvwmPacketMaxSize];
  FvwmPacket* packet = (FvwmPacket*)buffer;
  unsigned long length;

  /* The `start flag' value supposedly exists to synchronize the
     FVWM -> module communication.  However, the communication goes
     through a pipe.  I don't see how any data could ever get lost, so
     how would FVWM & the module become unsynchronized?
  */
  do {
    if ( positive_read( fd, (char *)buffer, sizeof(unsigned long) ) < 0 )
      return NULL;
  } while (packet->start_pattern != START_FLAG);

  /* Now read the rest of the header */
  if ( positive_read( fd, (char *)(&buffer[1]),
		      3 * sizeof(unsigned long) ) < 0 )
    return NULL;
  length = FvwmPacketBodySize_byte(*packet);
  if (length > FvwmPacketMaxSize_byte - FvwmPacketHeaderSize_byte)
  {
    /* packet too long */
    return NULL;
  }
  /* Finally, read the body, and we're done */
  if ( positive_read( fd, (char *)(&buffer[4]), length) < 0)
    return NULL;

  return packet;
}


/************************************************************************
 *
 * SendFinishedStartupNotification - informs fvwm that the module has
 * finished its startup procedures and is fully operational now.
 *
 ***********************************************************************/
char *ModuleFinished = "NOP FINISHED STARTUP";
void SendFinishedStartupNotification(int *fd)
{
  SendText(fd, ModuleFinished, 0);
}

/************************************************************************
 *
 * SendUnlockNotification - informs fvwm that the module has
 * finished it's procedures and fvwm may proceed.
 *
 ***********************************************************************/
char *ModuleUnlock = "NOP UNLOCK";
void SendUnlockNotification(int *fd)
{
  SendText(fd, ModuleUnlock, 0);
}

/************************************************************************
 *
 * SendQuitNotification - informs fvwm that the module has
 * finished and may be killed.
 *
 ***********************************************************************/
static unsigned long ModuleContinue = 1;
void SendQuitNotification(int *fd)
{
  ModuleContinue = 0;
  SendText(fd, ModuleUnlock, 0); /* unlock just in case */
}

/************************************************************************
 *
 * SendText - Sends arbitrary text/command back to fvwm
 *
 ***********************************************************************/
void SendText(int *fd, const char *message, unsigned long window)
{
  char *p, *buf;
  unsigned int len;

  if (!message)
    return;

  /* Get enough memory to store the entire message.
   */
  len = strlen(message);
  p = buf = alloca(sizeof(long) * (3 + 1 + (len / sizeof(long))));

  /* Put the message in the buffer, and... */
  *((unsigned long *)p) = window;
  p += sizeof(unsigned long);

  *((unsigned long *)p) = len;
  p += sizeof(unsigned long);

  strcpy(p, message);
  p += len;

  memcpy(p, &ModuleContinue, sizeof(unsigned long));
  p += sizeof(unsigned long);

  /* Send it!  */
  write(fd[0], buf, p - buf);
}

/************************************************************************
 *
 * SendFvwmPipe - Sends message to fvwm:  The message is a comma-delimited
 * string separated into its component sections and sent one by one to fvwm.
 * It is discouraged to use this function with a "synchronous" module.
 * (Form FvwmIconMan)
 *
 ***********************************************************************/
void SendFvwmPipe(int *fd, const char *message, unsigned long window)
{
  const char *hold = message;
  const char *temp;

  while ( (temp = strchr(hold, ',')) != NULL )
  {
    char *temp_msg = (char*)safemalloc(temp - hold + 1);

    strncpy(temp_msg, hold, (temp - hold));
    temp_msg[(temp - hold)] = '\0';
    hold = temp + 1;

    SendText(fd, temp_msg, window);
    free(temp_msg);
  } /* while */

  /*
   * Send the last part of the string :
   * we don't need to copy this into separate
   * storage because we don't need to modify it ...
   *
   * NOTE: this makes this second call to SendText()
   *       distinct from the first call. Two calls is
   *       cleaner than hacking the loop to make only
   *       one call.
   */
  SendText(fd, hold, window);
}

void SetMessageMask(int *fd, unsigned long mask)
{
  char set_mask_mesg[50];

  sprintf(set_mask_mesg,"SET_MASK %lu\n",mask);
  SendText(fd,set_mask_mesg,0);
}

void SetSyncMask(int *fd, unsigned long mask)
{
  char set_syncmask_mesg[50];

  sprintf(set_syncmask_mesg,"SET_SYNC_MASK %lu\n",mask);
  SendText(fd,set_syncmask_mesg,0);
}

void SetNoGrabMask(int *fd, unsigned long mask)
{
  char set_nograbmask_mesg[50];

  sprintf(set_nograbmask_mesg,"SET_NOGRAB_MASK %lu\n",mask);
  SendText(fd,set_nograbmask_mesg,0);
}

/*
 * Optional routine that sets the matching criteria for config lines
 * that should be sent to a module by way of the GetConfigLine function.
 *
 * If this routine is not called, all module config lines are sent.
 */
static int first_pass = 1;

void InitGetConfigLine(int *fd,char *match)
{
  char *buffer = (char *)alloca(strlen(match) + 32);
  first_pass = 0;                       /* make sure get wont do this */
  sprintf(buffer,"Send_ConfigInfo %s",match);
  SendText(fd,buffer,0);
}


/***************************************************************************
 * Gets a module configuration line from fvwm. Returns NULL if there are
 * no more lines to be had. "line" is a pointer to a char *.
 *
 * Changed 10/19/98 by Dan Espen:
 *
 * - The "isspace"  call was referring to  memory  beyond the end of  the
 * input area.  This could have led to the creation of a core file. Added
 * "body_size" to keep it in bounds.
 **************************************************************************/
void GetConfigLine(int *fd, char **tline)
{
    FvwmPacket* packet;
    int body_count;

    if (first_pass) {
	SendText(fd,"Send_ConfigInfo",0);
	first_pass = 0;
    }

    do {
	packet = ReadFvwmPacket( fd[1] );
	if ( packet == NULL || packet->type == M_END_CONFIG_INFO ) {
	    *tline = NULL;
	    return;
	}
    } while ( packet->type != M_CONFIG_INFO );

    /* For whatever reason CONFIG_INFO packets start with three
       (unsigned long) zeros.  Skip the zeros and any whitespace that
       follows */
    *tline = (char*)&(packet->body[3]);
    body_count = FvwmPacketBodySize(*packet) * sizeof(unsigned long);

    while ( body_count > 0 && isspace((unsigned char)**tline) ) {
        (*tline)++;
        --body_count;
    }
}


ModuleArgs* ParseModuleArgs( int argc, char* argv[], int use_arg6_as_alias )
{
    static ModuleArgs ma;

    /* Need at least six arguments:
       [0] name of executable
       [1] file descriptor of module->FVWM pipe (write end)
       [2] file descriptor of FVWM->module pipe (read end)
       [3] pathname of last config file read (ignored -- use Send_ConfigInfo)
       [4] application window context
       [5] window decoration context

       Optionally (left column used if use_arg6_as_alias is true):
       [6] alias       or  user argument 0
       [7] user arg 0  or  user arg 1
       ...
    */
    if ( argc < 6 ) return NULL;

    /* Module name is (last component of) argv[0] or possibly an alias
       passed on the command line. */
    if ( use_arg6_as_alias && argc >= 7 ) {
	ma.name = argv[6];
	ma.user_argc = argc - 7;
	ma.user_argv = &(argv[7]);
    } else {
	char* p = strrchr( argv[0], '/' );
	if ( p == NULL )
	    ma.name = argv[0];
	else
	    ma.name = ++p;
	ma.user_argc = argc - 6;
	ma.user_argv = &(argv[6]);
    }

    if ( ma.user_argc == 0 )
	ma.user_argv = NULL;

    /* File descriptors for the pipes */
    ma.to_fvwm = atoi( argv[1] );
    ma.from_fvwm = atoi( argv[2] );

    /* Ignore argv[3] */

    /* These two are generated as long hex strings */
    ma.window = strtoul( argv[4], NULL, 16 );
    ma.decoration = strtoul( argv[5], NULL, 16 );

    return &ma;
}


syntax highlighted by Code2HTML, v. 0.9.1