486 lines
18 KiB
C
486 lines
18 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// 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
|
|
// jlInputPoll.
|
|
//
|
|
// 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 jlKeyE 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.
|
|
//
|
|
// Typed characters: RAWKEY press events are also run through
|
|
// keymap.library's MapRawKey with the message qualifier and dead-key
|
|
// context, which applies the user's keymap (shift, caps, national
|
|
// layouts, dead keys) and yields translated bytes for the core typed-
|
|
// character queue. Intuition delivers key repeats as further RAWKEY
|
|
// events, so OS auto-repeat produces repeated characters for free.
|
|
|
|
#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/keymap.h>
|
|
#include <proto/lowlevel.h>
|
|
|
|
#include "port.h"
|
|
#include "inputInternal.h"
|
|
|
|
// ----- Constants -----
|
|
|
|
#define AMIGA_KEY_TABLE_SIZE 128
|
|
#define AMIGA_KEY_RELEASE_BIT 0x80
|
|
#define AMIGA_KEY_CODE_MASK 0x7F
|
|
|
|
// MapRawKey output buffer. A single keystroke maps to at most a few
|
|
// bytes (dead-key compositions); 8 is comfortably above any keymap's
|
|
// output.
|
|
#define MAP_RAW_KEY_BUF_SIZE 8
|
|
|
|
// Keymaps encode arrows / F-keys / Help as ANSI control sequences
|
|
// beginning with CSI (0x9B). The CSI byte itself would be filtered,
|
|
// but the sequence's TAIL bytes are printable ('A' for cursor up,
|
|
// "0~" for F1, ...) and would leak into the typed-character queue as
|
|
// stray text -- so a mapping that starts with CSI is discarded whole.
|
|
#define AMIGA_CSI 0x9B
|
|
|
|
// keymap.library base referenced by the MapRawKey inline stub. Defined
|
|
// here and opened explicitly (not left to libnix auto-open) so a
|
|
// system without the library degrades to "no typed characters" instead
|
|
// of failing at startup.
|
|
struct Library *KeymapBase = NULL;
|
|
|
|
// lowlevel.library base referenced by the ReadJoyPort and SetJoyPortAttrs
|
|
// inline stubs, defined and opened here for the same reason: a boot drive
|
|
// without Workbench's Libs/lowlevel.library degrades to "no joysticks".
|
|
struct Library *LowLevelBase = NULL;
|
|
|
|
// ----- External from hal.c -----
|
|
|
|
extern struct Screen *jlgScreen;
|
|
|
|
// ----- Empty pointer image -----
|
|
//
|
|
// Full-screen custom-screen games conventionally hide the system
|
|
// pointer; we install an all-zero sprite via SetPointer so Workbench's
|
|
// arrow doesn't drift over the playfield. Game code that needs a
|
|
// cursor draws its own from jlMouseX/Y.
|
|
//
|
|
// Amiga hardware sprite data: 2 leading control words (VSTART/HSTART/
|
|
// VSTOP), one line of (planeA, planeB) data words, 2 trailing zero
|
|
// words as terminator. All zeros yields a transparent pointer. Must
|
|
// live in chip RAM since sprite DMA reads it.
|
|
|
|
#define EMPTY_POINTER_BYTES (6 * sizeof(UWORD))
|
|
|
|
static UWORD *gEmptyPointer = NULL;
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void drainMessages(void);
|
|
static void joystickUnlock(int stick);
|
|
static void mouseButtonEvent(jlMouseButtonE button, bool down);
|
|
static void pollJoysticks(void);
|
|
|
|
// ----- Module state -----
|
|
|
|
// Amiga raw-key code -> jlKeyE. Codes are layout-independent; the
|
|
// OS has a separate keymap for producing characters. A second key for a
|
|
// named key carries INPUT_KEY_SECOND.
|
|
static const uint8_t gRawKeyToKey[AMIGA_KEY_TABLE_SIZE] = {
|
|
[0x01] = JL_KEY_1,
|
|
[0x02] = JL_KEY_2,
|
|
[0x03] = JL_KEY_3,
|
|
[0x04] = JL_KEY_4,
|
|
[0x05] = JL_KEY_5,
|
|
[0x06] = JL_KEY_6,
|
|
[0x07] = JL_KEY_7,
|
|
[0x08] = JL_KEY_8,
|
|
[0x09] = JL_KEY_9,
|
|
[0x0A] = JL_KEY_0,
|
|
[0x10] = JL_KEY_Q,
|
|
[0x11] = JL_KEY_W,
|
|
[0x12] = JL_KEY_E,
|
|
[0x13] = JL_KEY_R,
|
|
[0x14] = JL_KEY_T,
|
|
[0x15] = JL_KEY_Y,
|
|
[0x16] = JL_KEY_U,
|
|
[0x17] = JL_KEY_I,
|
|
[0x18] = JL_KEY_O,
|
|
[0x19] = JL_KEY_P,
|
|
[0x1E] = JL_KEY_DOWN | INPUT_KEY_SECOND, // keypad 2, 4, 6 and 8: the arrows too,
|
|
[0x20] = JL_KEY_A,
|
|
[0x21] = JL_KEY_S,
|
|
[0x22] = JL_KEY_D,
|
|
[0x23] = JL_KEY_F,
|
|
[0x24] = JL_KEY_G,
|
|
[0x25] = JL_KEY_H,
|
|
[0x26] = JL_KEY_J,
|
|
[0x27] = JL_KEY_K,
|
|
[0x28] = JL_KEY_L,
|
|
[0x2D] = JL_KEY_LEFT | INPUT_KEY_SECOND, // as on every port with a keypad
|
|
[0x2F] = JL_KEY_RIGHT | INPUT_KEY_SECOND,
|
|
[0x31] = JL_KEY_Z,
|
|
[0x32] = JL_KEY_X,
|
|
[0x33] = JL_KEY_C,
|
|
[0x34] = JL_KEY_V,
|
|
[0x35] = JL_KEY_B,
|
|
[0x36] = JL_KEY_N,
|
|
[0x37] = JL_KEY_M,
|
|
[0x3E] = JL_KEY_UP | INPUT_KEY_SECOND,
|
|
[0x40] = JL_KEY_SPACE,
|
|
[0x41] = JL_KEY_BACKSPACE,
|
|
[0x42] = JL_KEY_TAB,
|
|
[0x43] = JL_KEY_RETURN | INPUT_KEY_SECOND, // keypad Enter, as every port reads it
|
|
[0x44] = JL_KEY_RETURN,
|
|
[0x45] = JL_KEY_ESCAPE,
|
|
[0x4C] = JL_KEY_UP,
|
|
[0x4D] = JL_KEY_DOWN,
|
|
[0x4E] = JL_KEY_RIGHT,
|
|
[0x4F] = JL_KEY_LEFT,
|
|
[0x50] = JL_KEY_F1,
|
|
[0x51] = JL_KEY_F2,
|
|
[0x52] = JL_KEY_F3,
|
|
[0x53] = JL_KEY_F4,
|
|
[0x54] = JL_KEY_F5,
|
|
[0x55] = JL_KEY_F6,
|
|
[0x56] = JL_KEY_F7,
|
|
[0x57] = JL_KEY_F8,
|
|
[0x58] = JL_KEY_F9,
|
|
[0x59] = JL_KEY_F10,
|
|
[0x60] = JL_KEY_LSHIFT,
|
|
[0x61] = JL_KEY_RSHIFT,
|
|
[0x63] = JL_KEY_LCTRL,
|
|
[0x64] = JL_KEY_LALT,
|
|
[0x65] = JL_KEY_LALT | INPUT_KEY_SECOND, // right Alt: either Alt, as on the PC ports
|
|
};
|
|
|
|
static struct Window *gWindow = NULL;
|
|
|
|
// AmigaOS exposes joystick port 1 (the dedicated stick) via lowlevel
|
|
// ReadJoyPort(1); port 0 shares the mouse port. We map our public
|
|
// JL_JOYSTICK_0 to Amiga port 1 so the primary stick is what retro games
|
|
// expect.
|
|
static const ULONG gAmigaPortForStick[JL_JOYSTICK_COUNT] = { 1, 0 };
|
|
|
|
// lowlevel.library senses a port's controller again now and then, and a
|
|
// two-button stick held with both buttons down reads as no controller
|
|
// then, until it moves (measured under FS-UAE). So a port that has shown
|
|
// a stick is set to one (SetJoyPortAttrs) and stays one until
|
|
// jlJoystickReset or shutdown hands it back to autosensing.
|
|
static bool gJoyLocked[JL_JOYSTICK_COUNT];
|
|
|
|
// lowlevel.library reports a one-button stick and a two-button one alike
|
|
// (a second button is pin 9, which reads as released until it is pressed),
|
|
// and most Amiga owners use one-button Atari sticks. So a joystick counts as
|
|
// one-button (jlgJoyOneButton: a hold of its button is button 1) until its
|
|
// second button has been seen pressed, and again once its port has no
|
|
// controller. A CD32 game controller always has both.
|
|
static bool gJoySecondButton[JL_JOYSTICK_COUNT];
|
|
|
|
// Keys and mouse buttons as Intuition's messages leave them, and pressed
|
|
// since the last poll: a tap between two polls still reads down for one
|
|
// (jlInputLatch). The keys are latched only when a message changed them or
|
|
// the last latch showed a tap that must now go up.
|
|
static uint8_t gKeyHeld [JL_KEY_COUNT];
|
|
static uint8_t gKeyTapped [JL_KEY_COUNT];
|
|
static bool gKeysChanged;
|
|
static bool gKeyTapShown;
|
|
static uint8_t gMouseHeld [JL_MOUSE_BUTTON_COUNT];
|
|
static uint8_t gMouseTapped [JL_MOUSE_BUTTON_COUNT];
|
|
|
|
// ----- Internal helpers -----
|
|
|
|
static void drainMessages(void) {
|
|
struct IntuiMessage *msg;
|
|
struct InputEvent ie;
|
|
UBYTE mapBuf[MAP_RAW_KEY_BUF_SIZE];
|
|
WORD mapCount;
|
|
WORD mapIdx;
|
|
bool mapChord;
|
|
ULONG msgClass;
|
|
UWORD msgCode;
|
|
int16_t msgMouseX;
|
|
int16_t msgMouseY;
|
|
uint8_t code;
|
|
uint8_t entry;
|
|
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;
|
|
|
|
// Typed-character translation must run BEFORE ReplyMsg: the
|
|
// qualifier and the dead-key context behind IAddress belong
|
|
// to the message and may be recycled once replied. Release
|
|
// events (bit 7) type nothing.
|
|
mapCount = 0;
|
|
mapChord = (msg->Qualifier & IEQUALIFIER_CONTROL) != 0;
|
|
if (msgClass == IDCMP_RAWKEY && (msgCode & AMIGA_KEY_RELEASE_BIT) == 0 && KeymapBase != NULL) {
|
|
ie.ie_NextEvent = NULL;
|
|
ie.ie_Class = IECLASS_RAWKEY;
|
|
ie.ie_SubClass = 0;
|
|
ie.ie_Code = msgCode;
|
|
ie.ie_Qualifier = msg->Qualifier;
|
|
// Dead-key context: IAddress points at the previous-code
|
|
// storage MapRawKey wants in ie_EventAddress (RKM recipe).
|
|
ie.ie_EventAddress = (APTR)*((ULONG *)msg->IAddress);
|
|
mapCount = MapRawKey(&ie, (STRPTR)mapBuf, MAP_RAW_KEY_BUF_SIZE, NULL);
|
|
if (mapCount > 0 && mapBuf[0] == AMIGA_CSI) {
|
|
mapCount = 0;
|
|
}
|
|
}
|
|
|
|
ReplyMsg((struct Message *)msg);
|
|
|
|
switch (msgClass) {
|
|
case IDCMP_RAWKEY:
|
|
isRelease = (msgCode & AMIGA_KEY_RELEASE_BIT) != 0;
|
|
code = (uint8_t)(msgCode & AMIGA_KEY_CODE_MASK);
|
|
entry = gRawKeyToKey[code];
|
|
key = (uint8_t)(entry & INPUT_KEY_MASK);
|
|
if (key != JL_KEY_NONE) {
|
|
gKeyHeld[key] = inputKeyHeld(gKeyHeld[key], entry, !isRelease);
|
|
if (!isRelease) {
|
|
gKeyTapped[key] = 1u;
|
|
}
|
|
gKeysChanged = true;
|
|
}
|
|
// MapRawKey returns -1 on overflow (composition longer
|
|
// than the buffer) -- nothing usable, skip.
|
|
// A Ctrl chord types nothing (jlInputCharTyped); Alt is a
|
|
// layer of the keymap's characters.
|
|
for (mapIdx = 0; mapIdx < mapCount; mapIdx++) {
|
|
jlInputCharTyped((uint8_t)mapBuf[mapIdx], mapChord);
|
|
}
|
|
break;
|
|
case IDCMP_MOUSEMOVE:
|
|
jlInputMouseMove(msgMouseX, msgMouseY);
|
|
break;
|
|
case IDCMP_INACTIVEWINDOW:
|
|
// The keys and buttons it lets go elsewhere are never
|
|
// reported here: let them all go now.
|
|
memset(gKeyHeld, 0, sizeof(gKeyHeld));
|
|
memset(gMouseHeld, 0, sizeof(gMouseHeld));
|
|
gKeysChanged = true;
|
|
break;
|
|
case IDCMP_MOUSEBUTTONS:
|
|
switch (msgCode) {
|
|
case SELECTDOWN:
|
|
mouseButtonEvent(JL_MOUSE_BUTTON_LEFT, true);
|
|
break;
|
|
case SELECTUP:
|
|
mouseButtonEvent(JL_MOUSE_BUTTON_LEFT, false);
|
|
break;
|
|
case MENUDOWN:
|
|
mouseButtonEvent(JL_MOUSE_BUTTON_RIGHT, true);
|
|
break;
|
|
case MENUUP:
|
|
mouseButtonEvent(JL_MOUSE_BUTTON_RIGHT, false);
|
|
break;
|
|
case MIDDLEDOWN:
|
|
mouseButtonEvent(JL_MOUSE_BUTTON_MIDDLE, true);
|
|
break;
|
|
case MIDDLEUP:
|
|
mouseButtonEvent(JL_MOUSE_BUTTON_MIDDLE, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// MOUSEBUTTONS messages also carry the mouse position.
|
|
jlInputMouseMove(msgMouseX, msgMouseY);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Hand a locked port back to lowlevel.library's autosensing (which also
|
|
// frees the potgo bits it took for the stick).
|
|
static void joystickUnlock(int stick) {
|
|
if (!gJoyLocked[stick]) {
|
|
return;
|
|
}
|
|
SetJoyPortAttrs(gAmigaPortForStick[stick], SJA_Reinitialize, TRUE, TAG_DONE);
|
|
gJoyLocked[stick] = false;
|
|
}
|
|
|
|
|
|
// A mouse button's message: held as it leaves it, and pressed since the
|
|
// last poll.
|
|
static void mouseButtonEvent(jlMouseButtonE button, bool down) {
|
|
gMouseHeld[button] = down;
|
|
if (down) {
|
|
gMouseTapped[button] = 1u;
|
|
}
|
|
}
|
|
|
|
|
|
// 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) {
|
|
ULONG state;
|
|
ULONG type;
|
|
int stick;
|
|
|
|
if (LowLevelBase == NULL) {
|
|
return;
|
|
}
|
|
for (stick = 0; stick < JL_JOYSTICK_COUNT; stick++) {
|
|
state = ReadJoyPort(gAmigaPortForStick[stick]);
|
|
type = state & JP_TYPE_MASK;
|
|
if (type == JP_TYPE_JOYSTK && !gJoyLocked[stick]) {
|
|
gJoyLocked[stick] = SetJoyPortAttrs(gAmigaPortForStick[stick], SJA_Type, SJA_TYPE_JOYSTK, TAG_DONE) != FALSE;
|
|
}
|
|
if (type != JP_TYPE_JOYSTK && type != JP_TYPE_GAMECTLR) {
|
|
jlgJoyConnected[stick] = false;
|
|
jlgJoyAxisX[stick] = 0;
|
|
jlgJoyAxisY[stick] = 0;
|
|
jlgJoyOneButton[stick] = false;
|
|
gJoySecondButton[stick] = false;
|
|
jlgJoyButtonState[stick][JL_JOY_BUTTON_0] = false;
|
|
jlgJoyButtonState[stick][JL_JOY_BUTTON_1] = false;
|
|
continue;
|
|
}
|
|
jlgJoyConnected[stick] = true;
|
|
|
|
jlgJoyAxisX[stick] = 0;
|
|
if (state & JPF_JOY_LEFT) {
|
|
jlgJoyAxisX[stick] = JL_JOYSTICK_AXIS_MIN;
|
|
}
|
|
if (state & JPF_JOY_RIGHT) {
|
|
jlgJoyAxisX[stick] = JL_JOYSTICK_AXIS_MAX;
|
|
}
|
|
jlgJoyAxisY[stick] = 0;
|
|
if (state & JPF_JOY_UP) {
|
|
jlgJoyAxisY[stick] = JL_JOYSTICK_AXIS_MIN;
|
|
}
|
|
if (state & JPF_JOY_DOWN) {
|
|
jlgJoyAxisY[stick] = JL_JOYSTICK_AXIS_MAX;
|
|
}
|
|
|
|
// Standard 1-button stick reports JPF_BUTTON_RED only. CD32
|
|
// game controllers also report BLUE for the second face button.
|
|
jlgJoyButtonState[stick][JL_JOY_BUTTON_0] = (state & JPF_BUTTON_RED) != 0;
|
|
jlgJoyButtonState[stick][JL_JOY_BUTTON_1] = (state & JPF_BUTTON_BLUE) != 0;
|
|
if (type == JP_TYPE_GAMECTLR || (state & JPF_BUTTON_BLUE) != 0) {
|
|
gJoySecondButton[stick] = true;
|
|
}
|
|
jlgJoyOneButton[stick] = !gJoySecondButton[stick];
|
|
}
|
|
}
|
|
|
|
|
|
// ----- HAL API (alphabetical) -----
|
|
|
|
// Sticks are digital, so nothing to calibrate: the port goes back to
|
|
// autosensing, as a stick swapped for a mouse or a game controller needs.
|
|
void jlpJoystickReset(jlJoystickE js) {
|
|
joystickUnlock((int)js);
|
|
}
|
|
|
|
|
|
void jlpInputInit(void) {
|
|
memset(jlgKeyState, 0, sizeof(jlgKeyState));
|
|
memset(jlgKeyPrev, 0, sizeof(jlgKeyPrev));
|
|
memset(gKeyHeld, 0, sizeof(gKeyHeld));
|
|
memset(gKeyTapped, 0, sizeof(gKeyTapped));
|
|
gKeysChanged = true;
|
|
memset(gMouseHeld, 0, sizeof(gMouseHeld));
|
|
memset(gMouseTapped, 0, sizeof(gMouseTapped));
|
|
|
|
if (jlgScreen == NULL) {
|
|
return;
|
|
}
|
|
|
|
gWindow = OpenWindowTags(NULL, (ULONG)WA_CustomScreen, (ULONG)jlgScreen, (ULONG)WA_Left, (ULONG)0, (ULONG)WA_Top, (ULONG)0, (ULONG)WA_Width, (ULONG)jlgScreen->Width, (ULONG)WA_Height, (ULONG)jlgScreen->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 | IDCMP_INACTIVEWINDOW), TAG_DONE);
|
|
|
|
if (gWindow != NULL) {
|
|
jlgMouseAttached = true; // Intuition delivers IDCMP mouse events on this window
|
|
|
|
gEmptyPointer = (UWORD *)AllocMem((ULONG)EMPTY_POINTER_BYTES, (ULONG)(MEMF_CHIP | MEMF_CLEAR));
|
|
if (gEmptyPointer != NULL) {
|
|
SetPointer(gWindow, gEmptyPointer, 1, 16, 0, 0);
|
|
}
|
|
}
|
|
|
|
// lowlevel.library (OS 3.1, V40) is a disk library: Kickstart has none
|
|
// in ROM. If the boot drive has none either, the joystick API silently
|
|
// reports disconnected sticks rather than failing init.
|
|
memset(gJoyLocked, 0, sizeof(gJoyLocked));
|
|
LowLevelBase = OpenLibrary((CONST_STRPTR)"lowlevel.library", 0);
|
|
|
|
// keymap.library (OS 2.0+) drives the typed-character path. If
|
|
// absent, jlInputGetChar just stays empty; the predicates still
|
|
// work off the raw codes.
|
|
KeymapBase = OpenLibrary((CONST_STRPTR)"keymap.library", 0);
|
|
}
|
|
|
|
|
|
void jlpInputPoll(void) {
|
|
drainMessages();
|
|
if (gKeysChanged || gKeyTapShown) {
|
|
gKeysChanged = false;
|
|
gKeyTapShown = jlInputLatch(jlgKeyState, gKeyHeld, gKeyTapped, JL_KEY_COUNT);
|
|
}
|
|
jlInputLatch(jlgMouseButtonState, gMouseHeld, gMouseTapped, JL_MOUSE_BUTTON_COUNT);
|
|
pollJoysticks();
|
|
}
|
|
|
|
|
|
void jlpInputShutdown(void) {
|
|
if (KeymapBase != NULL) {
|
|
CloseLibrary(KeymapBase);
|
|
KeymapBase = NULL;
|
|
}
|
|
if (LowLevelBase != NULL) {
|
|
// The ports' types are lowlevel.library's, shared with every
|
|
// program: none may stay set to a stick after this one.
|
|
joystickUnlock(JL_JOYSTICK_0);
|
|
joystickUnlock(JL_JOYSTICK_1);
|
|
CloseLibrary(LowLevelBase);
|
|
LowLevelBase = NULL;
|
|
}
|
|
if (gWindow == NULL) {
|
|
return;
|
|
}
|
|
drainMessages();
|
|
if (gEmptyPointer != NULL) {
|
|
// Restore the system pointer before freeing our image so
|
|
// Intuition isn't holding a stale chip-RAM reference.
|
|
ClearPointer(gWindow);
|
|
FreeMem(gEmptyPointer, (ULONG)EMPTY_POINTER_BYTES);
|
|
gEmptyPointer = NULL;
|
|
}
|
|
CloseWindow(gWindow);
|
|
gWindow = NULL;
|
|
}
|