Copyright (c) 2012-2016 Hyperion Entertainment and contributors.

Input Device

From AmigaOS Documentation Wiki
Jump to: navigation, search
Codereview.png Code samples on this page are not yet updated to AmigaOS 4.x some of them may be obsolete or incompatible with AmigaOS 4.x.

Input Device

The input device is the central collection point for input events disseminated throughout the system. The best way to describe the input device is a manager of a stream with feeders. The input device itself and other modules such as the file system add events to the stream; so do input device “users”—programs or other devices that use parts of the stream or change it in some way. Feeders of the input device include the keyboard, timer and gameport devices. The keyboard, gameport, and timer devices are special cases in that the input device opens them and asks them for input. Users of the input device include Intuition and the console device.

Input Device Commands and Functions

Command Command Operation
CMD_FLUSH Purge all active and queued requests for the input device.
CMD_RESET Reset the input port to its initialized state. All active and queued I/O requests will be aborted. Restarts the device if it has been stopped.
CMD_START Restart the currently active input (if any) and resume queued I/O requests.
CMD_STOP Stop any currently active input and prevent queued I/O requests from starting.
IND_ADDEVENT Propagate an input event to all handlers, updating event qualifiers.
IND_ADDHANDLER Add an input-stream handler into the handler chain.
IND_ADDNOTIFY Add a hook which will be invoked whenever a configuration option changes.
IND_GETHANDLERLIST Obtain a pointer to the list of input handlers.
IND_GETKDEVICE Query the name and unit number of the device keyboard input events are collected from.
IND_GETMDEVICE Query the name and unit number of the device mouse input events are collected from.
IND_GETPERIOD Get the key repeat period.
IND_GETTHRESH Get the key repeat threshold.
IND_IMMEDIATEADDNOTIFY Add a hook which will be invoked whenever a configuration option changes. The hook will be invoked on the current configuration first.
IND_REMHANDLER Remove an input-stream handler from the handler chain.
IND_REMOVENOTIFY Remove a hook previously installed with the IND_ADDNOTIFY command.
IND_SETKDEVICE Choose the device to collect keyboard input events from.
IND_SETMDEVICE Choose the device to collect mouse input events from.
IND_SETMPORT Set the controller port to which the mouse is connected.
IND_SETMTRIG Set conditions that must be met by a mouse before a pending read request will be satisfied.
IND_SETMTYPE Set the type of device at the mouse port.
IND_SETPERIOD Set the period at which a repeating key repeats.
IND_SETTHRESH Set the repeating key hold-down time before repeat starts.
IND_WRITEEVENT Propagate an input event stream to all devices.
Input Device Functions
PeekQualifier() Return the input device’s current qualifiers.

Device Interface

The input device operates like the other Amiga devices. To use it, you must first open the input device, then send I/O requests to it, and then close it when finished. See Exec Device I/O for general information on device usage.

A number of structures are used by the input device to do its processing. Some are used to pass commands and data to the device, some are used to describe input events like mouse movements and key depressions, and one structure is used to describe the environment for input event handlers.

The I/O request used by the input device for most commands is IOStdReq.

struct IOStdReq
{
    struct Message io_Message; /* message reply port */
    struct Device  *io_Device; /* device node pointer */
    struct Unit    *io_Unit;   /* unit */
    UWORD  io_Command;         /* input device command */
    UBYTE  io_Flags;           /* input device flags */
    BYTE   io_Error;           /* error code */
    ULONG  io_Length;          /* number of bytes to transfer */
    APTR   io_Data;            /* pointer to data area */
};

See the include file exec/io.h for the complete structure definition.

Two of the input device commands—IND_SETTHRESH and IND_SETPERIOD—require a time specification and must use a TimeRequest structure instead of an IOStdReq.

struct TimeRequest
{
    struct IORequest Request;
    struct TimeVal Time;
};

As you can see, the TimeRequest structure includes an IORequest structure. The io_Command field of the IORequest indicates the command to the input device and the TimeVal structure sets the time values. See the include file devices/timer.h for the complete structure definition.

In Case You Feel Like Reinventing the Wheel...
You could define a “super-IORequest” structure for the input device which would combine the IOStdReq fields with the TimeVal structure of the TimeRequest structure.

Opening the Input Device

Three primary steps are required to open the input device:

  • Create a message port using AllocSysObject() and ASOT_PORT type. Reply messages from the device must be directed to a message port.
  • Create an extended I/O request structure of type IOStdReq or TimeRequest using AllocSysObject() and ASOT_IOREQUEST type. The I/O request created by the AllocSysObject() function will be used to pass commands and data to the input device.
  • Open the Input device. Call OpenDevice(), passing the I/O request.
struct MsgPort *InputMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
if (InputMP != NULL)
{
    struct IOStdReq *InputIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
        ASOIOR_Size, sizeof(struct IOStdReq),
        ASOIOR_ReplyPort, InputMP,
        TAG_END);
 
    if (ClipIO != NULL)
    {
        if (IExec->OpenDevice("input.device", 0, (struct IORequest *)InputIO, 0))
            IDOS->Printf("input.device did not open\n");

The above code will work for all the input device commands except for the ones which require a time specification. For those, the code would look like this:

#include <devices/timer.h>
 
struct MsgPort *InputMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
if (InputMP != NULL)
{
    struct TimeRequest *InputIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
        ASOIOR_Size, sizeof(struct TimeRequest),
        ASOIOR_ReplyPort, InputMP,
        TAG_END);
 
    if (ClipIO != NULL)
    {
        if (IExec->OpenDevice("input.device", 0, (struct IORequest *)InputIO, 0))
            IDOS->Printf("input.device did not open\n");

Input Device Event Types

The input device is automatically opened by the console device when the system boots. When the input device is opened, a task named “input.device” is started. The input device task communicates directly with the keyboard device to obtain raw key events. It also communicates with the gameport device to obtain mouse button and mouse movement events and with the timer device to obtain time events. In addition to these events, you can add your own input events to the input device, to be fed to the handler chain (see below).

The keyboard device is accessible directly (see Keyboard Device). However, once the input.device task has started, you should not read events from the keyboard device directly, since doing so will deprive the input device of the events and confuse key repeating.

The gameport device has two units. As you view the Amiga, looking at the gameport connectors, the left connector is assigned as the primary mouse input for Intuition and contributes gameport input events to the input event stream.

The right connector is handled by the other gameport unit and is currently unassigned. While the input device task is running, that task expects to read the input from the left connector. Direct use of the gameport device is covered in Gameport Device.

The timer device is used to generate time events for the input device. It is also used to control key repeat rate and key repeat threshold. The timer device is a shared-access device and is described in Timer Device.

The device-specific commands are described below. First though, it may be helpful to consider the types of input events that the input device deals with. An input event is a data structure that describes the following:

  • The class of the event — often describes the device that generated the event.
  • The subclass of the event — space for more information if needed.
  • The code — keycode if keyboard, button information if mouse, others.
  • A qualifier such as “Alt key also down,”or “key repeat active”.
  • A position field that contains a data address or a mouse position count.
  • A time stamp, to determine the sequence in which the events occurred.
  • A link-field by which input events are linked together.
  • The class, subclass, code and qualifier of the previous down key.

The full definitions for each field can be found in the include file devices/inputevent.h. You can find more information about input events in Gameport Device and Console Device.

The various types of input events are listed below.

Input Device Event Types
IECLASS_NULL A NOP input event
IECLASS_RAWKEY A raw keycode from the keyboard device
IECLASS_RAWMOUSE The raw mouse report from the gameport device
IECLASS_EVENT A private console event
IECLASS_POINTERPOS A pointer position report
IECLASS_TIMER A timer event
IECLASS_GADGETDOWN Select button pressed down over a gadget (address in ie_EventAddress)
IECLASS_GADGETUP Select button released over the same gadget (address in ie_EventAddress)
IECLASS_REQUESTER Some requester activity has taken place.
IECLASS_MENULIST This is a menu number transmission (menu number is in ie_Code)
IECLASS_CLOSEWINDOW User has selected the active window’s Close Gadget
IECLASS_SIZEWINDOW This window has a new size
IECLASS_REFRESHWINDOW The window pointed to by ie_EventAddress needs to be refreshed
IECLASS_NEWPREFS New preferences are available
IECLASS_DISKREMOVED The disk has been removed
IECLASS_DISKINSERTED The disk has been inserted
IECLASS_ACTIVEWINDOW The window is about to be been made active
IECLASS_INACTIVEWINDOW The window is about to be made inactive
IECLASS_NEWPOINTERPOS Extended-function pointer position report
IECLASS_MENUHELP Help key report during Menu session
IECLASS_CHANGEWINDOW The Window has been modified with move, size, zoom, or change

There is a difference between simply receiving an input event from a device and actually becoming a handler of an input event stream. A handler is a routine that is passed an input event list. It is up to the handler to decide if it can process the input events. If the handler does not recognize an event, it leaves it undisturbed in the event list.

It All Flows Downhill
Handlers can themselves generate new linked lists of events which can be passed down to lower priority handlers.

The InputEvent structure is used by the input device to describe an input event such as a keypress or a mouse movement.

struct InputEvent
{
    struct  InputEvent *ie_NextEvent;   /* the chronologically next event */
    UBYTE   ie_Class;                   /* the input event class */
    UBYTE   ie_SubClass;                /* optional subclass of the class */
    UWORD   ie_Code;                    /* the input event code */
    UWORD   ie_Qualifier;               /* qualifiers in effect for the event*/
    union
    {
        struct
        {
            WORD    ie_x;               /* the pointer position for the event*/
            WORD    ie_y;
        } ie_xy;
        APTR    ie_addr;                /* the event address */
        struct
        {
            UBYTE   ie_prev1DownCode;   /* previous down keys for dead */
            UBYTE   ie_prev1DownQual;   /*   key translation: the ie_Code */
            UBYTE   ie_prev2DownCode;   /*   & low byte of ie_Qualifier for */
            UBYTE   ie_prev2DownQual;   /*   last & second last down keys */
        } ie_dead;
    } ie_position;
    struct timeval ie_TimeStamp;        /* the system tick at the event */
};

The IEPointerPixel and IEPointerTablet structures are used to set the mouse position with the IECLASS_NEWPOINTERPOS input event class.

struct IEPointerPixel
{
    struct Screen       *iepp_Screen;   /* pointer to an open screen */
    struct
    {                           /* pixel coordinates in iepp_Screen */
        WORD    X;
        WORD    Y;
    } iepp_Position;
};
 
struct IEPointerTablet
{
    struct
    {
        UWORD   X;
        UWORD   Y;
    } iept_Range;       /* 0 is min, these are max      */
    struct
    {
        UWORD   X;
        UWORD   Y;
    } iept_Value;       /* between 0 and iept_Range     */
 
    WORD iept_Pressure; /* -128 to 127 (unused, set to 0)  */
};

See the include file devices/inputevent.h for the complete structure definitions.

For input device handler installation, the Interrupt structure is used.

struct Interrupt
{
    struct Node is_Node;
    APTR   is_Data;         /* server data segment */
    VOID   (*is_Code)();    /* server code entry   */
};

See the include file exec/interrupts.h for the complete structure definition.

Closing the Input Device

Each OpenDevice() must eventually be matched by a call to CloseDevice(). All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO():

if (!(IExec->CheckIO(InputIO)))
    {
    IExec->AbortIO(InputIO);  /* Ask device to abort request, if pending */
    }
IExec->WaitIO(InputIO);   /* Wait for abort, then clean up */
IExec->CloseDevice((struct IORequest *)InputIO);

Using the Mouse Port With the Input Device

To get mouse port information you must first set the current mouse port by passing an IOStdReq to the device with IND_SETMPORT set in io_Command and a pointer to a byte set in io_Data. If the byte is set to 0 the left controller port will be used as the current mouse port; if it is set to 1, the right controller port will be used.

int8 port = 1;      /* set mouse port to right controller */
 
InputIO->io_Data    = &port;
InputIO->io_Flags   = IOF_QUICK;
InputIO->io_Length  = 1;
InputIO->io_Command = IND_SETMPORT;
IExec->BeginIO((struct IORequest *)InputIO);
if (InputIO->io_Error)
    IDOS->Printf("\nSETMPORT failed %ld\n", InputIO->io_Error);
Put That Back!
The default mouse port is the left controller. Don’t forget to set the mouse port back to the left controller before exiting if you change it to the right controller during your application.

Setting the Conditions for a Mouse Port Report

You set the conditions for a mouse port report by passing an IOStdReq to the device with IND_SETMTRIG set in io_Command, the address of a GamePortTrigger structure set in io_Data and the length of the structure set in io_Length.

struct GamePortTrigger InputTR;
 
InputIO->io_Data    = (APTR)InputTR;  /* set trigger conditions */
InputIO->io_Command = IND_SETMTRIG;   /* from InputTR */
InputIO->io_Length  = sizeof(struct GamePortTrigger);
IExec->DoIO(InputIO);

The information needed for mouse port report setting is contained in a GamePortTrigger data structure which is defined in the include file devices/gameport.h.

struct GamePortTrigger
{
    UWORD    gpt_Keys;      /* key transition triggers */
    UWORD    gpt_Timeout;   /* time trigger (vertical blank units) */
    UWORD    gpt_XDelta;    /* X distance trigger */
    UWORD    gpt_YDelta;    /* Y distance trigger */
};

See Gameport Device for a full description of setting mouse port trigger conditions.

Adding an Input Handler

You add an input-stream handler to the input chain by passing an IOStdReq to the device with IND_ADDHANDLER set in io_Command and a pointer to an Interrupt structure set in io_Data.

struct Interrupt *InputHandler;
struct IOStdReq  *InputIO
 
InputHandler->is_Code         = ButtonSwap; /* Address of code */
InputHandler->is_Data         = NULL;       /* User Value passed in A1 */
InputHandler->is_Node.ln_Pri  = 100;        /* Priority in food chain */
InputHandler->is_Node.ln_Name = NameString; /* Name of handler */
 
InputIO->io_Data    = (APTR)inputHandler;   /* Point to the structure */
InputIO->io_Command = IND_ADDHANDLER;       /* Set command ... */
IExec->DoIO((struct IORequest *)InputIO);   /* DoIO( ) the command */

Intuition is one of the input device handlers and normally distributes most of the input events.

Intuition inserts itself at priority position 50. The console device sits at priority position 0. You can choose the position in the chain at which your handler will be inserted by setting the priority field in the list-node part of the interrupt data structure you pass to this routine.

Speed Saves
Any processing time expended by a handler subtracts from the time available before the next event happens. Therefore, handlers for the input stream must be fast.

Rules for Input Device Handlers

The following rules should be followed when you are designing an input handler:

  • If an input handler is capable of processing a specific kind of an input event and that event has no links (ie_NextEvent = 0), the handler can end the handler chain by returning a NULL (0) value.
  • If there are multiple events linked together, the handler is free to unlink an event from the input event chain, thereby passing a shorter list of events to subsequent handlers. The starting address of the modified list is the return value.
  • If a handler wishes to add new events to the chain that it passes to a lower-priority handler, it may initialize memory to contain the new event or event chain. The handler, when it again gets control on the next round of event handling, should assume nothing about the current contents of the memory blocks attached to the event chain. Lower priority handlers may have modified the memory as they handled their part of the event. The handler that allocates the memory for this purpose should keep track of the starting address and the size of this memory chunk so that the memory can be returned to the free memory list when it is no longer needed.

Your handler routine should be structured similar to the following pseudo-language statement:

newEventChain = yourHandlerCode(oldEventChain, yourHandlerData);

where:

yourHandlerCode
is the entry point to your routine.
oldEventChain
is the starting address for the current chain of input events.
yourHandlerData
is a user-definable value, usually a pointer to some data structure your handler requires.
newEventChain
is the starting address of an event chain which you are passing to the next handler, if any.

When your handler code is called, the event chain and the handler data is passed in. When your code returns, it should return the pointer to the event chain. If all of the events were removed by the routine, return NULL. A NULL (0) value terminates the handling thus freeing more CPU resources.

Memory that you use to describe a new input event that you have added to the event chain is available for reuse or deallocation when the handler is called again or after the IND_REMHANDLER command for the handler is complete. There is no guarantee that any field in the event is unchanged since a handler may change any field of an event that comes through the food chain.

Because IND_ADDHANDLER installs a handler in any position in the handler chain, it can, for example, ignore specific types of input events as well as act upon and modify existing streams of input. It can even create new input events for Intuition or other programs to interpret.

Removing an Input Handler

You remove a handler from the handler chain by passing an IOStdReq to the device IND_REMHANDLER set in io_Command and a pointer to the Interrupt structure used to add the handler.

struct Interrupt *InputHandler;
struct IOStdReq  *InputIO;
 
InputIO->io_Data    = (APTR)InputHandler;   /* Which handler to REM */
InputIO->io_Command = IND_REMHANDLER;       /* The REM command */
IExec->DoIO((struct IORequest *)InputIO);   /* Send the command */

Writing Events to the Input Device Stream

Typically, input events are internally generated by the timer device, keyboard device, and input device.

An application can also generate an input event by setting the appropriate fields for the event in an InputEvent structure and sending it to the input device. It will then be treated as any other event and passed through to the input handler chain. However, I/O requests for IND_WRITEVENT cannot be made from interrupt code.

You generate an input event by passing an IOStdReq to the device with IND_WRITEEVENT set in io_Command, a pointer to an InputEvent structure set in io_Data and the length of the structure set in io_Length.

struct InputEvent *FakeEvent;
struct IOStdReq   *InputIO;
 
InputIO->io_Data    = (APTR)FakeEvent;
InputIO->io_Length  = sizeof(struct InputEvent);
InputIO->io_Command = IND_WRITEEVENT;
IExec->DoIO((struct IORequest *)InputIO);
You Know What Happens When You Assume
This command propagates the input event through the handler chain. The handlers may link other events onto the end of this event or modify the contents of the data structure you constructed in any way they wish. Therefore, do not assume any of the data will be the same from event to event.

Setting the Position of the Mouse

One use of writing input events to the input device is to set the position of the mouse pointer. The mouse pointer can be positioned by using the input classes IECLASS_POINTERPOS and IECLASS_NEWPOINTERPOS.

There are two ways to set the position of the mouse pointer using the input class IECLASS_POINTERPOS:

  • At an absolute position on the current screen.
  • At a position relative to the current mouse pointer position on the current screen.

In both cases, you set the Class field of the InputEvent structure to IECLASS_POINTERPOS, ie_X with the new x-coordinate and ie_Y with the new y-coordinate. Absolute positioning is done by setting ie_Qualifier to 0 and relative positioning is done by setting ie_Qualifier to IEQUALIFIER_RELATIVEMOUSE.

Once the proper values are set, pass an IOStdReq to the input device with a pointer to the InputEvent structure set in io_Data and io_Command set to IND_WRITEEVENT.

There are three ways to set the mouse pointer position using IECLASS_NEWPOINTERPOS:

  • At an absolute x-y coordinate on a screen—you specify the exact location of the pointer and which screen.
  • At an relative x-y coordinate—you specify where it will go in relation to the current pointer position and which screen.
  • At a normalized position on a tablet device—you specify the maximum x-value and y-value of the tablet and an x-y coordinate between them and the input device will normalize it to fit.

The basic steps required are the same for all three methods.

  • Get a pointer to the screen where you want to position the pointer. This is not necessary for the tablet device.
  • Set up a structure to indicate the new position of the pointer.

For absolute and relative positioning, you set up an IEPointerPixel structure with iepp_Position.X set to the new x-coordinate, iepp_Position.Y set to the new y-coordinate and iepp_Screen set to the screen pointer. You set up an InputEvent structure with ie_SubClass set to IESUBCLASS_PIXEL, a pointer to the IEPointerPixel structure set in ie_EventAddress, IECLASS_NEWPOINTERPOS set in Class, and ie_Qualifier set to either IEQUALIFIER_RELATIVEMOUSE for relative positioning or 0 for absolute positioning.

For tablet positioning, you set up an IEPointerTablet structure with iept_Range.X set to the maximum x-coordinate and iept_Range.Y set to the maximum y-coordinate, and iept_Value.X set to the new x-coordinate and iept_Value.Y set to the new y-coordinate. You set up an InputEvent structure with a pointer to the IEPointerTablet structure set in ie_EventAddress, ie_SubClass to IESUBCLASS_TABLET and Class set to IECLASS_NEWPOINTERPOS.

Finally, for all three methods, pass an IOStdReq to the device with a pointer to the InputEvent structure set in io_Data and io_Command set to IND_WRITEEVENT.

The following example sets the mouse pointer at an absolute position on a public screen using IECLASS_NEWPOINTERPOS.

/*
 * Set_Mouse.c
 *
 * This example sets the mouse at x=100 and y=200
 *
 * Run from CLI only
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <intuition/screens.h>
 
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
 
struct IntuitionIFace *IIntuition = NULL;
 
int main()
{
struct MsgPort *InputMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
if (InputMP != NULL)
    {
    struct InputEvent *FakeEvent = IExec->AllocVecTags(sizeof(struct InputEvent),
        AVT_Type, MEMF_SHARED,
        TAG_END);
 
    if (FakeEvent != NULL)
        {
        struct IEPointerPixel *NeoPix = IExec->AllocVecTags(sizeof(struct IEPointerPixel),
            AVT_Type, MEMF_SHARED,
            TAG_END);
 
        if (NeoPix != NULL)
            {
            struct IOStdReq *InputIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
                ASOIOR_Size, sizeof(struct IOStdReq),
                ASOIOR_ReplyPort, InputMP,
                TAG_END);
 
            if (InputIO != NULL)
                {
                if (!IExec->OpenDevice("input.device", NULL, (struct IORequest *)InputIO, NULL))
                    {
                    struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
                    IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
 
                    if (IIntuition != NULL)
                        {
                        struct Screen *PubScreen;
                        /* Get pointer to screen and lock screen */
                        if (PubScreen = (struct Screen *)IIntuition->LockPubScreen(NULL))
                            {
                            /* Set up IEPointerPixel fields */
                            NeoPix->iepp_Screen     = (struct Screen *)PubScreen; /* WB screen */
                            NeoPix->iepp_Position.X = 100;  /* put pointer at x = 100 */
                            NeoPix->iepp_Position.Y = 200;  /* put pointer at y = 200 */
 
                            /* Set up InputEvent fields */
                            FakeEvent->ie_EventAddress = (APTR)NeoPix;          /* IEPointerPixel */
                            FakeEvent->ie_NextEvent    = NULL;
                            FakeEvent->ie_Class        = IECLASS_NEWPOINTERPOS; /* new mouse pos */
                            FakeEvent->ie_SubClass     = IESUBCLASS_PIXEL;      /* on pixel */
                            FakeEvent->ie_Code         = IECODE_NOBUTTON;
                            FakeEvent->ie_Qualifier    = 0;                     /* absolute positioning */
 
                            InputIO->io_Data    = (APTR)FakeEvent;    /* InputEvent */
                            InputIO->io_Length  = sizeof(struct InputEvent);
                            InputIO->io_Command = IND_WRITEEVENT;
                            IExec->DoIO((struct IORequest *)InputIO);
 
                            IIntuition->UnlockPubScreen(NULL, PubScreen);  /* Unlock screen */
                            }
                        else
                            IDOS->Printf("Could not get pointer to screen\n");
                        }
                    else
                        IDOS->Printf("Error: Could not open intuition.library\n");
 
                    IExec->DropInterface((struct Interface*)IIntuition);
                    IExec->CloseLibrary(IntuitionBase);
 
                    IExec->CloseDevice((struct IORequest *)InputIO);
                    }
                else
                    IDOS->Printf("Error: Could not open input.device\n");
 
                IExec->FreeSysObject(ASOT_IOREQUEST, InputIO);
                }
            else
                IDOS->Printf("Error: Could not create IORequest\n");
 
            IExec->FreeVec(NeoPix);
            }
        else
            IDOS->Printf("Error: Could not allocate memory for NeoPix\n");
 
        IExec->FreeVec(FakeEvent);
        }
    else
        IDOS->Printf("Error: Could not allocate memory for FakeEvent\n");
 
    IExec->FreeSysObject(ASOT_PORT, InputMP);
    }
else
    IDOS->Printf("Error: Could not create message port\n");
 
return 0;
}

Setting the Key Repeat Threshold

The key repeat threshold is the number of seconds and microseconds a user must hold down a key before it begins to repeat. This delay is normally set by the Preferences tool or by Intuition when it notices that the Preferences have been changed, but you can also do it directly through the input device.

You set the key repeat threshold by passing a TimeRequest with IND_SETTHRESH set in io_Command and the number of seconds to delay set in Seconds and the number of microseconds to delay set in Microseconds.

#include <devices/timer.h>
 
struct TimeRequest *InputTime;  /* Initialize with AllocSysObject(ASOT_IOREQUEST) before using */
 
InputTime->Time.Seconds       = 1;        /* 1 second */
InputTime->Time.Microseconds  = 500000;   /* 500,000 microseconds */
InputTime->Request.io_Command = IND_SETTHRESH;
IExec->DoIO((struct IORequest *)InputTime);

The code above will set the key repeat threshold to 1.5 seconds.

Setting the Key Repeat Interval

The key repeat interval is the time period, in seconds and microseconds, between key repeat events once the initial key repeat threshold has elapsed. (See "Setting the Key Repeat Threshold" above.) Like the key repeat threshold, this is normally issued by Intuition and preset by the Preferences tool.

You set the key repeat interval by passing a TimeRequest with IND_SETPERIOD set in io_Command and the number of seconds set in Seconds and the number of microseconds set in Microseconds.

struct TimeRequest *InputTime; /* Initialize with AllocSysObject(ASOT_IOREQUEST) before using */
 
InputTime->Time.Seconds       = 0;
InputTime->Time.Microseconds  = 12000;  /* 0.012 seconds */
InputTime->Request.io_Command = IND_SETPERIOD;
IExec->DoIO((struct IORequest *)InputTime);

The code above sets the key repeat interval to 0.012 seconds.

The Right Tool For The Right Job
As previously stated, you must use a TimeRequest structure with IND_SETTHRESH and IND_SETPERIOD.

Determining the Current Qualifiers

Some applications need to know whether the user is holding down a qualifier key or a mouse button during an operation. To determine the current qualifiers, you call the input device function PeekQualifier().

PeekQualifier() returns what the input device considers to be the current qualifiers at the time PeekQualifier() is called (e.g., keyboard qualifiers and mouse buttons). This does not include any qualifiers which have been added, removed or otherwise modified by input handlers.

In order to call the function, you must set a pointer to the input device base address and get the interface. Once you set the interface pointer, you can call the function. You must open the device in order to access the device base address and interface.

PeekQualifier() returns an unsigned word with bits set according to the qualifiers in effect at the time the function is called. It takes no parameters.

int main()
{
struct IOStdReq   *InputIO;           /* I/O request block */
uint16 Quals;                         /* qualifiers */
  .
  .
  .
if (!IExec->OpenDevice("input.device", NULL, (struct IORequest *)InputIO, NULL))
     {
     /* Set input device base address in InputBase */
     struct Library *InputBase = (struct Library *)InputIO->io_Device;
     struct InputIFace *IInput = (struct InputIFace *)IExec->GetInterface(InputBase, "main", 1, NULL);
 
     /* Call the function */
     Quals = IInput->PeekQualifier();
     .
     .
     .
     IExec->DropInterface((struct Interface *)IInput);
     IExec->CloseDevice(InputIO);
     }
}

The qualifiers returned are listed in the table below.

Bit Qualifier Key or Button
0 IEQUALIFIER_LSHIFT Left Shift
1 IEQUALIFIER_RSHIFT Right Shift
2 IEQUALIFIER_CAPSLOCK Caps Lock
3 IEQUALIFIER_CONTROL Control
4 IEQUALIFIER_LALT Left Alt
5 IEQUALIFIER_RALT Right Alt
6 IEQUALIFIER_LCOMMAND Left-Amiga
7 IEQUALIFIER_RCOMMAND Right-Amiga
12 IEQUALIFIER_MIDBUTTON Middle Mouse
13 IEQUALIFIER_RBUTTON Right Mouse
14 IEQUALIFIER_LEFTBUTTON Left Mouse

Input Device and Intuition

There are several ways to receive information from the various devices that are part of the input device. The first way is to communicate directly with the device. This method is not recommended while the input device task is running – which is most of the time. The second way is to become a handler for the stream of events which the input device produces. That method is shown above.

The third method of getting input from the input device is to retrieve the data from the console device or from the IDCMP (Intuition Direct Communications Message Port). These are the preferred methods for applications in a multitasking environment because each application can receive juts its own input (i.e., only the input which occurs when one of its window is active). See the Intuition Input and Output Methods for more information on IDCMP messages. See Console Device for more information on console device I/O.

Example Input Device Program

Swap_Buttons.c

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <devices/input.h>
#include <intuition/intuition.h>
 
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
 
struct IntuitionIFace *IIntuition = NULL;
 
UBYTE NameString[] = "Swap Buttons";
 
struct NewWindow mywin={50,40,124,18,0,1,CLOSEWINDOW,
                        WINDOWDRAG|WINDOWCLOSE|SIMPLE_REFRESH|NOCAREREFRESH,
                        NULL,NULL,NameString,NULL,NULL,0,0,0,0,WBENCHSCREEN};
 
extern VOID ButtonSwap();
 
/*
 * This routine opens a window and waits for the one event that
 * can happen (CLOSEWINDOW)  This is just to let the user play with
 * the swapped buttons and then close the program...
 */
VOID WaitForUser(VOID)
{
struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
IIntuition = (struct IntuitionIFace *)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
 
if (IIntuition != NULL)
    {
    struct Window *win = IIntuition->OpenWindow(&mywin);
    if (win != NULL)
        {
        IExec->WaitPort(win->UserPort);
        IExec->ReplyMsg(IExec->GetMsg(win->UserPort));
 
        IIntuition->CloseWindow(win);
        }
    }
 
IExec->DropInterface((struct Interface *)IIntuition);
IExec->CloseLibrary(IntuitionBase);
}
 
int main()
{
struct MsgPort *inputPort = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
if (inputPort != NULL)
    {
    struct Interrupt *inputHandler = IExec->AllocSysObjectTags(ASOT_INTERRUPT,
        ASOINTR_Code, ButtonSwap,
        TAG_END);
 
    if (inputHandler != NULL)
        {
        struct IOStdReq *inputReqBlk = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
            ASOIOR_Size, sizeof(struct IOStdReq),
            ASOIOR_ReplyPort, inputPort,
            TAG_END);
 
        if (inputReqBlk != NULL)
            {
            if (!IExec->OpenDevice("input.device", NULL,
                             (struct IORequest *)inputReqBlk, NULL))
                {
                inputHandler->is_Node.ln_Pri  = 100;
                inputHandler->is_Node.ln_Name = NameString;
 
                inputReqBlk->io_Data    = (APTR)inputHandler;
                inputReqBlk->io_Command = IND_ADDHANDLER;
                IExec->DoIO((struct IORequest *)inputReqBlk);
 
                WaitForUser();
 
                inputReqBlk->io_Data    = (APTR)inputHandler;
                inputReqBlk->io_Command = IND_REMHANDLER;
                IExec->DoIO((struct IORequest *)inputReqBlk);
 
                IExec->CloseDevice((struct IORequest *)inputReqBlk);
                }
            else
                IDOS->Printf("Error: Could not open input.device\n");
 
            IExec->FreeSysObject(ASOT_IOREQUEST, inputReqBlk);
            }
        else
            IDOS->Printf("Error: Could not create I/O request\n");
 
        IExec->FreeSysObject(ASOT_INTERRUPT, inputHandler);
        }
    else
        IDOS->Printf("Error: Could not allocate interrupt\n");
 
    IExec->FreeSysObject(ASOT_PORT, inputPort);
    }
else
    IDOS->Printf("Error: Could not create message port\n");
 
return 0;
}

InputHandler.a

****************************************************************************
*       InputHandler.a
*
* InputHandler that does a Left/Right mouse button swap...
*
* See Swap_Buttons.c for details on how to compile/assemble/link...
*
************************************************************************
*
* Required includes...
*
        INCDIR  "include:"
        INCLUDE "exec/types.i"
        INCLUDE "exec/io.i"
        INCLUDE "devices/inputevent.i"
*
************************************************************************
*
* Make the entry point external...
*
        xdef    _ButtonSwap
*
************************************************************************
*
* This is the input handler that will swap the
* mouse buttons for left handed use.
*
* The event list gets passed to you in  a0.
* The is_Data field is passed to you in a1.
* This example does not use the is_Data field...
*
* On exit you must return the event list in d0.  In this way
* you could add or remove items from the event list.
*
* The handler gets called here...
*
*
_ButtonSwap:    move.l  a0,-(sp)        ; Save the event list
*
* Since the event list could be a linked list, we start a loop
* here to handle all of the events passed to us.
*
CheckLoop:      move.w  ie_Qualifier(a0),d1             ; Get qualifiers...
                move.w  d1,d0                           ; Two places...
*
* Since we are changing left and right mouse buttons, we need to make
* sure that we change the qualifiers on all of the messages.  The
* left and right mouse buttons are tracked in the message qualifiers
* for use in such things as dragging.  To make sure that we continue
* to drag correctly, we change the qualifiers.
*
CheckRight:     btst    #IEQUALIFIERB_RBUTTON,d1        ; Check for right
                beq.s   NoRight
                bset    #IEQUALIFIERB_LEFTBUTTON,d0     ; Set the left...
                beq.s   CheckLeft
NoRight:        bclr    #IEQUALIFIERB_LEFTBUTTON,d0     ; Clear the left...
*
CheckLeft:      btst    #IEQUALIFIERB_LEFTBUTTON,d1     ; Check for left
                beq.s   NoLeft
                bset    #IEQUALIFIERB_RBUTTON,d0        ; Set the right...
                beq.s   SaveQual
NoLeft:         bclr    #IEQUALIFIERB_RBUTTON,d0        ; Clear the right...
*
SaveQual:       move.w  d0,ie_Qualifier(a0)             ; Save back...
*
* The actual button up/down events are transmitted as the
* code field in RAWMOUSE events.  The code field must the be
* checked and modified when needed on RAWMOUSE events.  If the
* event is not a RAWMOUSE, we are done with it.
*
                cmp.b   #IECLASS_RAWMOUSE,ie_Class(a0)  ; Check for mouse
                bne.s   NextEvent                       ; If not, next...
*
                move.w  ie_Code(a0),d0                  ; Get code...
                move.w  d0,d1                           ; Save...
                and.w   #$7F,d0                         ; Mask UP_PREFIX
                cmp.w   #IECODE_LBUTTON,d0              ; Check for Left...
                beq.s   SwapThem                        ; If so, swap...
                cmp.w   #IECODE_RBUTTON,d0              ; Check for Right...
                bne.s   NextEvent                       ; If not, next...
*
SwapThem:       eor.w   #1,d1                           ; Flip bottom bit
                move.w  d1,ie_Code(a0)                  ; Save it...
*
* The event list is linked via a pointer to the next event
* in the first element of the structure.  That is why it is not
* nessesary to use:  move.l ie_NextEvent(a0),d0
*
* The reason I move to d0 first is that this also checks for zero.
* The last event in the list will have a NULL ie_NextEvent field.
* This is NOT as standard EXEC list where the node after the last
* node is NULL.  Input events are single-linked for performance.
*
NextEvent:      move.l  (a0),d0                         ; Get next event
                move.l  d0,a0                           ; into a0...
                bne.s   CheckLoop                       ; Do some more.
*
* All done, just return the event list...  (in d0)
*
                move.l  (sp)+,d0        ; Get event list back...
                rts                     ; return from handler...

Additional Information on the Input Device

Additional programming information on the input device can be found in the include files and the autodocs for the input device. Both are contained in the SDK.

Includes
devices/input.h
devices/inputevent.h
AutoDocs
input.doc