Add AppleJack controller support, based on AdbMouse

All AppleJack controllers start in mouse emulation mode, behaving exactly like
ADB mice. Only upon receiving a Listen command on register 3 with handler ID
0x46 does the AppleJack switch protocols, albeit such a change consisting merely
of expanding register 0's buffer size to 4 bytes. In this state, the first 2
bytes remain defined as they are for an ADB mouse; the additional 16 bits carry
the respective states of each of the AppleJack controller's remaining 11 buttons
out of a possible supported 16.

For backward compatibility, honor both mouse clicks and shoulder button presses
on the host when considering the state of the emulated trigger buttons.
This commit is contained in:
Keith Kaisershot 2024-10-06 23:34:10 -07:00
parent d23f21b391
commit 34e0ba84f1
4 changed files with 200 additions and 20 deletions

View File

@ -0,0 +1,100 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus AppleJack controller emulation. */
#include <devices/common/adb/adbapplejack.h>
#include <devices/common/adb/adbbus.h>
#include <devices/deviceregistry.h>
#include <core/hostevents.h>
#include <loguru.hpp>
AdbAppleJack::AdbAppleJack(std::string name) : AdbMouse(name, AdbMouse::TRACKBALL, 2, 7, 300) {
EventManager::get_instance()->add_gamepad_handler(this, &AdbAppleJack::event_handler);
}
void AdbAppleJack::event_handler(const GamepadEvent& event) {
uint32_t button_bit = 1 << event.button;
if (event.flags & GAMEPAD_EVENT_DOWN)
this->buttons_state |= button_bit;
else if (event.flags & GAMEPAD_EVENT_UP)
this->buttons_state &= ~button_bit;
if (button_bit & ((1 << GamepadButton::LeftTrigger) | (1 << GamepadButton::RightTrigger)))
this->triggers_changed = true;
else
this->buttons_changed = true;
}
void AdbAppleJack::reset() {
this->AdbMouse::reset();
this->buttons_state = 0;
this->triggers_changed = false;
this->buttons_changed = false;
LOG_F(INFO, "%s: reset; in mouse emulation mode", this->name.c_str());
}
bool AdbAppleJack::get_register_0() {
bool changed = this->triggers_changed || this->buttons_changed;
uint8_t mouse_buttons = this->AdbMouse::get_buttons_state();
// AppleJack triggers always affect the mouse button bits.
mouse_buttons |= (this->buttons_state >> (GamepadButton::LeftTrigger - 0)) & 1;
mouse_buttons |= (this->buttons_state >> (GamepadButton::RightTrigger - 1)) & 2;
changed |= this->AdbMouse::get_register_0(mouse_buttons, changed);
this->triggers_changed = false;
if (this->dev_handler_id == APPLEJACK_HANDLER_ID && this->buttons_changed) {
uint8_t* out_buf = this->host_obj->get_output_buf();
out_buf[2] = ~this->buttons_state >> 8;
out_buf[3] = ~this->buttons_state & 0xFF;
this->host_obj->set_output_count(4);
this->buttons_changed = false;
}
return changed;
}
void AdbAppleJack::set_register_3() {
if (this->host_obj->get_input_count() < 2) // ensure we got enough data
return;
const uint8_t* in_data = this->host_obj->get_input_buf();
switch (in_data[1]) {
case APPLEJACK_HANDLER_ID: // switch over to AppleJack protocol
this->dev_handler_id = in_data[1];
LOG_F(INFO, "%s: switched to AppleJack mode", this->name.c_str());
break;
default:
this->AdbMouse::set_register_3();
break;
}
}
static const DeviceDescription AdbAppleJack_Descriptor = {
AdbAppleJack::create, {}, {}
};
REGISTER_DEVICE(AdbAppleJack, AdbAppleJack_Descriptor);

View File

@ -0,0 +1,60 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus AppleJack controller definitions. */
#ifndef ADB_APPLEJACK_H
#define ADB_APPLEJACK_H
#include <devices/common/adb/adbmouse.h>
#include <devices/common/hwcomponent.h>
#include <memory>
#include <string>
enum : uint8_t {
APPLEJACK_HANDLER_ID = 0x46,
};
class GamepadEvent;
class AdbAppleJack : public AdbMouse {
public:
AdbAppleJack(std::string name);
~AdbAppleJack() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AdbAppleJack>(new AdbAppleJack("ADB-APPLEJACK"));
}
void reset() override;
void event_handler(const GamepadEvent& event);
bool get_register_0() override;
void set_register_3() override;
private:
uint32_t buttons_state = 0;
bool triggers_changed = false;
bool buttons_changed = false;
};
#endif // ADB_APPLEJACK_H

View File

@ -28,7 +28,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <memaccess.h>
#include <loguru.hpp>
AdbMouse::AdbMouse(std::string name) : AdbDevice(name) {
AdbMouse::AdbMouse(
std::string name, uint8_t device_class, int num_buttons, int num_bits, uint16_t resolution) : AdbDevice(name), device_class(device_class), num_buttons(num_buttons), num_bits(num_bits), resolution(resolution) {
EventManager::get_instance()->add_mouse_handler(this, &AdbMouse::event_handler);
this->reset();
@ -63,8 +64,10 @@ void AdbMouse::reset() {
this->changed = false;
}
bool AdbMouse::get_register_0() {
if (this->x_rel || this->y_rel || this->changed) {
bool AdbMouse::get_register_0(uint8_t buttons_state, bool force) {
bool should_update = this->x_rel || this->y_rel || this->changed || force;
if (should_update) {
uint8_t* p;
uint8_t* out_buf = this->host_obj->get_output_buf();
@ -86,7 +89,7 @@ bool AdbMouse::get_register_0() {
bits = 7;
while (bits_remaining > 0) {
*p = (val & ((1 << bits) - 1)) | (((this->buttons_state >> button) ^ 1) << bits) | (*p << (bits + 1));
*p = (val & ((1 << bits) - 1)) | (((buttons_state >> button) ^ 1) << bits) | (*p << (bits + 1));
val >>= bits;
bits_remaining -= bits;
p = bits == 7 ? &out_buf[2] : p + 1;
@ -109,12 +112,16 @@ bool AdbMouse::get_register_0() {
if (this->device_class == MOUSE && this->dev_handler_id == 1)
count = 2;
this->host_obj->set_output_count(count);
return true;
return should_update;
}
return false;
}
bool AdbMouse::get_register_0() {
return get_register_0(this->buttons_state, false);
}
bool AdbMouse::get_register_1() {
uint8_t* out_buf = this->host_obj->get_output_buf();
// Identifier
@ -156,6 +163,10 @@ void AdbMouse::set_register_3() {
}
}
uint8_t AdbMouse::get_buttons_state() const {
return this->buttons_state;
}
static const DeviceDescription AdbMouse_Descriptor = {
AdbMouse::create, {}, {}
};

View File

@ -35,19 +35,31 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
class MouseEvent;
class AdbMouse : public AdbDevice {
public:
enum DeviceClass {
TABLET = 0,
MOUSE = 1,
TRACKBALL = 2,
};
public:
AdbMouse(std::string name);
AdbMouse(
std::string name, uint8_t device_class, int num_buttons, int num_bits, uint16_t resolution);
~AdbMouse() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AdbMouse>(new AdbMouse("ADB-MOUSE"));
#ifdef ABSOLUTE
uint8_t device_class = TABLET;
int num_buttons = 3;
int num_bits = 16;
uint16_t resolution = 72;
#else
uint8_t device_class = MOUSE;
int num_buttons = 3;
int num_bits = 10;
uint16_t resolution = 300;
#endif
return std::unique_ptr<AdbMouse>(
new AdbMouse("ADB-MOUSE", device_class, num_buttons, num_bits, resolution));
}
void reset() override;
@ -57,6 +69,10 @@ public:
bool get_register_1() override;
void set_register_3() override;
protected:
bool get_register_0(uint8_t buttons_state, bool force);
uint8_t get_buttons_state() const;
private:
int32_t x_rel = 0;
int32_t y_rel = 0;
@ -64,17 +80,10 @@ private:
int32_t y_abs = 0;
uint8_t buttons_state = 0;
bool changed = false;
#ifdef ABSOLUTE
uint8_t device_class = TABLET;
int num_buttons = 3;
int num_bits = 16;
uint16_t resolution = 72;
#else
uint8_t device_class = MOUSE;
int num_buttons = 3;
int num_bits = 10;
uint16_t resolution = 300;
#endif
uint8_t device_class;
int num_buttons;
int num_bits;
uint16_t resolution;
};
#endif // ADB_MOUSE_H