/*************************************************************************
 *
 *	wicon.c - window iconifier (Right Mouse Button)         29-Dec-87
 */
char *Version =\
   "Wicon 1.14 10-May-88 Copyright (c) 1987,88  Steven Sweeting - SMAUG";
/*
 *	Usage: wicon [ -b boxes ] [ -t ] [ -f | -!f ] [ -p priority ]
 *		     [ -q ] [ -s | -!s ]
 *
 *	use the Right Mouse Button (RMB) to iconify the Active window
 *
 *	Portions Copyright (c) 1987 Davide P. Cervone (Input Handler)
 *
 *	May be freely distributed, hacked etc. Please include wicon.doc
 *	if the code hasn't changed too much.
 *
 *	I really like Matt Dillon's DME's iconifying windows, so I thought
 *	there has to be a way of modifying Workbench to make it possible,
 *	I tried doing things like moving other peoples windows from one
 *	screen to another (SILENT) one. Then it hit me, and it was simple,
 *	easy to program and even could be done without violating
 *	all those Intuition laws (don't hold me to that). Try it out before
 *	looking into the source code.
 *
 *	Even if you chuck this program away, please hang on to
 *	UnSetFunction(), so that the hackers (the old meaning) can
 *	no only co-exist peacefully, but die peacefully as well.
 *
 *	I urge others to use RMBTRAP to do the iconifying themselves
 *
 *************************************************************************/

#define WICON__C

#include "wicon.h"
#include <sgs/arg.h>
#include <stdio.h>
#include <libraries/dos.h>

#ifdef DEBUG
/* hence functions/variables externally available to db */
#define Static
#else  DEBUG
#define Static static
#endif DEBUG

#ifndef SMALLWICON

#define ERROR	char *

#define ERRIBASE    "Can't open intuition.library (V 33)"
#define ERRGFXBASE  "Can't open graphics.library (V 33)"
#define ERRLAYBASE  "Can't open layers.library (V 33)"
#define ERRICONBASE "Can't open icon.library (V 33)"
#define PROCSIGNAL  "\nProcess signalled\n"
#define ERRPORT     "Can't open DeathPort"
#define ERRWIND     "Can't open little window"
#define ERRSIGNAL   "Couldn't allocate signal"
#define ERRBACKDROP "OpenWindow: Can't open Backdrop Window"
#define ERRSTDIO    "CreateStdIO: Couldn't create IO block"
#define ERRINPUT    "OpenDevice: Couldn't open input.device"
#define ERRDOIO1     "DoIo1: failed"
#define ERRDOIO2     "DoIo2: failed"
#define NONE	    NULL

#else SMALLWICON

#define ERROR	short

#define ERRIBASE     1001
#define ERRGFXBASE   1002
#define ERRLAYBASE   1003
#define ERRICONBASE  1004
#define PROCSIGNAL   1005
#define ERRPORT      1006
#define ERRWIND      1007
#define ERRSIGNAL    1008
#define ERRBACKDROP  1009
#define ERRSTDIO     1010
#define ERRINPUT     1011
#define ERRDOIO1     1012
#define ERRDOIO2     1013
#define NONE	     0
#endif

struct ArgList ArgList[] =
{
    { (void *)&Arg_AllScreens,    "ALLSCREENS",    1, 'a', ARGBOOL },
    { (void *)&Arg_Boxes,         "BOXES",         1, 'b', ARGWORD },
    { (void *)&Arg_Chars,         "CHARSACROSS",   1, 'c', ARGWORD },
    { (void *)&Arg_Depth,         "DEPTHGADGETS",  1, 'd', ARGBOOL },
    { (void *)&Arg_Execute,       "EXECUTE",       1, '&', ARGBOOL },
    { (void *)&Arg_FirstScreen,   "FIRSTSCREEN",   1, 'f', ARGBOOL },
    { (void *)&Arg_Iconify,       "ICONIFY",       1, 'i', ARGBOOL },
    { (void *)&Arg_Lines,         "LINES",         1, 'l', ARGWORD },
    { (void *)&Arg_Priority,      "PRIORITY",      1, 'p', ARGWORD },
    { (void *)&Arg_Quiet,         "QUIET",         1, 'q', ARGBOOL },
    { (void *)&Arg_RMBTRAP,       "RMBTRAP",       1, 'r', ARGBOOL },
    { (void *)&Arg_Sticky,        "STICKY",        1, 's', ARGBOOL },
    { (void *)&Arg_Testing,       "TESTING",       1, 't', ARGBOOL },
    { (void *)&Arg_Window,        "WINDOW",        6, 'W', ARGSTRING },
    { (void *)&Arg_WBWs,          "WBWINDOWS",     1, 'w', ARGBOOL },
    { (void *)NULL }    /* End of Array Marker */
};


struct FileHandle *Open();

struct	NewWindow   WBWindow =
{
    1,1, 1,1,  -1,-1, NULL, NULL,
    NULL, NULL, NULL,
    NULL, NULL, 50,15, -1,-1,
    WBENCHSCREEN
};


/*************************************************************************
 *  WiconWait - does the hard stuff, waiting for two things
 *
 *	    1 - PushMask - has a *Window been pushed to the back
 *	    2 - SuicideMask - has this process been oredered to die
 *
 *  This routine could easily be extended to wait on messages arriving
 *  from the the Icon Windows - eg to pass on CLOSEWINDOW IntuiMessages
 *
 *							       01-Jan-88
 */

WiconWait()
{
    long	    WaitMask=0;
    struct  Window  *Window;
    UWORD	    *Flags;


    while (! (WaitMask & SuicideMask) )
    {
	WaitMask = Wait( PushMask | SuicideMask );

	if (WaitMask & PushMask)
	{
	    Window = PushedWindow;

	    W_Push(Window);    /* Note: an Icon may want to PULL */
	}
    }
    /* We have been sent a suicide signal */

    W_Cleanup();
}


/*************************************************************************
 *  main - OPENS everything
 *	   calls WiconWait
 *	   CLOSES everything, calls error(NULL)
 *			      10-Jan-88
 */
int
main(argc,argv)
int	argc;
char	**argv;
{
    WORD	i;		  /* Misc Counter */
    LONG	Key;		  /* Lock on IntuitionBase */

#ifndef SMALLWICON
    extern char *GetArgs();
#endif SMALLWICON
    char	*errString;

    struct  Window *BWindow;
    struct  Window *tmpWindow;

    static struct  BoxInfo MouseBox = {0,0,0,0};

    CWVector = NULL;
    OWVector = NULL;

    if ( (IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 33L ))==NULL )
	error(ERRIBASE);

    if ( (GfxBase = (struct GfxBase *)OpenLibrary( "graphics.library", 33L ))==NULL )
	error(ERRGFXBASE);

    if ( (LayersBase = (struct LayersBase *)OpenLibrary( "layers.library", 33L ))==NULL )
	error(ERRLAYBASE);

    if ( (IconBase = (struct IconBase *)OpenLibrary( "icon.library", 33L ))==NULL )
	error(ERRICONBASE);


	/* If this program is running already, kill it and
		   exit ourselves				   */

    if (DeathPort = FindPort( "wicon.Death" ))
    {
	/* Tell the process to stop */

	Signal( DeathPort->mp_SigTask, (1L<<DeathPort->mp_SigBit) );

	DeathPort = 0;
	error(PROCSIGNAL);
    }
    else
    {
	if ((DeathPort = CreatePort( "wicon.Death", 0L ))==NULL)
	    error(ERRPORT);

	SuicideMask = (1L<<DeathPort->mp_SigBit);
    }

#ifndef SMALLWICON

    SetDefaults();

		/* Process arguments */

    if (errString = GetArgs(argc, argv))
    {
	FILE *errout=stderr;

	if (argc==0)    /* probably WB */
	{
	    errout = fopen( Arg_Window, "w" );
	}

	fputs(Version,stderr);
	if (*errString)
	    fprintf(errout, "\nWicon: %s\n", errString );
	else
	    fprintf(errout, "\nClick the Right Mouse Button quickly to iconify\n");
	fprintf(errout, "uSage: \"Wicon ?\" for help (and list of defaults)\n" );

	if (argc==0)
	    Delay(5*50L);   /* wait 5 seconds */

	error(NONE);
    }

#endif SMALLWICON

    if (!Arg_Quiet)
	printf("%s\n", Version);

    if ((Arg_Execute)    /* fork another wicon wait for it then exit */
#ifdef SMALLWICON
	 && (argc==1)    /* So we don't fork when we have -!& */
#endif SMALLWICON
      )
    {
	ExecuteWicon(argc, argv);
	error(NONE);
    }

    SetTaskPri( FindTask(0L), (LONG)Arg_Priority);

	/* This seems to be the only way to find the Workbench screen
	 * Other than doing weird things with GetScreenData()
	 */

    if ((tmpWindow = OpenWindow( &WBWindow )) == NULL)
	error(ERRWIND);
    WorkBenchScreen = tmpWindow->WScreen;
    CloseWindow( tmpWindow );

    if (Arg_FirstScreen)
	WiconScreen = IntuitionBase->FirstScreen;
    else
	WiconScreen = WorkBenchScreen;


    if (Arg_Iconify)        /* TRUE by default */
    {

		/* clear the array */

	for (i=0 ; i<MAXWINFO ; i++)
	    Winfo[i].Flags = 0; 	/* Hence all ~WINFO_USED */
	Winfos = 0;


		    /* Allocate a signal for our Handler to use
			    to tell us to PUSH a window */

	WiconTask = FindTask(0L);
	if ((PushSignal = AllocSignal( -1L )) == -1L)
	    error(ERRSIGNAL);

	PushMask = (1L << PushSignal);



		/* If there is no Backdrop window then create one */

	Key = LockIBase(0L);
	{
	    BWindow = WiconScreen->FirstWindow;
	    while (BWindow)
		if (BWindow->RPort->Layer->Flags & LAYERBACKDROP)
		    break;
		else
		    BWindow = BWindow->NextWindow;
	}
	UnlockIBase(Key);

	if (BWindow==NULL)
	{
	    NewBackdrop.Screen = WiconScreen;
	    NewBackdrop.Width  = WiconScreen->Width  - NewBackdrop.LeftEdge;
	    NewBackdrop.Height = WiconScreen->Height - NewBackdrop.TopEdge;
	    if ((BackdropWindow = OpenWindow( &NewBackdrop ))==NULL)
		error(ERRBACKDROP);
	    SetWindowTitles( BackdropWindow, -1L, (UBYTE *)Version);
	}



	      /* Set up communication with the input.device */

	if ((InputDevPort = CreatePort(0L,0L)) == NULL)
	    error(ERRPORT);

	if ((InputRequestBlock = CreateStdIO(InputDevPort)) == NULL)
	    error(ERRSTDIO);

	InputDeviceOpen=(OpenDevice("input.device",0L,InputRequestBlock,0L)==0);

	if (InputDeviceOpen == NULL)
	    error(ERRINPUT);


			 /* Install the handler */

	WiconInterrupt.is_Code = (VOID (*)())WiconHandlerStub;
	WiconInterrupt.is_Node.ln_Pri = 51;    /* priority above Intuition */

	InputRequestBlock->io_Command = (long) IND_ADDHANDLER;
	InputRequestBlock->io_Data    = (APTR) &WiconInterrupt;

	if (DoIO(InputRequestBlock))
	    error(ERRDOIO1);
    }

			/* Install the new functions */

    OWVector = SetFunction( IntuitionBase, LVOOpenWindow,  WiconOpenW );
    CWVector = SetFunction( IntuitionBase, LVOCloseWindow, WiconCloseW );

    W_Init();   /* initialise the semaphore and other structures */

    if (Arg_Testing)
    {
	Test();             /* This just opens and closes a window */
    }
    else
    {
	if (argc==0)        /* Running from Workbench */
	{
	    MouseBox.LeftEdge = WiconScreen->MouseX;
	    MouseBox.TopEdge  = WiconScreen->MouseY;
	    SizeRec( WiconScreen, &MouseBox, &WiconScreen->LeftEdge );
	}

	WiconWait();        /* Do all the mumbo jumbo */

	if (argc==0)
	{
	    MouseBox.LeftEdge = WiconScreen->MouseX;
	    MouseBox.TopEdge  = WiconScreen->MouseY;
	    SizeRec( WiconScreen, &WiconScreen->LeftEdge, &MouseBox );
	}
    }


				/* Remove the handler */

    if (Arg_Iconify)
    {
	InputRequestBlock->io_Command = (long) IND_REMHANDLER;
	InputRequestBlock->io_Data    = (APTR) &WiconInterrupt;

	if (DoIO(InputRequestBlock))
	    error(ERRDOIO2);
    }

    error(NONE);
}

/*************************************************************************
 * error - handles erroneous exits as well as the cleanup
 *	   if there is a Message, then print it as an error,
 *	   except if (*Message == '\0').
 */

error(Message)
ERROR	Message;
{
#ifndef SMALLWICON
    if ((Message!=NULL) && *Message)
	puts( Message );
#else
    if (Message!=0)
	printf("Error status %d\n",Message);
#endif

    if (OWVector)
	UnSetFunction( IntuitionBase, LVOOpenWindow, OWVector, WiconOpenW );

    if (CWVector)
	UnSetFunction( IntuitionBase, LVOCloseWindow, CWVector, WiconCloseW );

    if (BackdropWindow)
	CloseWindow( BackdropWindow );

    if (InputDeviceOpen)
	CloseDevice( InputRequestBlock );
    if (InputRequestBlock)
	DeleteStdIO( InputRequestBlock );
    if (InputDevPort)
	DeletePort(  InputDevPort );

    if (PushSignal)
	FreeSignal( PushSignal );
    if (DeathPort)
	DeletePort( DeathPort );

    if (IconBase)
	CloseLibrary( IconBase );
    if (LayersBase)
	CloseLibrary( LayersBase );
    if (GfxBase)
	CloseLibrary( GfxBase );
    if (IntuitionBase)
	CloseLibrary( IntuitionBase );

    exit(0);
}


/*=======================================================================*/

/* This routine is mostly (c) Davide Cervone's, although all the original shell
 *  was originally based on his as well, thanks Davide!.

/*
 *  Click-Handler.c	Input Handler for ClickUpFront, which brings a
 *			window to the front when you double-click in it.
 *
 *		Copyright (c) 1987 by Davide P. Cervone
 *  You may use this code provided this copyright notice is left intact.
 */

/*************************************************************************
 *  WiconHandler()
 *
 *  This is the input handler. The `self-documenting code :-)' explains what
 *  it does better than I can.
 *
 *  Remember RMB events don't get passed to Intuition unless the RMB is
 *  held down longer than DoubleClickTime(), or the mouse is moved, that is
 *  if we are at all interested in the InutionBase->ActiveWindow.
 */

struct InputEvent *WiconHandler(EventList,data)
struct InputEvent *EventList;
APTR data;
{
    struct InputEvent		*theEvent = EventList;
    register struct Layer	*theLayer, *topLayer;
    register struct Screen	*theScreen;

    static			LastSecs=0;
    static			LastMics=0;

    struct Window		*Window;
    struct InputEvent		**ThisEventPointer;

    WORD			SkipEvent;

    static struct InputEvent	PushDownEvent;
    static WORD 		WaitingForMenuUp = FALSE;

    ThisEventPointer = &EventList;  /* Keep track of the pointer to this event */

    Forbid();

    while(theEvent)
    {
	SkipEvent = FALSE;

	switch(theEvent->ie_Class)
	{
	    case IECLASS_RAWMOUSE:

	    if (theEvent->ie_Code == MENUUP)
	    {
		if
		(
		  WaitingForMenuUp
		&&
		  DoubleClick(LastSecs,LastMics,
			theEvent->ie_TimeStamp.tv_secs,
			theEvent->ie_TimeStamp.tv_micro)
		&&
		  (Window = IntuitionBase->ActiveWindow)
		&&
		  (Window->WScreen == WiconScreen)
		&&
		  (((Window->Flags & RMBTRAP) == 0) || Arg_RMBTRAP)
		)
		{
		    PushedWindow = Window;
		    Signal( WiconTask, PushMask );
		    SkipEvent = TRUE;		/* forget about this event */
		    WaitingForMenuUp = FALSE;	/* forget about the Menu down event too */

		}
		else
		{   /* Put back MENU DOWN event */
		    if (WaitingForMenuUp)
			goto putmenudownback;
		}
		LastSecs = 0;
		LastMics = 0;
	    }
	    else
	    {
		if
		(
		  theEvent->ie_Code == MENUDOWN
		&&
		  (Window = IntuitionBase->ActiveWindow)
		&&
		  Window->WScreen == WiconScreen
		)
		{
		    LastSecs = theEvent->ie_TimeStamp.tv_secs;
		    LastMics = theEvent->ie_TimeStamp.tv_micro;

		    PushDownEvent = *theEvent;	/* Remember the event structure */
		    SkipEvent = TRUE;
		    WaitingForMenuUp = TRUE;
		}
		else
		{   /* Put back MENU DOWN event */
		    if (WaitingForMenuUp)
			goto putmenudownback;
		}
	    }
	    break;

	 case IECLASS_RAWKEY:
	    LastSecs = LastMics = 0;
	    if (WaitingForMenuUp)
		goto putmenudownback;
	    break;

	 case IECLASS_TIMER:
	    if (WaitingForMenuUp)       /* Do we give up waiting for a MENU up ? */
		if (!DoubleClick(LastSecs, LastMics,
			theEvent->ie_TimeStamp.tv_secs,
			theEvent->ie_TimeStamp.tv_micro ))
		{
		 putmenudownback:
		    LastSecs = 0;
		    LastMics = 0;
		    WaitingForMenuUp = FALSE;

		    *ThisEventPointer = &PushDownEvent;
		    ThisEventPointer = &(PushDownEvent.ie_NextEvent);
		}

	    break;
	}

	if (!SkipEvent)
	{
	    *ThisEventPointer = theEvent;
	    ThisEventPointer  = &(theEvent->ie_NextEvent);
	}
	theEvent = theEvent->ie_NextEvent;
    }

    *ThisEventPointer = NULL;

    Permit();

    return (EventList);
}

	    /* For testing purposes */

struct	NewWindow   nw =
{
    100,10, 120,200,  -1,-1, NULL, WINDOWDEPTH | WINDOWDRAG,
    NULL, NULL, (UBYTE *)"test",
    NULL, NULL, 50,15, -1,-1,
    WBENCHSCREEN
};

/*************************************************************************
 * Test - is just a test routine, called when Arg_Testing is set, the
 *	  TESTING=TRUE (from WB) or -t options (from CLI)
 */
Test()
{
    CloseWindow (OpenWindow( &nw ));  /* OpenWindow fail? come on! */
}


/*************************************************************************
 * ExecuteWicon - looks at the command line arguments and makes a wild
 *		  hack to put them in back into a single string.
 *		  It will work Ok for Wicon, but maybe not for all
 *   24-Apr-88	  other cases (like imbedded `"''s).
 */
Static
ExecuteWicon(argc, argv)
int argc;		    /* Number of arguments */
char **argv;		    /* Array of arguments */
{
#define ARGLIMIT   256

    char command[ARGLIMIT+1];	   /* the command line we are making */
    struct FileHandle *FileH;
    short   limit;		    /* Counter/Waiting for port */

    char *p;

    p = command;
    *p = '\0';

    strcat(p, "run <NIL: >NIL: \"");  /* cross our fingers */

    strncat(command, *argv++    ,          &command[ARGLIMIT] - p);
    argc--;
    strncat(command, "\" <NIL: >NIL: -!&", &command[ARGLIMIT] - p);

    while (*(++p))
	;

    if (argc!=0)
	while (argc--)
	{
	    if ((*argv)[1] != '&')    /* We don't want the -& argument */
	    {
		*(p++) = ' ';
		*(p++) = '\"';
		*p     = '\0';

		strncat(command, *argv, &command[ARGLIMIT] - p);

		while (*(++p))
		    ;

		*(p++) = '\"';
		*p     = '\0';
		}
	    argv++;
	}

    FileH = Open( "NIL:", MODE_NEWFILE );   /* From Rob Peck's Runback */

#ifdef DEBUG
    printf("Execute(\"%s\", NIL, NIL)\n", command);
#endif DEBUG

    Execute(command, FileH, FileH);         /* Fred Fish 74? */

    if (DeathPort)
	DeletePort( DeathPort );
     DeathPort = NULL;

    limit = 100;	/* Wait 5L * 100 / 50 seconds for child process */
    while (limit-- > 0)
	if (FindPort( "wicon.Death" ))
	    /* Other process has started */
	    break;
	else
	    Delay(5L);      /* 1/10 of a second */

    return;
}

#ifndef SMALLWICON

/*********************************************************************
 *
 * SetDefaults - they are done here so that no mucking around is
 *		 done in the .h files, and Priority must be done
 *		 at run-time
 *
 */
SetDefaults()
{
    struct Task *Task = FindTask(0L);  /* Info about this task */

    /* See wicon.doc for documentation */

    Arg_AllScreens    = TRUE;
    Arg_Boxes	      = 15;
    Arg_Chars	      = 12;
    Arg_Depth	      = TRUE;
    Arg_Execute       = TRUE;
    Arg_FirstScreen   = FALSE;
    Arg_Iconify       = TRUE;
    Arg_Lines	      = 1;
    Arg_Priority      = Task->tc_Node.ln_Pri;
    Arg_Quiet	      = FALSE;
    Arg_RMBTRAP       = FALSE;
    Arg_Sticky	      = FALSE;
    Arg_Testing       = FALSE;
    Arg_WBWs	      = FALSE;
    Arg_Window	      = "CON:0/0/640/150/Wicon error messages";

}

#endif SMALLWICON

