// 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 #include #include #include #include #include #include #include #include #include #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; }