Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Keyboard Device"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
 
Line 472: Line 472:
 
Reading keyboard events is normally not done through direct access to the keyboard device. (Higher level devices such as the input device and console device are available for this. See [[Input Device]] for more information on the intimate linkage between the input device and the keyboard device.) This section is provided primarily to show you the component parts of a keyboard input event.
 
Reading keyboard events is normally not done through direct access to the keyboard device. (Higher level devices such as the input device and console device are available for this. See [[Input Device]] for more information on the intimate linkage between the input device and the keyboard device.) This section is provided primarily to show you the component parts of a keyboard input event.
   
The keyboard matrix figure shown at the beginning of this chapter gives the code value that each key places into the ie_Code field of the input event for a key-down event. For a key-up event, a value of hexadecimal 80 is or’ed with the value shown above. Additionally, if either shift key is down, or if the key is one of those in the numeric keypad, the qualifier field of the keyboard input event will be filled in accordingly.
+
The keyboard matrix figure shown at the beginning of this article gives the code value that each key places into the ie_Code field of the input event for a key-down event. For a key-up event, a value of hexadecimal 80 is or’ed with the value shown above. Additionally, if either shift key is down, or if the key is one of those in the numeric keypad, the qualifier field of the keyboard input event will be filled in accordingly.
   
 
When you ask to read events from the keyboard, the call will not be satisfied until at least one keyboard event is available to be returned. The io_Length field must contain the number of bytes available in io_Data to insert events into. Thus, you should use a multiple of the number of bytes in an InputEvent (see example below).
 
When you ask to read events from the keyboard, the call will not be satisfied until at least one keyboard event is available to be returned. The io_Length field must contain the number of bytes available in io_Data to insert events into. Thus, you should use a multiple of the number of bytes in an InputEvent (see example below).

Latest revision as of 20:16, 4 November 2015

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.

Keyboard Device

The keyboard device gives low-level access to the Amiga keyboard. When you send this device the command to read one or more keystrokes from the keyboard, for each keystroke (whether key-up or key-down) the keyboard device creates a data structure called an input event to describe what happened. The keyboard device also provides the ability to do operations within the system reset processing (Ctrl-Amiga-Amiga).

Keyboard Device Commands and Functions

Command Command Operation
CMD_CLEAR Clear the keyboard input buffer. Removes any key transitions from the input buffer.
KBD_ADDRESETHANDLER Add a reset handler function to the list of functions called by the keyboard device to clean up before a hard reset.
KBD_REMRESETHANDLER Remove a previously added reset handler from the list of functions called by the keyboard device to clean up before a hard reset.
KBD_RESETHANDLERDONE Indicate that a handler has completed its job and reset could possibly occur now.
KBD_READMATRIX Read the state of every key in the keyboard. Tells the up/down state of every key.
KBD_READEVENT Read one (or more) raw key event from the keyboard device.

Device Interface

The keyboard device operates like the other Amiga devices. To use it, you must first open the keyboard 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.

The I/O request used by the keyboard device is called IOStdReq.

struct IOStdReq
{
    struct  Message io_Message;
    struct  Device  *io_Device;     /* device node pointer  */
    struct  Unit    *io_Unit;       /* unit (driver private)*/
    UWORD   io_Command;             /* device command */
    UBYTE   io_Flags;
    BYTE    io_Error;               /* error or warning num */
    ULONG   io_Actual;              /* actual number of bytes transferred */
    ULONG   io_Length;              /* requested number bytes transferred*/
    APTR    io_Data;                /* points to data area */
    ULONG   io_Offset;              /* offset for block structured devices */
};

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

Opening the Keyboard Device

Three primary steps are required to open the keyboard device:

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

Closing the Keyboard Device

An 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() and remove them with WaitIO().

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

Reading the Keyboard Matrix

The KBD_READMATRIX command returns the current state of every key in the key matrix (up = 0, down = 1). You provide a data area that is at least large enough to hold one bit per key, approximately 16 bytes. The keyboard layout for the A500, A2000 and A3000 is shown in the figure below, indicating the raw numeric value that each key transmits when it is pressed. This value is the numeric position that the key occupies in the key matrix.

DevFig7-1.png

The following example will read the key matrix and display the up-down state of all of the elements in the matrix in a table. Reading the column header and then the row number as a hex number gives you the raw key code.

/*
 * Read_Keyboard_Matrix.c
 *
 * Compile with gcc (GCC) 4.2.4
 * gcc -o Read_Keyboard_Matrix Read_Keyboard_Matrix.c
 * 
 * Run from CLI only
 */
 
#include <proto/exec.h>
#include <proto/dos.h>
#include <devices/keyboard.h>
 
/*
 * There are keycodes from 0x00 to 0x7F, so the matrix needs to be
 * of 0x80 bits in size, or 0x80/8 which is 0x10 or 16 bytes...
 */
#define MATRIX_SIZE 16L
 
/*
 * This assembles the matrix for display that translates directly
 * to the RAW key value of the key that is up or down
 */
 
void Display_Matrix ( uint8 *keyMatrix )
{
  int16 bitcount;
  int16 bytecount;
  int16 mask;
  uint16 twobyte;
 
  IDOS->Printf( "\n    0 1 2 3 4 5 6 7" );
  IDOS->Printf( "\n  +-----------------" );
  for ( bitcount = 0; bitcount < 16; bitcount++ )
  {
    IDOS->Printf( "\n%lx |", bitcount );
    mask = ( 1 << bitcount );
    for ( bytecount = 0; bytecount < 16; bytecount += 2 )
    {
      twobyte = keyMatrix [ bytecount ] | ( keyMatrix [ bytecount + 1 ] << 8 );
      if ( twobyte & mask )
      {
        IDOS->Printf( " *" );
      }
      else
      {
        IDOS->Printf( " -" );
      }
    }
  }
 
  IDOS->Printf( "\n\n" );
}
 
int main ( int argc, char *argv[] )
{
  struct IOStdReq *KeyIO;
  struct MsgPort *KeyMP;
  uint8 *keyMatrix;
 
  if ( KeyMP = IExec->AllocSysObjectTags ( ASOT_PORT, TAG_END ) )
  {
    if ( KeyIO = IExec->AllocSysObjectTags ( ASOT_IOREQUEST, ASOIOR_ReplyPort, KeyMP, ASOIOR_Size, sizeof ( struct IOStdReq ), TAG_END ) )
    {
      if ( ! IExec->OpenDevice ( "keyboard.device", 0, ( struct IORequest * ) KeyIO, 0 ) )
      {
        if ( keyMatrix = IExec->AllocVecTags ( MATRIX_SIZE, AVT_ClearWithValue, 0, TAG_END ) )
        {
          KeyIO->io_Command = KBD_READMATRIX;
          KeyIO->io_Data    = ( APTR ) keyMatrix;
          KeyIO->io_Length  = MATRIX_SIZE;
          IExec->DoIO ( ( struct IORequest *) KeyIO );
 
          /* Check for CLI startup... */
          if ( argc )
          {
            Display_Matrix( keyMatrix );
          }
 
          IExec->FreeVec ( keyMatrix );
        }
        else
        {
          IDOS->Printf( "Error: Could not allocate keymatrix memory\n" );
        }
 
        IExec->CloseDevice ( ( struct IORequest * ) KeyIO );
      }
      else
      {
        IDOS->Printf( "Error: Could not open keyboard.device\n" );
      }
 
      IExec->FreeSysObject ( ASOT_IOREQUEST, KeyIO );
    }
    else
    {
      IDOS->Printf( "Error: Could not create I/O request\n" );
    }
 
    IExec->FreeSysObject ( ASOT_PORT, KeyMP );
  }
  else
  {
    IDOS->Printf( "Error: Could not create message port\n" );
  }
 
  return ( 0 );
}

In addition to the matrix data returned in io_Data, io_Actual returns the number of bytes filled in io_Data with key matrix data, i.e., the minimum of the supplied length and the internal key matrix size.

Value of io_Length
A value of 13 in the io_Length field will be sufficient for most keyboards; extended keyboards will require a larger number.

To find the status of a particular key—for example, to find out if the F2 key is down—you find the bit that specifies the current state by dividing the key matrix value by 8. Since hex 51 = 81, this indicates that the bit is in byte number 10 of the matrix. Then take the same number (decimal 81) and use modulo 8 to determine which bit position within that byte represents the state of the key. This yields a value of 1. So, by reading bit position 1 of byte number 10, you determine the status of the function key F2.

Amiga Reset Handling

When a user presses the Ctrl key and both left- and right-Amiga keys simulataneously (the reset sequence), the keyboard device senses this and calls a prioritized chain of reset-handlers. These might be thought of as clean-up routines that “must” be performed before reset is allowed to occur. For example, if a disk write is in progress, the system should finish that before resetting the hardware so as not to corrupt the contents of the disk.

It is important to note that not all Amigas handle reset processing in the same way. On the A500, the reset key sequence sends a hardware reset signal and never goes through the reset handlers. Also some of the early A2000s (i.e., German keyboards with the function keys the same size as the Esc key) do not handle the reset via the reset handlers. It is thus recommended that your application not rely on the reset handler abilities of the keyboard device.

Adding a Reset Handler

The KBD_ADDRESETHANDLER command adds a custom routine to the chain of reset-handlers. Reset handlers are just like any other handler and are added to the handler list with an Interrupt structure. The priority field in the list node of the Interrupt structure establishes the sequence in which reset handlers are processed by the system. Keyboard reset handlers are currently limited to the priority values of a software interrupt, that is, values of -32, -16, 0, 16, and 32.

The io_Data field of the I/O request is filled in with a pointer to the Interrupt structure and the io_Command field is set to KBD_ADDRESETHANDLER. These are the only two fields you need to initialize to add a reset handler. Any return value from the command is ignored. All keyboard reset handlers are activated if time permits. Normally, a reset handler will just signal the requisite task and return. The task then does whatever processing it needs to do and notifies the system that it is done by using the KBD_RESETHANDLERDONE command described below.

Non-interference and speed are the keys to success
If you add your own handler to the chain, you must ensure that your handler allows the rest of reset processing to occur. Reset must continue to function. Also, if you don’t execute your reset code fast enough, the system will still reboot (about 10 seconds).

Removing a Reset Handler

This command is used to remove a keyboard reset handler from the system. You need to supply the same Interrupt structure to this command that you used with the KBD_ADDRESETHANDLER command.

Ending a Reset Task

This command tells the system that your reset handling code has completed. If you are the last outstanding reset handler, the system will reset after this call.

Can’t Stop, Got No Brakes
After 10 seconds, the system will reboot, regardless of outstanding reset handlers.

Here is an example program that installs a reset handler and either waits for the reboot or for the user to close the window. If there was a reboot, the window will close and, if executed from the shell, it will display a few messages. If the user closes the window, the handler is removed and the program exits cleanly.

/*
 * Key_Reset.c
 *
 * This is in two parts...
 *
 * Compile this C code with SAS C 5.10:
 *      lc -b1 -cfistq -v -y Key_Reset
 *
 * Assemble the ASM code with Adapt
 *  HX68 KeyHandler.a to KeyHandler.o
 *
 * Link with:
 *      Blink FROM LIB:c.o+Key_Reset.o+KeyHandler.o TO Key_Reset LIB LIB:lc.lib LIB:amiga.lib
 */

/*
 * Keyboard device reset handler example...
 */
#include <exec/types.h>
#include <exec/io.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <devices/keyboard.h>
#include <intuition/intuition.h>
#include <exec/interrupts.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/intuition_protos.h>
#include <clib/dos_protos.h>

#include <stdio.h>

#ifdef LATTICE
int CXBRK(void) { return(0); }     /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); }  /* really */
void main();
#endif

extern VOID ResetHandler();

UBYTE NameString[]="Reset Handler Test";

struct NewWindow mywin={0,0,178,10,0,1,CLOSEWINDOW,
                        WINDOWDRAG|WINDOWCLOSE|SIMPLE_REFRESH|NOCAREREFRESH,
                        NULL,NULL,NameString,NULL,NULL,0,0,0,0,WBENCHSCREEN};

extern struct IntuitionBase *IntuitionBase;

struct MyData
    {
    struct Task  *MyTask;
           ULONG MySignal;
    };

/*
 * This routine opens a window and waits for the one event that
 * can happen (CLOSEWINDOW)
 */
short WaitForUser(ULONG MySignal)
{
struct Window  *win;
       short   ret=0;

if (IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",0L))
    {
    if (win=(struct Window *)OpenWindow(&mywin))
        {
        ret=(MySignal==Wait(MySignal | (1L << win->UserPort->mp_SigBit)));
        CloseWindow(win);
        }
    else
        printf("Error: Could not open window\n");
    CloseLibrary((struct Library *)IntuitionBase);
    }
else
    printf("Error: Could not open intution.library\n");
return(ret);
}

VOID main(int argc, char *argv[])
{
struct IOStdReq  *KeyIO;
struct MsgPort   *KeyMP;
struct Interrupt *keyHandler;
struct MyData    MyDataStuff;
       ULONG     MySignal;

if ((MySignal=AllocSignal(-1L))!=-1)
    {
    MyDataStuff.MyTask=FindTask(NULL);
    MyDataStuff.MySignal=1L << MySignal;

    if (KeyMP=CreatePort(NULL,NULL))
        {
        if (keyHandler=AllocMem(sizeof(struct Interrupt),MEMF_PUBLIC|MEMF_CLEAR))
            {
            if (KeyIO=(struct IOStdReq *)CreateExtIO(KeyMP,sizeof(struct IOStdReq)))
                {
                if (!OpenDevice("keyboard.device",NULL,(struct IORequest *)KeyIO,NULL))
                    {
                    keyHandler->is_Code=ResetHandler;
                    keyHandler->is_Data=(APTR)&MyDataStuff;

                    /*
                     * Note that only software interrupt priorities
                     * can be used for the .ln_Pri on the reset
                     * handler...
                     */
                    keyHandler->is_Node.ln_Pri=16;

                    keyHandler->is_Node.ln_Name=NameString;
                    KeyIO->io_Data=(APTR)keyHandler;
                    KeyIO->io_Command=KBD_ADDRESETHANDLER;
                    DoIO((struct IORequest *)KeyIO);

                    if (WaitForUser(MyDataStuff.MySignal))
                        {
                        if (argc) /* Check for CLI */
                            {
                            printf("System going down\n");
                            printf("Cleaning up...\n");
                            /* Show a delay, like cleanup... */
                            Delay(20);
                            printf("*Poof*\n");
                            }
                        /* We are done with our cleanup */

                        KeyIO->io_Data=(APTR)keyHandler;
                        KeyIO->io_Command=KBD_RESETHANDLERDONE;
                        DoIO((struct IORequest *)KeyIO);
                        /*
                         * Note that since the above call
                         * tells the system it is safe to reboot
                         * and will cause the reboot if this
                         * task was the last to say so, the call
                         * never really returns...  The system
                         * just reboots...
                         */
                        }

                    KeyIO->io_Data=(APTR)keyHandler;
                    KeyIO->io_Command=KBD_REMRESETHANDLER;
                    DoIO((struct IORequest *)KeyIO);

                    CloseDevice((struct IORequest *)KeyIO);
                    }
                else
                    printf("Error: Could not open keyboard.device\n");

                DeleteExtIO((struct IORequest *)KeyIO);
                }
            else
                printf("Error: Could not create I/O request\n");

            FreeMem(keyHandler,sizeof(struct Interrupt));
            }
        else
            printf("Error: Could not allocate memory for interrupt\n");

        DeletePort(KeyMP);
        }
    else
        printf("Error: Could not create message port\n");

    FreeSignal(MySignal);
    }
else
    printf("Error: Could not allocate signal\n");
}
****************************************************************************
*       KeyHandler.a
*
* Keyboard reset handler that signals the task in the structure...
*
* See Key_Reset.c for details on how to compile/assemble/link...
*
************************************************************************
* Required includes...
*
        INCDIR  "include:"
        INCLUDE "exec/types.i"
        INCLUDE "exec/io.i"
        INCLUDE "devices/keyboard.i"
*
        xref    _AbsExecBase    ; We get this from outside...
        xref    _LVOSignal      ; We get this from outside...
*
************************************************************************
* Make the entry point external...
*
        xdef    _ResetHandler
*
************************************************************************
*
* This is the input handler
* The is_Data field is passed to you in a1.
*
* This is the structure that is passed in A1 in this example...
*
        STRUCTURE       MyData,0
        APTR            MyTask
        ULONG           MySignal
*
************************************************************************
* The handler gets called here...
*
_ResetHandler:  move.l  MySignal(a1),d0 ; Get signal to send
                move.l  MyTask(a1),a1           ; Get task
*
* Now signal the task...
*
                move.l  a6,-(sp)        ; Save the stack...
                move.l  _AbsExecBase,a6 ; Get ExecBase
                jsr     _LVOSignal(a6)  ; Send the signal
                move.l  (sp)+,a6        ; Restore A6
*
* Return to let other handlers execute.
*
                rts                     ; return from handler...
*
                END
************************************************************************

Reading Keyboard Events

Reading keyboard events is normally not done through direct access to the keyboard device. (Higher level devices such as the input device and console device are available for this. See Input Device for more information on the intimate linkage between the input device and the keyboard device.) This section is provided primarily to show you the component parts of a keyboard input event.

The keyboard matrix figure shown at the beginning of this article gives the code value that each key places into the ie_Code field of the input event for a key-down event. For a key-up event, a value of hexadecimal 80 is or’ed with the value shown above. Additionally, if either shift key is down, or if the key is one of those in the numeric keypad, the qualifier field of the keyboard input event will be filled in accordingly.

When you ask to read events from the keyboard, the call will not be satisfied until at least one keyboard event is available to be returned. The io_Length field must contain the number of bytes available in io_Data to insert events into. Thus, you should use a multiple of the number of bytes in an InputEvent (see example below).

Type-Ahead Processing
The keyboard device can queue up several keystrokes without a task requesting a report of keyboard events. However, when the keyboard event buffer has been filled with no task interaction, additional keystrokes will be discarded.

Example Read Keyboard Event Program

Shown below is an example keyboard.device read-event program:

/*
 * Keyboard_Events.c
 *
 * This example does not work very well in a system where
 * input.device is active since input.device also actively calls for
 * keyboard events via this call. For that reason, you will not get all of
 * the keyboard events. Neither will the input device; no one will be happy.
 *
 * Run from CLI only
 */
 
#include <exec/types.h>
#include <exec/io.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <devices/inputevent.h>
#include <devices/keyboard.h>
 
#include <proto/dos.h>
#include <proto/exec.h>
 
VOID Display_Event(struct InputEvent *keyEvent)
{
IDOS->Printf("Got key event: KeyCode: %2x  Quailifiers: %4x\n",
               keyEvent->ie_Code,
               keyEvent->ie_Qualifier);
}
 
int main(int argc, char **argv)
{
struct MsgPort *keyPort = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
if (keyPort != NULL)
    {
    struct IOStdReq *keyRequest = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
        ASOIOR_Size, sizeof(struct IOStdReq),
        ASOIOR_ReplyPort, keyPort,
        TAG_END);
 
    if (keyRequest != NULL)
        {
        if (!IExec->OpenDevice("keyboard.device", NULL, (struct IORequest *)keyRequest, NULL))
            {
            struct InputEvent *keyEvent = IExec->AllocVecTags(sizeof(struct InputEvent), TAG_END);
            if (keyEvent != NULL)
                {
                for (int16 loop=0;loop<4;loop++)
                     {
                     keyRequest->io_Command = KBD_READEVENT;
                     keyRequest->io_Data = (APTR)keyEvent;
 
                     /*
                      * We want 1 event, so we just set the
                      * length field to the size, in bytes
                      * of the event.  For multiple events,
                      * set this to a multiple of that size.
                      * The keyboard device NEVER fills partial
                      * events...
                      */
 
                     keyRequest->io_Length = sizeof(struct InputEvent);
                     IExec->DoIO((struct IORequest *)keyRequest);
 
                         /* Check for CLI startup... */
                     if (argc)
                         Display_Event(keyEvent);
                     }
 
                IExec->FreeVec(keyEvent);
                }
            else
                IDOS->Printf("Error: Could not allocate memory for InputEvent\n");
 
            IExec->CloseDevice((struct IORequest *)keyRequest);
            }
        else
            IDOS->Printf("Error: Could not open keyboard.device\n");
 
        IExec->FreeSysObject(ASOT_IOREQUEST, keyRequest);
        }
    else
        IDOS->Printf("Error: Could not create I/O request\n");
 
    IExec->FreeSysObject(ASOT_PORT, keyPort);
    }
else
    IDOS->Printf("Error: Could not create message port\n");
 
return 0;
}

Additional Information on the Keyboard Device

Additional programming information on the keyboard device can be found in the include files for the keyboard and input devices and the Autodocs for the keyboard device. All are contained in the SDK.

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