/*
 * WinControl v1.0
 *
 * written by John Corigliano with SAS/C 6.56
 *
 * Copyright 1996 John Corigliano
 *
 * jcorig@strauss.udel.edu
 *
 * Feel free to enhance this code!  
 */

/* These are for SAS/C */
#define __USE_SYSBASE
long    __oslibversion = 37L;

/* The includes */
#include <exec/types.h>
#include <exec/exec.h>
#include <exec/interrupts.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <devices/input.h>
#include <workbench/workbench.h>
#include <dos/dos.h>

/* With SAS/C, if we include these, the libs are Auto Open/Close */
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/dos.h>
#include <proto/icon.h>
#include <proto/layers.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* This has all the stuff for StartMenu */
#include "messages.h"

UBYTE ver[] = {"$VER: WinControl v1.0 "__AMIGADATE__};

extern struct IntuitionBase *IntuitionBase;
struct Library *IntBase;

/* This will be the linked list of our windows and their gadgets */
typedef struct WinControl {
    struct WinControl *wc_Next;
    struct Window     *wc_Win;
    struct Gadget     *wc_Gadget;
    UBYTE              wc_Title[24];
    BOOL               wc_IsIcon;
    struct IBox        wc_RealPos;
    UWORD              wc_MinX, wc_MinY;
} WIN_CON;

WIN_CON *whead, *wtail;
struct Screen *scn;
struct MsgPort *my_port;
volatile WIN_INFO sm_win;
START_MSG smsg;
IMAGE_SCALED ims;

typedef ULONG (*FUNCPTR)();
FUNCPTR OldOpenWin;
FUNCPTR OldOpenWinTag;
FUNCPTR OldCloseWin;
FUNCPTR OldSetTitle;
FUNCPTR OldUpfrontLayer;
ULONG MyOpenWin();
ULONG MyOpenWinTag();
ULONG MyCloseWin();
ULONG MySetTitle();
ULONG MyUpfrontLayer();

VOID ShutDown(STRPTR, int);
BOOL UnLink(struct Gadget *);
VOID CatchUp(VOID);
VOID FindStartMenu(VOID);
VOID SendStartMsg(START_MSG *, APTR, UBYTE);
VOID SendStartMsgAlt(APTR, UBYTE);
VOID SetPatches(VOID);
VOID __asm __saveds HandleOpenWin(register __d0 struct Window *);
VOID __asm __saveds HandleCloseWin(register __a0 struct Window *);
VOID __asm __saveds HandleSetTitle(register __a0 struct Window *,
                                   register __a1 STRPTR);
LONG __asm __saveds HandleUpfrontLayer(register __a1 struct Layer *);
VOID DoGadget(WIN_CON *);
VOID DoIconify(WIN_CON *);
WIN_CON *FindWC(struct Gadget *);

int main(int argc, char **argv)
{
    GADGET_STATS gs;
    START_MSG *rep_smsg;
    ULONG sig, my_sig, x;
    BOOL done = FALSE;
    BPTR lock;
    UBYTE icon_name[128];
    struct DiskObject *d_obj;

    /* This just saves a little typing */
    IntBase = (struct Library *)IntuitionBase;

    FindStartMenu();


    if (0 == argc) {
        argc = _WBArgc;
        argv = _WBArgv;
    }
    for (x = 1; x < argc; x++) {
        if (0 == strnicmp(argv[x], "ICON=", 5)) {
            if (0 == strnicmp(&(argv[x][5]), "TRUE", 4)) {
                /* Use the program's WB icon for the imagery in the gadgets */
                lock = GetProgramDir();
                NameFromLock(lock, icon_name, 128);
                AddPart(icon_name, "WinControl", 128);
                /* Scale the image */
                if (d_obj = GetDiskObject(icon_name)) {
                    if (d_obj->do_Gadget.GadgetRender) {
                        ims.ims_Image = d_obj->do_Gadget.GadgetRender;
                        SendStartMsg(&smsg, (APTR)(&ims), SCALE_IMAGE);
                        /* No need to check image - if it's NULL we just won't useit */
                    }
                    FreeDiskObject(d_obj);
                }
                break;
            }
        }
    }

    CatchUp();
    SetPatches();

    /* The event loop */
    my_sig = 1L << my_port->mp_SigBit;
    while (!done) {
        sig = Wait(my_sig);
        if (sig & my_sig) {
            while (rep_smsg = (START_MSG *)GetMsg(my_port)) {
                /* If it's GADGET_STATS, the user pressed a button */
                if (rep_smsg->sm_Command == GADGET_STATUS) {
                    /* Copy and reply ASAP */
                    memcpy((APTR)(&gs), rep_smsg->sm_Data, sizeof(GADGET_STATS));
                    ReplyMsg((struct Message *)rep_smsg);
                    /* Handle a button press - these are TOGGLE gadgets */
                    if (gs.gs_IMSG->Class == IDCMP_GADGETDOWN) {
                        if ((gs.gs_IMSG->Qualifier & IEQUALIFIER_LSHIFT) ||
                              (gs.gs_IMSG->Qualifier & IEQUALIFIER_RSHIFT))
                            DoIconify(FindWC(gs.gs_Gadget));
                        else
                            DoGadget(FindWC(gs.gs_Gadget));
                    }
                    else if (gs.gs_IMSG->Class == IDCMP_GADGETUP)
                        DoGadget(FindWC(gs.gs_Gadget));
                }
                /* Start menu booted us! It must be shutting down */
                else if (rep_smsg->sm_Command == REM_TASKBAR_GADGET) {
                    RE_GADGET *re = (RE_GADGET *)(rep_smsg->sm_Data);
                    /* Only quit after ALL gadgets have been removed */
                    done = UnLink(re->re_Gadget);
                    ReplyMsg((struct Message *)rep_smsg);
                }
            }
        }
    }
    ShutDown(NULL, 0);
}

/*
 * Clean up routine.
 */
VOID ShutDown(STRPTR s, int rc)
{
    FREE_BITMAP fbm;

    if (ims.ims_ScaledBM) {
        fbm.fbm_BitMap = ims.ims_ScaledBM;
        fbm.fbm_Mask = ims.ims_Mask;
        SendStartMsg(&smsg, (APTR)(&fbm), FREE_BIT_MAP);
    }

    if (my_port) DeletePort(my_port);

    /* Okay, so it's not the safest way to un-patch */
    if (OldOpenWin) SetFunction(IntBase, -0xCC, OldOpenWin);
    if (OldOpenWinTag) SetFunction(IntBase, -0x25E, OldOpenWinTag);
    if (OldCloseWin) SetFunction(IntBase, -0x48, OldCloseWin);
    if (OldSetTitle) SetFunction(IntBase, -0x114, OldSetTitle);
    if (OldUpfrontLayer) SetFunction(LayersBase, -0x30, OldUpfrontLayer);

    if (s) printf("WinControl: %s\n", s);
    exit(rc);
}

/*
 * Remove a gadget and return TRUE if it was the last one.
 */
BOOL UnLink(struct Gadget *g)
{
    WIN_CON *work = whead, *prev = NULL;
    
    while (work) {
        if (work->wc_Gadget == g) {
            if (whead == work) {
                if (work->wc_Next) whead = work->wc_Next;
                else whead = wtail = NULL;
            }
            else if (wtail == work) {
                wtail = prev;
                prev->wc_Next = NULL;
            }
            else
                prev->wc_Next = work->wc_Next;
            if (work->wc_IsIcon) {
                ChangeWindowBox(work->wc_Win, work->wc_RealPos.Left,
                                              work->wc_RealPos.Top,
                                              work->wc_RealPos.Width,
                                              work->wc_RealPos.Height);
                work->wc_Win->MinWidth = work->wc_MinX;
                work->wc_Win->MinHeight = work->wc_MinY;
            }
            FreeMem(work, sizeof(WIN_CON));
        }
        work = work->wc_Next;
    }
    return((BOOL)((NULL == whead) ? TRUE : FALSE));
}

/*
 * Used at init to catch any windows that may have been open already.
 */
VOID CatchUp(VOID)
{
    struct Window *win;
    
    win = scn->FirstWindow;
    while (win) {
        HandleOpenWin(win);
        win = win->NextWindow;
    }
}

/*
 * Need to see if StartMenu is actually up and running.
 */
VOID FindStartMenu(VOID)
{
    WORD x = 0;
    struct MsgPort *tport;
    
    /* Make a couple of tries to find StartMenu */
    while (NULL == (tport = FindPort(STARTMENU_PORT_NAME))) {
        if (++x == 10) break;
        Delay(250);
    }
    if (NULL == tport)
        ShutDown("Could not find StartMenu.", 10);

    /* This port is for StartMenu to send us messages and for us to
       send StartMenu mesages */
    if (NULL == (my_port = CreatePort(0,0)))
        ShutDown("Could not create port.", 10);

    /* These are constant */
    smsg.sm_Msg.mn_Node.ln_Type = NT_MESSAGE;
    smsg.sm_Msg.mn_Length = START_MSG_SIZE;
    smsg.sm_Msg.mn_ReplyPort = my_port;

    /* Want to know what screen StartMenu is running on */
    SendStartMsg(&smsg, (APTR)(&sm_win), GET_WINDOW_INFO);
    if (NULL == (scn = sm_win.wi_Window->WScreen))
        ShutDown("Could not get screen.", 5);
}

/*
 * Easy way to send a message.
 */
VOID SendStartMsg(START_MSG *msg, APTR data, UBYTE command)
{
    struct MsgPort *sm_port;
    
    /* Make sure StartMenu hasn't gone anywhere */
    if (sm_port = FindPort(STARTMENU_PORT_NAME)) {
        msg->sm_Command = command;
        msg->sm_Data    = data;
        PutMsg(sm_port, (struct Message *)msg);
        WaitPort(my_port);           /* StartMenu replies to ALL messages!! */
        GetMsg(my_port);
    }
}

/*
 * This version is used by the patch code. Need to create a new port
 * each time since we never know who's process we'll be running on.
 */
VOID SendStartMsgAlt(APTR data, UBYTE command)
{
    START_MSG msg;
    struct MsgPort *port_a, *port_b;

    if (port_a = FindPort(STARTMENU_PORT_NAME)) {
        if (port_b = CreatePort(0,0)) {
            msg.sm_Msg.mn_Node.ln_Type = NT_MESSAGE;
            msg.sm_Msg.mn_Length = START_MSG_SIZE;
            msg.sm_Msg.mn_ReplyPort = port_b;
            msg.sm_Command = command;
            msg.sm_Data    = data;
            PutMsg(port_a, (struct Message *)(&msg));
            WaitPort(port_b);
            GetMsg(port_b);
            DeletePort(port_b);
        }
    }
}

/*
 * Is this "System Friendly" ??  :P
 */
VOID SetPatches(VOID)
{
    OldOpenWin = (FUNCPTR)SetFunction(IntBase, -0xCC, MyOpenWin);
    OldOpenWinTag = (FUNCPTR)SetFunction(IntBase, -0x25E, MyOpenWinTag);
    OldCloseWin = (FUNCPTR)SetFunction(IntBase, -0x48, MyCloseWin);
    OldSetTitle = (FUNCPTR)SetFunction(IntBase, -0x114, MySetTitle);
    OldUpfrontLayer = (FUNCPTR)SetFunction(LayersBase, -0x30, MyUpfrontLayer);
}

/*
 * This get called by the OpenWindow() and OpenWindowTags() patch code.
 */
VOID __asm __saveds HandleOpenWin(register __d0 struct Window *win)
{
    WIN_CON *work = whead;
    START_MENU_GADGET sg = {0};

    if (win->WScreen != scn) return;            // Right screen?
    if (win->Flags & WFLG_BACKDROP) return;     // Ignore these...
    if (win->Flags & WFLG_BORDERLESS) return;   // And these...
    /* WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET = 0xE */
    if (!(win->Flags & 0xE)) return;

    /* Has some weird problem with requesters.  This conditional tries
       to make an educated guess if the window being opened is a
       requester.  If it is, forget about it. */
    if ((0 == win->LeftEdge) && (0 == win->TopEdge) && 
           !(win->Flags & WFLG_SIZEGADGET) && !(win->Flags & WFLG_CLOSEGADGET))
        return;

    /* Don't add the same window twice (damn patches!) */
    while (work) {
        if (work->wc_Win == win) return;
        work = work->wc_Next;
    }

    if (work = AllocMem(sizeof(WIN_CON), MEMF_PUBLIC|MEMF_CLEAR)) {
        if (win->Title && *(win->Title))
            strncpy(work->wc_Title, win->Title, 20);
        else
            strcpy(work->wc_Title, "<UNKNOWN>");
        sg.smg_Title = work->wc_Title;
        sg.smg_Port  = my_port;
        sg.smg_BitMap = ims.ims_ScaledBM;
        sg.smg_Mask   = ims.ims_Mask;
        sg.smg_PrivateBM = TRUE;
        work->wc_IsIcon = FALSE;
        /* Create the gadget */
        SendStartMsgAlt((APTR)(&sg), ADD_TASKBAR_GADGET);
        if (sg.smg_Gadget) {
            if (wtail) {
                wtail->wc_Next = work;
                wtail = work;
            }
            else
                whead = wtail = work;
            work->wc_Win        = win;
            work->wc_Gadget     = sg.smg_Gadget;
        }
        else
            FreeMem(work, sizeof(WIN_CON));
    }
}

/*
 * This get called by the CloseWindow() patch code.
 */
VOID __asm __saveds HandleCloseWin(register __a0 struct Window *win)
{
    WIN_CON *work = whead, *prev = NULL;
    RE_GADGET re;

    while (work) {
        if (work->wc_Win == win) break;
        prev = work;
        work = work->wc_Next;
    }
    if (NULL == work) return;

    re.re_Gadget = work->wc_Gadget;
    SendStartMsgAlt((APTR)(&re), REM_TASKBAR_GADGET);
    
    if (whead == work) {
        if (work->wc_Next) whead = work->wc_Next;
        else whead = wtail = NULL;
    }
    else if (wtail == work) {
        wtail = prev;
        prev->wc_Next = NULL;
    }
    else
        prev->wc_Next = work->wc_Next;

    FreeMem(work, sizeof(WIN_CON));
}

/*
 * This get called by the SetWindowTitles() patch code.
 */
VOID __asm __saveds HandleSetTitle(register __a0 struct Window *win,
                                   register __a1 STRPTR s)
{
    WIN_CON *work = whead;

    if (-1 == (LONG)s) return;

    while (work) {
        if (work->wc_Win == win) break;
        work = work->wc_Next;
    }
    if (NULL == work) return;
    
    if (s && *s)
        strncpy(work->wc_Title, s, 20);
    else
        strcpy(work->wc_Title, "<Unknown>");

    /* DO NOT try to resize a StartMenu gadget!! We can only set the
       title */
    SetGadgetAttrs(work->wc_Gadget, sm_win.wi_Window, NULL,
                    GA_Text, work->wc_Title, TAG_END);
}

/*
 * This get called by the UpfrontLayer() patch code.
 */
LONG __asm __saveds HandleUpfrontLayer(register __a1 struct Layer *lay)
{
    WIN_CON *work = whead;

    while (work) {
        if (work->wc_Win->WLayer == lay) break;
        work = work->wc_Next;
    }
    if (NULL == work) return(1);

    /* If the window (layer) is being popped-to-front, we un-icinify it */
    if (work->wc_IsIcon) {
        DoGadget(work);
        /* Set the new state of the gadget to un-selected */
        SetGadgetAttrs(work->wc_Gadget, sm_win.wi_Window, NULL, GA_Selected, FALSE, TAG_END);
        work->wc_IsIcon = FALSE;
        return(0);
    }
    else return(1);
}

    
/*
 * This gets called when a gadget is pressed
 */
VOID DoGadget(WIN_CON *work)
{
    /* Un-Iconify? */
    if (work->wc_IsIcon) {
        ChangeWindowBox(work->wc_Win, work->wc_RealPos.Left,
                                      work->wc_RealPos.Top,
                                      work->wc_RealPos.Width,
                                      work->wc_RealPos.Height);
        work->wc_Win->MinWidth = work->wc_MinX;
        work->wc_Win->MinHeight = work->wc_MinY;
        work->wc_IsIcon = FALSE;
    }
    /* Otherwise, fake a BOOLEAN gadget (TaskBar gadgets are TOGGLESELECT) */
    else {
        Delay(5);
        SetGadgetAttrs(work->wc_Gadget, sm_win.wi_Window, NULL, GA_Selected, FALSE, TAG_END);
    }

    /* Activate the window */
    WindowToFront(work->wc_Win);
    ActivateWindow(work->wc_Win);
}

/*
 * "Iconify" a window - resize it and move it behind StartMenu's window
 */
VOID DoIconify(WIN_CON *work)
{
    /* These will be the new size */
    ULONG ww = 100, hh = sm_win.wi_Window->Height;

    /* Save the old values */
    work->wc_RealPos.Left   = work->wc_Win->LeftEdge;
    work->wc_RealPos.Top    = work->wc_Win->TopEdge;
    work->wc_RealPos.Width  = work->wc_Win->Width;
    work->wc_RealPos.Height = work->wc_Win->Height;

    /* Make sure it's completely hidden */
    if (sm_win.wi_Window->Width < ww) ww = sm_win.wi_Window->Width;

    /* Arghhh!  Some very strange bug causes WB to crash if I iconify a
     * WB Window that is empty (no icons) and the size = 26!  I have
     * no idea why, so this quick seems to work.... :(  */
    if (hh == 26) hh = 25;

    /* Store and set the new min sizes */
    work->wc_MinX = work->wc_Win->MinWidth;
    work->wc_MinY = work->wc_Win->MinHeight;
    work->wc_Win->MinWidth = ww;
    work->wc_Win->MinHeight = hh;

    /* Make sure it goes behind StartMenu */
    WindowToFront(sm_win.wi_Window);

    /* Resize it */
    ChangeWindowBox(work->wc_Win, sm_win.wi_Window->LeftEdge,
                                  sm_win.wi_Window->TopEdge,
                                  ww, hh);
    work->wc_IsIcon = TRUE;
}

/*
 * Find the WIN_CON structure associatated with the gadget
 */
WIN_CON *FindWC(struct Gadget *g)
{
    WIN_CON *work = whead;

    while (work) {
        if (work->wc_Gadget == g) break;
        work = work->wc_Next;
    }
    return(work);
}
