Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Keyboard Device"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
m (Adde to WIP category)
Line 1: Line 1:
  +
{{WIP}}
 
== Keyboard Device ==
 
== Keyboard Device ==
   

Revision as of 16:20, 12 April 2012

WIP.png This page is currently being updated to AmigaOS 4.x. Some of the information contained here may not yet be applicable in part or totally.

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.

Exec Functions as Used in This Chapter:

<tbody> </tbody>
AbortIO() 8.9cmAbort a command to the keyboard device.
AllocMem() Allocate a block of memory.
CheckIO() Return the status of an I/O request.
CloseDevice() Relinquish use of the keyboard device.
DoIO() 8.9cmInitiate a command and wait for it to complete (synchronous request).
FreeMem() Free a block of previously allocated memory.
OpenDevice() Obtain use of the keyboard device.
SendIO() 8.9cmInitiate a command and return immediately (asynchronous request).
WaitIO() 8.9cmWait for the completion of an asynchronous request. When the request is complete the message will be removed from reply port.

Exec Support Functions as Used in This Chapter:

<tbody> </tbody>
CreateExtIO() 8.9cmCreate an extended I/O request structure. This structure will be used to communicate commands to the keyboard device.
CreatePort() 8.9cmCreate a signal message port for reply messages from the keyboard device. Exec will signal a task when a message arrives at the port.
DeleteExtIO() 8.9cmDelete an extended I/O request structure created by CreateExtIO().
DeletePort() 8.9cmDelete the message port created by CreatePort().

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 the “Introduction to Amiga System Devices” chapter 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 the CreatePort() function.
  • Create an extended I/O request structure using the CreateExtIO() function. CreateExtIO() will initialize the I/O request with your reply port.
  • Open the keyboard device. Call OpenDevice(), passing the I/O request.
struct MsgPort  *KeyMP;         /* Pointer for Message Port */
struct IOStdReq *KeyIO;         /* Pointer for I/O request */

if (KeyMP=CreatePort(NULL,NULL))
    if (KeyIO=(struct IOStdReq *)
               CreateExtIO(KeyMP,sizeof(struct IOStdReq)) )
        if (OpenDevice("keyboard.device",NULL,(struct IORequest *)KeyIO,NULL))
            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 (!(CheckIO(KeyIO)))
    {
    AbortIO(KeyIO);  /* Ask device to abort request, if pending */
    }
    WaitIO(KeyIO);   /* Wait for abort, then clean up */
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 SAS C 5.10  lc -b1 -cfistq -v -y -L
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#include <devices/keyboard.h>

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

#include <stdio.h>

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


/*
 * 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(UBYTE *keyMatrix)
{
SHORT  bitcount;
SHORT  bytecount;
SHORT   mask;
USHORT twobyte;

printf("\n    0 1 2 3 4 5 6 7");
printf("\n  +-----------------");
for (bitcount=0;bitcount<16;bitcount++)
    {
    printf("\n%x |",bitcount);
    mask=1 << bitcount;
    for (bytecount=0;bytecount<16;bytecount+=2)
        {
        twobyte=keyMatrix[bytecount] | (keyMatrix[bytecount+1] << 8);
        if (twobyte & mask)
            printf(" *");
        else
            printf(" -");
        }
    }
printf("\n\n");
}


void main(int argc, char *argv[])
{
extern struct Library *SysBase;
struct IOStdReq *KeyIO;
struct MsgPort  *KeyMP;
UBYTE    *keyMatrix;

if (KeyMP=CreatePort(NULL,NULL))
    {
    if (KeyIO=(struct IOStdReq *)CreateExtIO(KeyMP,sizeof(struct IOStdReq)))
        {
        if (!OpenDevice("keyboard.device",NULL,(struct IORequest *)KeyIO,NULL))
            {
            if (keyMatrix=AllocMem(MATRIX_SIZE,MEMF_PUBLIC|MEMF_CLEAR))
                {
                KeyIO->io_Command=KBD_READMATRIX;
                KeyIO->io_Data=(APTR)keyMatrix;
                KeyIO->io_Length= SysBase->lib_Version >= 36 ? MATRIX_SIZE : 13;
                DoIO((struct IORequest *)KeyIO);

                /* Check for CLI startup... */
                if (argc)
                    Display_Matrix(keyMatrix);

                FreeMem(keyMatrix,MATRIX_SIZE);
                }
            else
                printf("Error: Could not allocate keymatrix memory\");

            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");

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

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.

boxValue 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. However, you must always set this field to 13 for V34 and earlier versions of Kickstart.

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.

boxNon-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.

boxCan’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 the chapter “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. In V34 and earlier versions of Kickstart, the keyboard device does not set the numeric qualifier for the keypad keys ‘(’, ‘)’, ‘/’, ‘*’ and ‘+’.

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).

boxType-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.
 *
 * Compile with SAS 5.10  lc -b1 -cfistq -v -y -L
 *
 * 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 <clib/exec_protos.h>
#include <clib/alib_protos.h>

#include <stdio.h>

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

VOID Display_Event(struct InputEvent *keyEvent)
{
printf("Got key event: KeyCode: %2x  Quailifiers: %4x\n",
               keyEvent->ie_Code,
               keyEvent->ie_Qualifier);
}

VOID main(int argc, char *argv[])
{
struct IOStdReq   *keyRequest;
struct MsgPort    *keyPort;
struct InputEvent *keyEvent;
       SHORT      loop;

if (keyPort=CreatePort(NULL,NULL))
    {
    if (keyRequest=(struct IOStdReq *)CreateExtIO(keyPort,sizeof(struct IOStdReq)))
        {
        if (!OpenDevice("keyboard.device",NULL,(struct IORequest *)keyRequest,NULL))
            {
            if (keyEvent=AllocMem(sizeof(struct InputEvent),MEMF_PUBLIC))
                {
                for (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);
                     DoIO((struct IORequest *)keyRequest);

                         /* Check for CLI startup... */
                     if (argc)
                         Display_Event(keyEvent);
                     }

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

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

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

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

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 Amiga ROM Kernel Reference Manual: Includes and Autodocs.

|ll|

2cKeyboard Device Information
Includes & devices/keyboard.h
& devices/keyboard.i
& devices/inputevent.h
& devices/inputevent.i
AutoDocs & keyboard.doc