joeylib2/src/port/amiga/input.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;
}