/* Keyclick V1.1 by Mike M. Duppong  [3/29/88] */
/* Last modified: 11/25/88 */
/* Written in Aztec C V3.6A */
/* Compile: cc keyclick.c */
/* Link: ln keyclick.o -lc */
/* Note: all of my routine and variable names are in lower case letters
 * only.  The Amiga system calls almost always contain at least one
 * capital letter.  In using this convention, I hope to simplify the
 * distinction between my calls and the system calls. */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/errors.h>
#include <exec/devices.h>
#include <exec/execbase.h>
#include <intuition/intuition.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <devices/audio.h>
#include <functions.h>

#define SCREENTITLE "Keyclick V1.1 by Mike Duppong"
#define WINDOWTITLE "Keyclick"
#define HANDLER_PRIORITY 51 /* Priority of input handler */
#define AUDIO_PRIORITY -90 /* Priority of click sound */
#define TASK_PRIORITY 40L /* Priority of this task */
#define WAVELENGTH 16L  /* # of bytes in waveform */
#define PERIOD 1L       /* Waveform period */
#define CYCLES 25L      /* Number of waveform cycles */
#define WINDOWX 0       /* Window's LeftEdge */
#define WINDOWY 13      /* Window's TopEdge */
#define WINDOWW 174     /* Window width */
#define WINDOWH 24      /* Window height */
#define VOLGADX 13      /* Volume gadget's LeftEdge */
#define VOLGADY 12      /* Volume gadget's TopEdge */
#define VOLGADW 128     /* Volume gadget's width */
#define VOLGADH 8       /* Volume gadget's height */
#define VOLGADID 1      /* Volume gadget ID */
#define SWTGADX (VOLGADX + VOLGADW + 10) /* LeftEdge */
#define SWTGADY 11      /* Switch gadget's TopEdge */
#define SWTGADW 9       /* Switch gadget's pixel width */
#define SWTGADWW ((SWTGADW + 15) / 16) /* word width */
#define SWTGADD 1       /* # of planes in gadget image */
#define SWTGADH 12      /* Switch gadget's height */
#define SWTGADID 2      /* Switch gadget ID */
#define CLUP_INPDEV 1   /* Close-up flags for cleanup() */
#define CLUP_AUDDEV 2   /* Audio device */
#define CLUP_HANDLER 4  /* Remove handler */

struct IOStdReq *iostdreq;
struct IOAudio *ioaudio;
struct Interrupt interrupt;
struct MsgPort *input_port, *audio_port;
struct IntuitionBase *IntuitionBase;
struct Window *window;
struct PropInfo prop;
struct Image vol_image;
struct Image onswt_image =
{
  0, 0,                 /* LeftEdge, TopEdge */
  SWTGADW, SWTGADH, SWTGADD,/* Width, Height, Depth */
  NULL,                 /* ImageData - set below */
  0x1, 0,               /* PlanePick, PlaneOnOff */
  NULL                  /* NextImage */
};
struct Image offswt_image =
{
  0, 0,                 /* LeftEdge, TopEdge */
  SWTGADW, SWTGADH, SWTGADD,/* Width, Height, Depth */
  NULL,                 /* ImageData - set below */
  0x1, 0,               /* PlanePick, PlaneOnOff */
  NULL                  /* NextImage */
};
struct Gadget switch_gadget =
{
  NULL,                 /* NextGadget - none */
  SWTGADX, SWTGADY,     /* LeftEdge, TopEdge */
  SWTGADW, SWTGADH,     /* Width, Height */
  SELECTED | GADGHIMAGE | GADGIMAGE, /* Flags */
  GADGIMMEDIATE | RELVERIFY | TOGGLESELECT, /* Activation */
  BOOLGADGET,           /* GadgetType */
  NULL,                 /* GadgetRender - set below */
  NULL,                 /* SelectRender - set below */
  NULL,                 /* GadgetText */
  NULL,                 /* MutualExclude */
  NULL,                 /* SpecialInfo */
  SWTGADID,             /* GadgetID */
  NULL                  /* UserData */
};
struct Gadget volgadget =
{
  &switch_gadget,       /* NextGadget */
  VOLGADX, VOLGADY,     /* LeftEdge, TopEdge */
  VOLGADW, VOLGADH,     /* Width, Height */
  GADGHCOMP,            /* Flags */
  GADGIMMEDIATE | RELVERIFY,/* Activation */
  PROPGADGET,           /* GadgetType */
  NULL,                 /* GadgetRender- set below */
  NULL,                 /* SelectRender */
  NULL,                 /* GadgetText */
  NULL,                 /* MutualExclude */
  (APTR)&prop,          /* SpecialInfo */
  VOLGADID,             /* GadgetID */
  NULL                  /* UserData */
};
struct NewWindow windowdef =
{
  WINDOWX, WINDOWY, WINDOWW, WINDOWH, /* X, Y, W, H */
  0, 1,                 /* Detail/block pen */
  CLOSEWINDOW | GADGETUP, /* IDCMP flags */
  NOCAREREFRESH | SMART_REFRESH | ACTIVATE | WINDOWDRAG |
  WINDOWDEPTH | WINDOWCLOSE, /* Flags */
  &volgadget,           /* First gadget */
  NULL,                 /* Check mark */
  (UBYTE *)WINDOWTITLE, /* Title */
  NULL,                 /* Screen */
  NULL,                 /* Bitmap */
  0, 0, 0, 0,           /* Min & Max Width & Height */
  WBENCHSCREEN          /* Workbench window */
};

UBYTE allocation[] = {1, 8, 2, 4}; /* Channel allocation */
BYTE *waveform;         /* Pointer to waveform RAM */
UWORD onswt_data[] =
{
  0x0000, 0x0800, 0x1C00, 0x1C00, 0x3E00, 0xDD80,
  0x8880, 0xC180, 0x3E00, 0x0000, 0x0000, 0x0000
};
UWORD offswt_data[] =
{
  0x0000, 0x0000, 0x0000, 0x0000, 0x3C00, 0xC180,
  0x8880, 0xDD80, 0x3E00, 0x1C00, 0x1C00, 0x0800
};
struct infostruct  /* Information passed between program and input handler */
{
  struct Task *taskp;
  ULONG key_signal;
  struct Gadget *gadget;
} ih_info;
int clup;
int volume = 63;  /* Click volume */
void handler_interface();



main()
{
  init_gadgets();
  open_window();
  init_sound();
  init_info();
  open_input_device();
  add_handler();
  watch();
  /* cleanup() is used by watch() to exit this program. */
}


init_gadgets()
{
  init_slider();
  init_switch();
}


init_slider()
{
  /* Initialize pointers to image RAM */
  volgadget.GadgetRender = (APTR)&vol_image;

  /* Set slider to horizontally-moving auto-knob */
  prop.Flags = AUTOKNOB | FREEHORIZ;
  prop.HorizPot = 0xFFFF; /* Pegged to begin with */
  prop.HorizBody = 0xFFFF / 32;
}


init_switch()
{
  register int i;

  /* Allocate RAM for on-switch image data */
  if((onswt_image.ImageData = AllocMem((long)
  (SWTGADWW * SWTGADH * SWTGADD * 2), MEMF_CHIP)) == NULL)
    cleanup("Not enough memory.");

  /* Allocate RAM for off-switch image */
  if((offswt_image.ImageData = AllocMem((long)
  (SWTGADWW * SWTGADH * SWTGADD * 2), MEMF_CHIP)) == NULL)
    cleanup("Not enough memory.");

  /* Move switch image data to CHIP memory */
  for(i=0; i<SWTGADWW * SWTGADH * SWTGADD; i++)
  {
    *((UWORD *)(onswt_image.ImageData + i)) = onswt_data[i];
    *((UWORD *)(offswt_image.ImageData + i)) = offswt_data[i];
  }

  /* Point to appropriate image structures */
  switch_gadget.GadgetRender = (APTR)&offswt_image;
  switch_gadget.SelectRender = (APTR)&onswt_image;
}


open_window()
{
  short exists;

  if((IntuitionBase = OpenLibrary("intuition.library", 1L)) == NULL)
    error("Can't open Intuition library.");

  /* Look for an already existing message port named "keyclick.audio."
   * If it exists, another key-clicker is active in the system and this
   * program will abort. */
  if((ULONG)FindPort("keyclick.audio") != NULL)
  {
    /* Move window over a tad to avoid obscuring the original (should it
     * remain unmoved) */
    windowdef.LeftEdge += 5;
    windowdef.TopEdge += 5;
    exists = TRUE;
  }
  else exists = FALSE;
  if((window = OpenWindow(&windowdef)) == NULL)
    error("Can't open a window.");
  if(exists == TRUE)
    error("A key-clicker is already active!");
}


init_sound()
{
  register int i;

  if((ioaudio = AllocMem((long)sizeof(struct IOAudio),
  MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
    error("Can't allocate RAM for IOAudio structure.\n");
  if((audio_port = CreatePort("keyclick.audio", NULL)) == NULL)
    cleanup("Can't create audio message port.");
  if((OpenDevice("audio.device", NULL, ioaudio, NULL)) != NULL)
    error("Audio device failed to open.");
  clup |= CLUP_AUDDEV;

  if((waveform = AllocMem(WAVELENGTH, MEMF_CHIP)) == NULL)
    error("Can't allocate memory for waveform.");

  for(i=0; i<WAVELENGTH; i++) /* Create a triangle waveform */
  {
    waveform[i / 2] = i * 127 / WAVELENGTH;
    waveform[i / 2 + (WAVELENGTH / 2)] = (127 - i) * WAVELENGTH / 127;
  }
}


init_info() /* Initializes the info structure */
{
  ih_info.taskp = FindTask(NULL); /* Pointer to this task */
  SetTaskPri(ih_info.taskp, TASK_PRIORITY); /* Set appropriate priority */
  ih_info.key_signal = AllocSignal(-1L); /* Get a signal to talk to in.han. */
  if(ih_info.key_signal == -1)  /* Check for correct allocation */
    error("Can't allocate a signal");
  ih_info.gadget = &switch_gadget; /* Input handler must know about this */
}


open_input_device()
{
  if((input_port = CreatePort("keyclick.input", NULL)) == NULL)
    error("Can't create an input device message port.");
  if((iostdreq = CreateStdIO(input_port)) == NULL)
    error("Can't create an IOStdReq structure.");
  if((OpenDevice("input.device", NULL, iostdreq, NULL)) != NULL)
    error("Cannot open input device.");
  clup |= CLUP_INPDEV;
}


add_handler()
{
  interrupt.is_Code = handler_interface;  /* Pointer to assembly interface */
  interrupt.is_Data = (APTR)&ih_info;     /* Pointer to mem needed by hand. */
  interrupt.is_Node.ln_Pri = HANDLER_PRIORITY; /* Input handler priority */
  iostdreq->io_Command = IND_ADDHANDLER;
  iostdreq->io_Data = (APTR)&interrupt;
  DoIO(iostdreq);
  clup |= CLUP_HANDLER;
}


watch() /* Watches input device port for messages */
{
  struct IntuiMessage *msg, *dqmsg;
  struct Gadget *gadget;
  ULONG mclass, sig;

  /* Remove all waiting messages (if any) from window's IDCMP first */
  while((dqmsg = (struct IntuiMessage *)GetMsg(window->UserPort)))
    ReplyMsg(dqmsg);
  SetWindowTitles(window, -1L, SCREENTITLE);
  while(1)
  {
    /* Wait for a signal from Intuition or the custom input handler */
    sig = Wait((ULONG)(1L << ih_info.key_signal) |
      (ULONG)(1L << (ULONG)window->UserPort->mp_SigBit));

    /* If the signal was from the input handler... */
    if(sig & (ULONG)(1L << ih_info.key_signal))
      click();

    /* If the signal was from Intuition... */
    if(sig & (ULONG)(1L << (ULONG)window->UserPort->mp_SigBit))
    {
      /* Take the message from the IDCMP */
      msg = (struct IntuiMessage *) GetMsg(window->UserPort);

      /* Make a copy of the message class */
      mclass = msg->Class;

      /* Also make a copy of which gadget was pressed (if any) */
      gadget = (struct Gadget *)msg->IAddress;

      /* Reply to the message before processing it */
      ReplyMsg(msg); /* Reply to sender */

      switch(mclass)
      {
        case CLOSEWINDOW: cleanup(); break; /* If close-gadget hit... */
        case GADGETUP:  /* If a gadget has been pressed (let up on) */
          if(gadget->GadgetID == SWTGADID)  /* Switch? */
            toggle_click();  /* Yes, toggle the click */
          else if(gadget->GadgetID == VOLGADID)  /* Volume slider? */
            adjust_volume(); /* Yes, adjust to new volume level */
          break;
      }
    }
  }
}


toggle_click()  /* Enables/disables volume gadget */
{
  if(switch_gadget.Flags & (WORD)SELECTED)
    OnGadget(&volgadget, window, NULL);
  else
    OffGadget(&volgadget, window, NULL);
}


get_audio_channel()  /* Allocates a free audio channel (if any) */
{
  ioaudio->ioa_Request.io_Command = ADCMD_ALLOCATE;
  ioaudio->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
  ioaudio->ioa_AllocKey = 0;    /* Generate new key */
  ioaudio->ioa_Request.io_Message.mn_Node.ln_Pri = AUDIO_PRIORITY;
  ioaudio->ioa_Request.io_Message.mn_ReplyPort = audio_port;
  ioaudio->ioa_Data = allocation;
  ioaudio->ioa_Length = (long)sizeof(allocation);
  BeginIO(ioaudio);
}


free_audio_channel()  /* Frees up a previously allocated audio channel */
{
  ioaudio->ioa_Request.io_Command = ADCMD_FREE;
  ioaudio->ioa_Request.io_Flags = IOF_QUICK | ADIOF_SYNCCYCLE;
  BeginIO(ioaudio);
}


adjust_volume()  /* Record new volume in the variable "volume" */
{
  volume = ((long)(prop.HorizPot) * 64L) >> 16;
}


click()  /* Take care of all things required to produce a click */
{
  get_audio_channel();  /* Allocate a channel */
  if((long)ioaudio->ioa_Request.io_Unit == NULL) /* If channel not avail...*/
    return;

  /* Fill in the ioaudio structure with appropriate parameters */
  ioaudio->ioa_Request.io_Command = CMD_WRITE;
  ioaudio->ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK | ADIOF_NOWAIT;
  ioaudio->ioa_Data = (UBYTE *)waveform;
  ioaudio->ioa_Length = WAVELENGTH;
  ioaudio->ioa_Period = PERIOD;
  ioaudio->ioa_Volume = (ULONG)volume;
  ioaudio->ioa_Cycles = CYCLES;

  BeginIO(ioaudio); /* Send off audio command */
  WaitIO(ioaudio);  /* Wait for CMD_WRITE operation to complete */
  free_audio_channel(); /* Free up the audio channel */
}


remove_handler() /* Removes the custom input handler from system list */
{
  iostdreq->io_Command = IND_REMHANDLER;
  iostdreq->io_Data = (APTR)&interrupt;
  DoIO(iostdreq);
}


struct InputEvent *input_handler(event, info_str)   /* The informer */
register struct InputEvent *event;
register struct infostruct *info_str;
{
  /* If toggle switch is in the on-position
       If a RAWKEY (down-key) event is intercepted OR
       a raw mouse event (button pressed)
         Signal the waiting task to make a click. */

  if(info_str->gadget->Flags & (WORD)SELECTED)
    if((event->ie_Class == IECLASS_RAWKEY &&
    !(event->ie_Code & IECODE_UP_PREFIX)) ||
    (event->ie_Class == IECLASS_RAWMOUSE &&
    (event->ie_Code == IECODE_LBUTTON ||
    event->ie_Code == IECODE_RBUTTON ||
    event->ie_Code == IECODE_MBUTTON)))
      Signal(info_str->taskp, (ULONG)(1L << info_str->key_signal));
  return(event);
}


error(message)
char *message;
{
  struct IntuiMessage *msg;
  ULONG mclass = NULL;

  /* If the window opened okay, place the error message in
  the Workbench screen's title bar and wait for the window
  to close.  Otherwise, attempt to output the message to
  a CLI window with puts(). */
  if(window != NULL) /* If opened successfully... */
  {
    /* Turn off gadgets */
    OffGadget(&volgadget, window, NULL);
    OffGadget(&switch_gadget, window, NULL);
    SetWindowTitles(window, -1L, message);  /* Set title to error message */
    while(mclass != CLOSEWINDOW) /* Wait for a CLOSEWINDOW message */
    {
      WaitPort(window->UserPort); /* Wait for a message */
      msg = (struct IntuiMessage *) GetMsg(window->UserPort);
      mclass = msg->Class; /* Grab message class */
      ReplyMsg(msg); /* Reply to message */
    }
  }
  else puts(message); /* Try to output to CLI if window isn't open */
  cleanup(); /* Do the standard cleanup from here. */
}


cleanup()
{
  struct Message *dqmsg;

  if(clup & CLUP_HANDLER)    /* Remove the handler from system */
    remove_handler();
  if(ih_info.key_signal != -1) /* Deallocate handler-to-task signal */
    FreeSignal((ULONG)(1L << ih_info.key_signal));
  if(clup & CLUP_INPDEV)     /* Close input device */
    CloseDevice(iostdreq);
  if(iostdreq)               /* Delete created structure */
    DeleteStdIO(iostdreq);
  if(clup & CLUP_AUDDEV)     /* Close audio device */
    CloseDevice(ioaudio);
  if(waveform != NULL)       /* Free waveform memory */
    FreeMem(waveform, WAVELENGTH);
  if(ioaudio != NULL)        /* Free IOAudio structure */
    FreeMem(ioaudio, (long)sizeof(struct IOAudio));
  if(input_port)             /* Remove input device message port */
    DeletePort(input_port);
  if(audio_port)             /* Remove audio device message port */
    DeletePort(audio_port);
  if(window != NULL)         /* Dequeue message port and close window */
  { /* Make sure all messages are answered to free all memory */
    while((dqmsg = GetMsg(window->UserPort))) ReplyMsg(dqmsg);
    CloseWindow(window);
  }
  if(onswt_image.ImageData != NULL) /* Free ON-switch image data */
    FreeMem(onswt_image.ImageData, (long)
    (SWTGADWW * SWTGADH * SWTGADD * 2));
  if(offswt_image.ImageData != NULL) /* Free OFF-switch image data */
    FreeMem(offswt_image.ImageData, (long)
    (SWTGADWW * SWTGADH * SWTGADD * 2));
  if(IntuitionBase != NULL)  /* Close Intuition library */
    CloseLibrary(IntuitionBase);
  exit(NULL);                /* Terminate program here */
}


/* Custom assembly language input handler interface - needed for system to
 * communicate with custom input handler; the system has the required
 * information in registers A0 and A1.  They must be placed on the stack
 * with this function so the C routine (input_handler()) can pull the
 * arguments off properly */
#asm
  cseg
  xref _input_handler
  xdef _handler_interface

_handler_interface:
  move.l a4,-(a7)
  movem.l a0-a1,-(a7)
  jsr _geta4#
  jsr _input_handler
  addq.l #8,a7
  move.l (a7)+,a4
  rts
#endasm

