mirror of https://github.com/buserror/mii_emu.git
288 lines
7.9 KiB
C
288 lines
7.9 KiB
C
/*
|
|
* mii_mouse.c
|
|
*
|
|
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
*/
|
|
|
|
#include "mii.h"
|
|
|
|
// https://github.com/ivanizag/izapple2/blob/master/cardMouse.go
|
|
// https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator/log/188017-entry-23-here-mousie-mousie-mousie
|
|
// https://github.com/ct6502/apple2ts/blob/main/src/emulator/mouse.ts
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "mii.h"
|
|
#include "mii_bank.h"
|
|
|
|
/*
|
|
* Coded against this information
|
|
* http://stason.org/TULARC/pc/apple2/programmer/012-How-do-I-write-programs-which-use-the-mouse.html
|
|
*/
|
|
|
|
/*
|
|
* Screen holes
|
|
* $0478 + slot Low byte of absolute X position
|
|
* $04F8 + slot Low byte of absolute Y position
|
|
* $0578 + slot High byte of absolute X position
|
|
* $05F8 + slot High byte of absolute Y position
|
|
* $0678 + slot Reserved and used by the firmware
|
|
* $06F8 + slot Reserved and used by the firmware
|
|
* $0778 + slot Button 0/1 interrupt status byte
|
|
* $07F8 + slot Mode byte
|
|
*
|
|
* Interrupt status byte:
|
|
* Set by READMOUSE
|
|
* Bit 7 6 5 4 3 2 1 0
|
|
* | | | | | | | |
|
|
* | | | | | | | `--- Previously, button 1 was up (0) or down (1)
|
|
* | | | | | | `----- Movement interrupt
|
|
* | | | | | `------- Button 0/1 interrupt
|
|
* | | | | `--------- VBL interrupt
|
|
* | | | `----------- Currently, button 1 is up (0) or down (1)
|
|
* | | `------------- X/Y moved since last READMOUSE
|
|
* | `--------------- Previously, button 0 was up (0) or down (1)
|
|
* `----------------- Currently, button 0 is up (0) or down (1)
|
|
*
|
|
* Mode byte
|
|
* Valid after calling SERVEMOUSE, cleared with READMOUSE
|
|
* Bit 7 6 5 4 3 2 1 0
|
|
* | | | | | | | |
|
|
* | | | | | | | `--- Mouse off (0) or on (1)
|
|
* | | | | | | `----- Interrupt if mouse is moved
|
|
* | | | | | `------- Interrupt if button is pressed
|
|
* | | | | `--------- Interrupt on VBL
|
|
* | | | `----------- Reserved
|
|
* | | `------------- Reserved
|
|
* | `--------------- Reserved
|
|
* `----------------- Reserved
|
|
*/
|
|
|
|
enum {
|
|
CLAMP_MIN_LO = 0x478,
|
|
CLAMP_MIN_HI = 0x578,
|
|
CLAMP_MAX_LO = 0x4F8,
|
|
CLAMP_MAX_HI = 0x5F8,
|
|
|
|
// RAM Locations
|
|
// (Add $Cn where n is slot to these)
|
|
MOUSE_X_LO = 0x03B8,
|
|
MOUSE_X_HI = 0x04B8,
|
|
MOUSE_Y_LO = 0x0438,
|
|
MOUSE_Y_HI = 0x0538,
|
|
MOUSE_STATUS = 0x06B8,
|
|
MOUSE_MODE = 0x0738,
|
|
};
|
|
|
|
enum {
|
|
mouseEnabled = 1,
|
|
mouseIntMoveEnabled = 2,
|
|
mouseIntButtonEnabled = 4,
|
|
mouseIntVBlankEnabled = 8,
|
|
};
|
|
|
|
typedef struct mii_card_mouse_t {
|
|
struct mii_slot_t * slot;
|
|
mii_t * mii;
|
|
uint8_t timer_id; // 60hz timer
|
|
uint8_t slot_offset;
|
|
uint8_t mode; // cached mode byte
|
|
struct {
|
|
uint16_t x, y;
|
|
bool button;
|
|
} last;
|
|
} mii_card_mouse_t;
|
|
|
|
static uint64_t
|
|
_mii_mouse_vbl_handler(
|
|
mii_t * mii,
|
|
void *param)
|
|
{
|
|
mii_card_mouse_t *c = param;
|
|
/* this is not exact, the VBL interrupt should still work when
|
|
* the mouse is disabled, but it's not really important -- for the moment
|
|
*/
|
|
if (!mii->mouse.enabled)
|
|
return 1000000 / 60;
|
|
|
|
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
|
uint8_t status = mii_bank_peek(main, MOUSE_STATUS + c->slot_offset);
|
|
uint8_t old = status;
|
|
|
|
if (c->mode & mouseIntMoveEnabled) {
|
|
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y)) {
|
|
mii->cpu_state.irq = 1;
|
|
status |= 1 << 1;
|
|
}
|
|
}
|
|
if (c->mode & mouseIntButtonEnabled) {
|
|
if (mii->mouse.button && !c->last.button) {
|
|
mii->cpu_state.irq = 1;
|
|
status |= 1 << 2;
|
|
}
|
|
}
|
|
if (c->mode & mouseIntVBlankEnabled) {
|
|
mii->cpu_state.irq = 1;
|
|
status |= 1 << 3;
|
|
}
|
|
// if (mii->cpu_state.irq) mii->trace_cpu = true;
|
|
if (status != old)
|
|
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
|
|
return 1000000 / 60;
|
|
}
|
|
|
|
static int
|
|
_mii_mouse_init(
|
|
mii_t * mii,
|
|
struct mii_slot_t *slot )
|
|
{
|
|
mii_card_mouse_t *c = calloc(1, sizeof(*c));
|
|
c->slot = slot;
|
|
slot->drv_priv = c;
|
|
c->mii = mii;
|
|
|
|
printf("%s loading in slot %d\n", __func__, slot->id + 1);
|
|
|
|
c->slot_offset = slot->id + 1 + 0xc0;
|
|
uint8_t data[256] = {};
|
|
|
|
c->timer_id = mii_timer_register(mii,
|
|
_mii_mouse_vbl_handler, c,
|
|
1000000 / 60, __func__);
|
|
// Identification as a mouse card
|
|
// From Technical Note Misc #8, "Pascal 1.1 Firmware Protocol ID Bytes":
|
|
data[0x05] = 0x38;
|
|
data[0x07] = 0x18;
|
|
data[0x0b] = 0x01;
|
|
data[0x0c] = 0x20;
|
|
// From "AppleMouse // User's Manual", Appendix B:
|
|
//data[0x0c] = 0x20
|
|
data[0xfb] = 0xd6;
|
|
|
|
// Set 8 entrypoints to sofstwitches 2 to 1f
|
|
for (int i = 0; i < 14; i++) {
|
|
uint8_t base = 0x60 + 0x05 * i;
|
|
data[0x12+i] = base;
|
|
data[base+0] = 0x8D; // STA $C0x2
|
|
data[base+1] = 0x82 + i + ((slot->id + 1) << 4);
|
|
data[base+2] = 0xC0;
|
|
data[base+3] = 0x18; // CLC ;no error
|
|
data[base+4] = 0x60; // RTS
|
|
}
|
|
uint16_t addr = 0xc100 + (slot->id * 0x100);
|
|
mii_bank_write(
|
|
&mii->bank[MII_BANK_CARD_ROM],
|
|
addr, data, 256);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_mii_mouse_dispose(
|
|
mii_t * mii,
|
|
struct mii_slot_t *slot )
|
|
{
|
|
mii_card_mouse_t *c = slot->drv_priv;
|
|
free(c);
|
|
slot->drv_priv = NULL;
|
|
}
|
|
|
|
static uint8_t
|
|
_mii_mouse_access(
|
|
mii_t * mii,
|
|
struct mii_slot_t *slot,
|
|
uint16_t addr,
|
|
uint8_t byte,
|
|
bool write)
|
|
{
|
|
mii_card_mouse_t *c = slot->drv_priv;
|
|
|
|
int psw = addr & 0x0F;
|
|
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
|
switch (psw) {
|
|
case 2: {
|
|
if (write) {
|
|
byte &= 0xf;
|
|
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, byte);
|
|
printf("%s: mouse mode %02x\n", __func__, byte);
|
|
mii->mouse.enabled = byte & mouseEnabled;
|
|
printf("Mouse %s\n", mii->mouse.enabled ? "enabled" : "disabled");
|
|
printf(" Interupt: %s\n", byte & mouseIntMoveEnabled ? "enabled" : "disabled");
|
|
printf(" Button: %s\n", byte & mouseIntButtonEnabled ? "enabled" : "disabled");
|
|
printf(" VBlank: %s\n", byte & mouseIntVBlankEnabled ? "enabled" : "disabled");
|
|
c->mode = byte;
|
|
}
|
|
} break;
|
|
case 3: // service mouse
|
|
// no need to handle that, the VBL handler does it
|
|
break;
|
|
case 4: {// read mouse
|
|
if (!mii->mouse.enabled)
|
|
break;
|
|
uint8_t status = 0;
|
|
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y))
|
|
status |= 1 << 5;
|
|
status |= c->last.button ? 1 << 6 : 0;
|
|
status |= mii->mouse.button ? 1 << 7 : 0;
|
|
mii_bank_poke(main, MOUSE_X_HI + c->slot_offset, mii->mouse.x >> 8);
|
|
mii_bank_poke(main, MOUSE_Y_HI + c->slot_offset, mii->mouse.y >> 8);
|
|
mii_bank_poke(main, MOUSE_X_LO + c->slot_offset, mii->mouse.x);
|
|
mii_bank_poke(main, MOUSE_Y_LO + c->slot_offset, mii->mouse.y);
|
|
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
|
|
// mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0xf); // already in place
|
|
c->last.x = mii->mouse.x;
|
|
c->last.y = mii->mouse.y;
|
|
c->last.button = mii->mouse.button;
|
|
} break;
|
|
case 5: // clear mouse
|
|
break;
|
|
case 7: // set mouse
|
|
if (byte == 0) {
|
|
mii->mouse.min_x = mii_bank_peek(main, CLAMP_MIN_LO) |
|
|
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
|
|
mii->mouse.max_x = mii_bank_peek(main, CLAMP_MAX_LO) |
|
|
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
|
|
} else if (byte == 1) {
|
|
mii->mouse.min_y = mii_bank_peek(main, CLAMP_MIN_LO) |
|
|
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
|
|
mii->mouse.max_y = mii_bank_peek(main, CLAMP_MAX_LO) |
|
|
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
|
|
}
|
|
printf("Mouse clamp to %d,%d - %d,%d\n",
|
|
mii->mouse.min_x, mii->mouse.min_y,
|
|
mii->mouse.max_x, mii->mouse.max_y);
|
|
break;
|
|
case 8: // home mouse
|
|
mii->mouse.x = mii->mouse.min_x;
|
|
mii->mouse.y = mii->mouse.min_y;
|
|
break;
|
|
case 0xc: // init mouse
|
|
mii->mouse.min_x = mii->mouse.min_y = 0;
|
|
mii->mouse.max_x = mii->mouse.max_y = 1023;
|
|
mii->mouse.enabled = 0;
|
|
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0x0);
|
|
break;
|
|
default:
|
|
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
|
|
mii->cpu.PC, addr, byte, write);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static mii_slot_drv_t _driver = {
|
|
.name = "mouse",
|
|
.desc = "Mouse card",
|
|
.init = _mii_mouse_init,
|
|
.dispose = _mii_mouse_dispose,
|
|
.access = _mii_mouse_access,
|
|
};
|
|
MI_DRIVER_REGISTER(_driver);
|