283 lines
8.8 KiB
C
283 lines
8.8 KiB
C
// Amiga keyboard + mouse input: opens a backdrop borderless window on
|
|
// the JoeyLib CUSTOMSCREEN to receive IDCMP_RAWKEY, IDCMP_MOUSEMOVE,
|
|
// and IDCMP_MOUSEBUTTONS events, then drains those messages each
|
|
// joeyInputPoll.
|
|
//
|
|
// RAWKEY is the lowest-level IDCMP event Intuition exposes: ie_Code
|
|
// carries the Amiga raw key code (press in 0x00..0x7F, release with
|
|
// bit 7 set), which we map straight to JoeyKeyE entries. The backdrop
|
|
// borderless window covers the whole screen without drawing over our
|
|
// planar bitmap (c2p writes to Screen->BitMap->Planes directly), and
|
|
// WFLG_ACTIVATE ensures keyboard focus lands on us from the start.
|
|
//
|
|
// MOUSEMOVE messages carry MouseX/MouseY in window-relative pixels;
|
|
// since the window is at (0,0) covering the whole screen, those are
|
|
// also surface-space coordinates. WA_ReportMouse = TRUE is required
|
|
// for MOUSEMOVE events to fire continuously rather than only on button
|
|
// transitions. WFLG_RMBTRAP keeps the right mouse button from opening
|
|
// Intuition's screen menu so right-clicks come to us as MENUDOWN.
|
|
|
|
#include <string.h>
|
|
|
|
#include <exec/types.h>
|
|
#include <exec/ports.h>
|
|
#include <intuition/intuition.h>
|
|
#include <intuition/screens.h>
|
|
#include <devices/inputevent.h>
|
|
#include <libraries/lowlevel.h>
|
|
|
|
#include <proto/exec.h>
|
|
#include <proto/intuition.h>
|
|
#include <proto/lowlevel.h>
|
|
|
|
#include "hal.h"
|
|
#include "inputInternal.h"
|
|
|
|
// ----- Constants -----
|
|
|
|
#define AMIGA_KEY_TABLE_SIZE 128
|
|
#define AMIGA_KEY_RELEASE_BIT 0x80
|
|
#define AMIGA_KEY_CODE_MASK 0x7F
|
|
|
|
// ----- External from hal.c -----
|
|
|
|
extern struct Screen *gScreen;
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void drainMessages(void);
|
|
static void pollJoysticks(void);
|
|
|
|
// ----- Module state -----
|
|
|
|
// Amiga raw-key code -> JoeyKeyE. Codes are layout-independent; the
|
|
// OS has a separate keymap for producing characters.
|
|
static const uint8_t gRawKeyToKey[AMIGA_KEY_TABLE_SIZE] = {
|
|
[0x01] = KEY_1,
|
|
[0x02] = KEY_2,
|
|
[0x03] = KEY_3,
|
|
[0x04] = KEY_4,
|
|
[0x05] = KEY_5,
|
|
[0x06] = KEY_6,
|
|
[0x07] = KEY_7,
|
|
[0x08] = KEY_8,
|
|
[0x09] = KEY_9,
|
|
[0x0A] = KEY_0,
|
|
[0x10] = KEY_Q,
|
|
[0x11] = KEY_W,
|
|
[0x12] = KEY_E,
|
|
[0x13] = KEY_R,
|
|
[0x14] = KEY_T,
|
|
[0x15] = KEY_Y,
|
|
[0x16] = KEY_U,
|
|
[0x17] = KEY_I,
|
|
[0x18] = KEY_O,
|
|
[0x19] = KEY_P,
|
|
[0x20] = KEY_A,
|
|
[0x21] = KEY_S,
|
|
[0x22] = KEY_D,
|
|
[0x23] = KEY_F,
|
|
[0x24] = KEY_G,
|
|
[0x25] = KEY_H,
|
|
[0x26] = KEY_J,
|
|
[0x27] = KEY_K,
|
|
[0x28] = KEY_L,
|
|
[0x31] = KEY_Z,
|
|
[0x32] = KEY_X,
|
|
[0x33] = KEY_C,
|
|
[0x34] = KEY_V,
|
|
[0x35] = KEY_B,
|
|
[0x36] = KEY_N,
|
|
[0x37] = KEY_M,
|
|
[0x40] = KEY_SPACE,
|
|
[0x41] = KEY_BACKSPACE,
|
|
[0x42] = KEY_TAB,
|
|
[0x44] = KEY_RETURN,
|
|
[0x45] = KEY_ESCAPE,
|
|
[0x4C] = KEY_UP,
|
|
[0x4D] = KEY_DOWN,
|
|
[0x4E] = KEY_RIGHT,
|
|
[0x4F] = KEY_LEFT,
|
|
[0x50] = KEY_F1,
|
|
[0x51] = KEY_F2,
|
|
[0x52] = KEY_F3,
|
|
[0x53] = KEY_F4,
|
|
[0x54] = KEY_F5,
|
|
[0x55] = KEY_F6,
|
|
[0x56] = KEY_F7,
|
|
[0x57] = KEY_F8,
|
|
[0x58] = KEY_F9,
|
|
[0x59] = KEY_F10,
|
|
[0x60] = KEY_LSHIFT,
|
|
[0x61] = KEY_RSHIFT,
|
|
[0x63] = KEY_LCTRL,
|
|
[0x64] = KEY_LALT,
|
|
};
|
|
|
|
static struct Window *gWindow = NULL;
|
|
static struct Library *gLowLevelBase = NULL;
|
|
|
|
// AmigaOS exposes joystick port 1 (the dedicated stick) via lowlevel
|
|
// ReadJoyPort(1); port 0 shares the mouse port. We map our public
|
|
// JOYSTICK_0 to Amiga port 1 so the primary stick is what retro games
|
|
// expect.
|
|
#define AMIGA_PORT_FOR_JS0 1
|
|
#define AMIGA_PORT_FOR_JS1 0
|
|
|
|
// ----- Internal helpers -----
|
|
|
|
static void drainMessages(void) {
|
|
struct IntuiMessage *msg;
|
|
UWORD msgClass;
|
|
UWORD msgCode;
|
|
int16_t msgMouseX;
|
|
int16_t msgMouseY;
|
|
uint8_t code;
|
|
uint8_t key;
|
|
bool isRelease;
|
|
|
|
if (gWindow == NULL) {
|
|
return;
|
|
}
|
|
while ((msg = (struct IntuiMessage *)GetMsg(gWindow->UserPort)) != NULL) {
|
|
msgClass = msg->Class;
|
|
msgCode = msg->Code;
|
|
msgMouseX = (int16_t)msg->MouseX;
|
|
msgMouseY = (int16_t)msg->MouseY;
|
|
ReplyMsg((struct Message *)msg);
|
|
|
|
switch (msgClass) {
|
|
case IDCMP_RAWKEY:
|
|
isRelease = (msgCode & AMIGA_KEY_RELEASE_BIT) != 0;
|
|
code = (uint8_t)(msgCode & AMIGA_KEY_CODE_MASK);
|
|
key = gRawKeyToKey[code];
|
|
if (key != KEY_NONE) {
|
|
gKeyState[key] = !isRelease;
|
|
}
|
|
break;
|
|
case IDCMP_MOUSEMOVE:
|
|
gMouseX = msgMouseX;
|
|
gMouseY = msgMouseY;
|
|
break;
|
|
case IDCMP_MOUSEBUTTONS:
|
|
switch (msgCode) {
|
|
case SELECTDOWN: gMouseButtonState[MOUSE_BUTTON_LEFT] = true; break;
|
|
case SELECTUP: gMouseButtonState[MOUSE_BUTTON_LEFT] = false; break;
|
|
case MENUDOWN: gMouseButtonState[MOUSE_BUTTON_RIGHT] = true; break;
|
|
case MENUUP: gMouseButtonState[MOUSE_BUTTON_RIGHT] = false; break;
|
|
case MIDDLEDOWN: gMouseButtonState[MOUSE_BUTTON_MIDDLE] = true; break;
|
|
case MIDDLEUP: gMouseButtonState[MOUSE_BUTTON_MIDDLE] = false; break;
|
|
default: break;
|
|
}
|
|
// MOUSEBUTTONS messages also carry the mouse position.
|
|
gMouseX = msgMouseX;
|
|
gMouseY = msgMouseY;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Read both joystick ports via lowlevel.library. Devices that report
|
|
// JP_TYPE_JOYSTK or JP_TYPE_GAMECTLR populate gJoy*; anything else
|
|
// (mouse on port 0, no device, unknown) leaves the stick disconnected
|
|
// with zeroed state.
|
|
static void pollJoysticks(void) {
|
|
static const int amigaPortForStick[JOYSTICK_COUNT] = {
|
|
AMIGA_PORT_FOR_JS0,
|
|
AMIGA_PORT_FOR_JS1
|
|
};
|
|
ULONG state;
|
|
ULONG type;
|
|
int stick;
|
|
|
|
if (gLowLevelBase == NULL) {
|
|
return;
|
|
}
|
|
for (stick = 0; stick < JOYSTICK_COUNT; stick++) {
|
|
state = ReadJoyPort((UWORD)amigaPortForStick[stick]);
|
|
type = state & JP_TYPE_MASK;
|
|
if (type != JP_TYPE_JOYSTK && type != JP_TYPE_GAMECTLR) {
|
|
gJoyConnected[stick] = false;
|
|
gJoyAxisX[stick] = 0;
|
|
gJoyAxisY[stick] = 0;
|
|
gJoyButtonState[stick][JOY_BUTTON_0] = false;
|
|
gJoyButtonState[stick][JOY_BUTTON_1] = false;
|
|
continue;
|
|
}
|
|
gJoyConnected[stick] = true;
|
|
|
|
gJoyAxisX[stick] = 0;
|
|
if (state & JPF_JOY_LEFT) { gJoyAxisX[stick] = JOYSTICK_AXIS_MIN; }
|
|
if (state & JPF_JOY_RIGHT) { gJoyAxisX[stick] = JOYSTICK_AXIS_MAX; }
|
|
gJoyAxisY[stick] = 0;
|
|
if (state & JPF_JOY_UP) { gJoyAxisY[stick] = JOYSTICK_AXIS_MIN; }
|
|
if (state & JPF_JOY_DOWN) { gJoyAxisY[stick] = JOYSTICK_AXIS_MAX; }
|
|
|
|
// Standard 1-button stick reports JPF_BUTTON_RED only. CD32
|
|
// game controllers also report BLUE for the second face button.
|
|
gJoyButtonState[stick][JOY_BUTTON_0] = (state & JPF_BUTTON_RED) != 0;
|
|
gJoyButtonState[stick][JOY_BUTTON_1] = (state & JPF_BUTTON_BLUE) != 0;
|
|
}
|
|
}
|
|
|
|
|
|
// ----- HAL API (alphabetical) -----
|
|
|
|
void halInputInit(void) {
|
|
memset(gKeyState, 0, sizeof(gKeyState));
|
|
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
|
|
|
if (gScreen == NULL) {
|
|
return;
|
|
}
|
|
|
|
gWindow = OpenWindowTags(NULL,
|
|
(ULONG)WA_CustomScreen, (ULONG)gScreen,
|
|
(ULONG)WA_Left, (ULONG)0,
|
|
(ULONG)WA_Top, (ULONG)0,
|
|
(ULONG)WA_Width, (ULONG)gScreen->Width,
|
|
(ULONG)WA_Height, (ULONG)gScreen->Height,
|
|
(ULONG)WA_Flags, (ULONG)(WFLG_BACKDROP
|
|
| WFLG_BORDERLESS
|
|
| WFLG_ACTIVATE
|
|
| WFLG_RMBTRAP
|
|
| WFLG_NOCAREREFRESH
|
|
| WFLG_REPORTMOUSE),
|
|
(ULONG)WA_IDCMP, (ULONG)(IDCMP_RAWKEY
|
|
| IDCMP_MOUSEMOVE
|
|
| IDCMP_MOUSEBUTTONS),
|
|
TAG_DONE);
|
|
|
|
if (gWindow != NULL) {
|
|
gMouseX = (int16_t)(gWindow->Width / 2);
|
|
gMouseY = (int16_t)(gWindow->Height / 2);
|
|
}
|
|
|
|
// lowlevel.library shipped with AmigaOS 2.05+. If absent (very old
|
|
// Kickstart, or stripped AROS), the joystick API silently reports
|
|
// disconnected sticks rather than failing init.
|
|
gLowLevelBase = OpenLibrary((CONST_STRPTR)"lowlevel.library", 0);
|
|
}
|
|
|
|
|
|
void halInputPoll(void) {
|
|
drainMessages();
|
|
pollJoysticks();
|
|
}
|
|
|
|
|
|
void halInputShutdown(void) {
|
|
if (gLowLevelBase != NULL) {
|
|
CloseLibrary(gLowLevelBase);
|
|
gLowLevelBase = NULL;
|
|
}
|
|
if (gWindow == NULL) {
|
|
return;
|
|
}
|
|
drainMessages();
|
|
CloseWindow(gWindow);
|
|
gWindow = NULL;
|
|
}
|