Update to v102r21 release.

byuu says:

Changelog:

  - GBA: fixed WININ2 reads, BG3PB writes [Jonas Quinn]
  - R65816: added support for yielding/resuming from WAI/STP¹
  - SFC: removed status.dmaCounter functionality (also fixes possible
    TAS desync issue)
  - tomoko: added support for combinatorial inputs [hex\_usr\]²
  - nall: fixed missing return value from Arithmetic::operator--
    [Hendricks266]

Now would be the time to start looking for major regressions with the
new GBA PPU renderer, I suppose ...

¹: this doesn't matter for the master thread (SNES CPU), but is
important for slave threads (SNES SA1). If you try to save a state and
the SA1 is inside of a WAI instruction, it will get stuck there forever.
This was causing attempts to create a save state in Super Bomberman
- Panic Bomber W to deadlock the emulator and crash it. This is now
finally fixed.

Note that I still need to implement similar functionality into the Mega
Drive 68K and Z80 cores. They still have the possibility of deadlocking.
The SNES implementation was more a dry-run test for this new
functionality. This possible crashing bug in the Mega Drive core is the
major blocking bug for a new official release.

²: many, many thanks to hex\_usr for coming up with a really nice
design. I mostly implemented it the exact same way, but with a few tiny
differences that don't really matter (display " and ", " or " instead of
" & ", " | " in the input settings windows; append → bind;
assignmentName changed to displayName.)

The actual functionality is identical to the old higan v094 and earlier
builds. Emulated digital inputs let you combine multiple possible keys
to trigger the buttons. This is OR logic, so you can map to eg
keyboard.up OR gamepad.up for instance. Emulated analog inputs always
sum together. Emulated rumble outputs will cause all mapped devices to
rumble, which is probably not at all useful but whatever. Hotkeys use
AND logic, so you have to press every key mapped to trigger them. Useful
for eg Ctrl+F to trigger fullscreen.

Obviously, there are cases where OR logic would be nice for hotkeys,
too. Eg if you want both F11 and your gamepad's guide button to trigger
the fullscreen toggle. Unfortunately, this isn't supported, and likely
won't ever be in tomoko. Something I might consider is a throw switch in
the configuration file to swap between AND or OR logic for hotkeys, but
I'm not going to allow construction of mappings like "(Keyboard.Ctrl and
Keyboard.F) or Gamepad.Guide", as that's just too complicated to code,
and too complicated to make a nice GUI to set up the mappings for.
This commit is contained in:
Tim Allen 2017-06-06 23:44:40 +10:00
parent 3bcf3c24c9
commit a4629e1f64
18 changed files with 166 additions and 109 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "102.20"; static const string Version = "102.21";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";

View File

@ -68,7 +68,7 @@ auto PPU::readIO(uint32 addr) -> uint8 {
case 0x0400'004a: return window3.io.active[BG0] << 0 | window3.io.active[BG1] << 1 | window3.io.active[BG2] << 2 | window3.io.active[BG3] << 3 | window3.io.active[OBJ] << 4 | window3.io.active[SFX] << 5; case 0x0400'004a: return window3.io.active[BG0] << 0 | window3.io.active[BG1] << 1 | window3.io.active[BG2] << 2 | window3.io.active[BG3] << 3 | window3.io.active[OBJ] << 4 | window3.io.active[SFX] << 5;
//WININ2 //WININ2
case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 3 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5; case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 2 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5;
//MOSAIC (write-only) //MOSAIC (write-only)
@ -266,7 +266,7 @@ auto PPU::writeIO(uint32 addr, uint8 data) -> void {
//BG3PB //BG3PB
case 0x0400'0032: bg3.io.pb.byte(0) = data; return; case 0x0400'0032: bg3.io.pb.byte(0) = data; return;
case 0x0400'0033: bg3.io.pb.byte(0) = data; return; case 0x0400'0033: bg3.io.pb.byte(1) = data; return;
//BG3PC //BG3PC
case 0x0400'0034: bg3.io.pc.byte(0) = data; return; case 0x0400'0034: bg3.io.pc.byte(0) = data; return;

View File

@ -53,15 +53,16 @@ L r.pc.h = readLong(vector + 1);
r.pc.b = 0x00; r.pc.b = 0x00;
} }
auto R65816::op_stp() { auto R65816::op_stp() -> void {
while(r.wai = true) { r.stp = true;
while(r.stp && !synchronizing()) {
L idle(); L idle();
} }
} }
auto R65816::op_wai() { auto R65816::op_wai() -> void {
r.wai = true; r.wai = true;
while(r.wai) { while(r.wai && !synchronizing()) {
L idle(); L idle();
} }
idle(); idle();

View File

@ -19,6 +19,7 @@ struct R65816 {
virtual auto lastCycle() -> void = 0; virtual auto lastCycle() -> void = 0;
virtual auto interruptPending() const -> bool = 0; virtual auto interruptPending() const -> bool = 0;
virtual auto interrupt() -> void; virtual auto interrupt() -> void;
virtual auto synchronizing() const -> bool = 0;
virtual auto readDisassembler(uint24 addr) -> uint8 { return 0; } virtual auto readDisassembler(uint24 addr) -> uint8 { return 0; }
@ -174,8 +175,8 @@ struct R65816 {
auto op_move_b(int adjust); auto op_move_b(int adjust);
auto op_move_w(int adjust); auto op_move_w(int adjust);
auto op_interrupt(uint16); auto op_interrupt(uint16);
auto op_stp(); auto op_stp() -> void;
auto op_wai(); auto op_wai() -> void;
auto op_xce(); auto op_xce();
auto op_set_flag(uint bit); auto op_set_flag(uint bit);
auto op_clear_flag(uint bit); auto op_clear_flag(uint bit);

View File

@ -75,6 +75,7 @@ struct Registers {
bool irq = false; //IRQ pin (0 = low, 1 = trigger) bool irq = false; //IRQ pin (0 = low, 1 = trigger)
bool wai = false; //raised during wai, cleared after interrupt triggered bool wai = false; //raised during wai, cleared after interrupt triggered
bool stp = false; //raised during stp, never cleared
uint8 mdr = 0; //memory data register uint8 mdr = 0; //memory data register
uint16 vector = 0; //interrupt vector address uint16 vector = 0; //interrupt vector address
}; };

View File

@ -14,6 +14,7 @@ auto R65816::serialize(serializer& s) -> void {
s.integer(r.e); s.integer(r.e);
s.integer(r.irq); s.integer(r.irq);
s.integer(r.wai); s.integer(r.wai);
s.integer(r.stp);
s.integer(r.mdr); s.integer(r.mdr);
s.integer(r.vector); s.integer(r.vector);

View File

@ -14,6 +14,9 @@ auto SA1::Enter() -> void {
} }
auto SA1::main() -> void { auto SA1::main() -> void {
if(r.wai) return op_wai();
if(r.stp) return op_stp();
if(mmio.sa1_rdyb || mmio.sa1_resb) { if(mmio.sa1_rdyb || mmio.sa1_resb) {
//SA-1 co-processor is asleep //SA-1 co-processor is asleep
tick(); tick();
@ -75,6 +78,10 @@ auto SA1::interruptPending() const -> bool {
return status.interruptPending; return status.interruptPending;
} }
auto SA1::synchronizing() const -> bool {
return scheduler.synchronizing();
}
auto SA1::tick() -> void { auto SA1::tick() -> void {
step(2); step(2);
if(++status.counter == 0) synchronize(cpu); if(++status.counter == 0) synchronize(cpu);
@ -142,6 +149,7 @@ auto SA1::power() -> void {
r.e = 1; r.e = 1;
r.mdr = 0x00; r.mdr = 0x00;
r.wai = false; r.wai = false;
r.stp = false;
r.vector = 0x0000; r.vector = 0x0000;
status.counter = 0; status.counter = 0;

View File

@ -8,6 +8,7 @@ struct SA1 : Processor::R65816, Thread {
alwaysinline auto triggerIRQ() -> void; alwaysinline auto triggerIRQ() -> void;
alwaysinline auto lastCycle() -> void override; alwaysinline auto lastCycle() -> void override;
alwaysinline auto interruptPending() const -> bool override; alwaysinline auto interruptPending() const -> bool override;
auto synchronizing() const -> bool override;
auto init() -> void; auto init() -> void;
auto load() -> void; auto load() -> void;

View File

@ -13,6 +13,7 @@ CPU cpu;
auto CPU::interruptPending() const -> bool { return status.interruptPending; } auto CPU::interruptPending() const -> bool { return status.interruptPending; }
auto CPU::pio() const -> uint8 { return io.pio; } auto CPU::pio() const -> uint8 { return io.pio; }
auto CPU::joylatch() const -> bool { return io.joypadStrobeLatch; } auto CPU::joylatch() const -> bool { return io.joypadStrobeLatch; }
auto CPU::synchronizing() const -> bool { return scheduler.synchronizing(); }
CPU::CPU() { CPU::CPU() {
PPUcounter::scanline = {&CPU::scanline, this}; PPUcounter::scanline = {&CPU::scanline, this};
@ -23,6 +24,9 @@ auto CPU::Enter() -> void {
} }
auto CPU::main() -> void { auto CPU::main() -> void {
if(r.wai) return op_wai();
if(r.stp) return op_stp();
if(status.interruptPending) { if(status.interruptPending) {
status.interruptPending = false; status.interruptPending = false;
if(status.nmiPending) { if(status.nmiPending) {
@ -93,6 +97,7 @@ auto CPU::power() -> void {
r.e = 1; r.e = 1;
r.mdr = 0x00; r.mdr = 0x00;
r.wai = false; r.wai = false;
r.stp = false;
r.vector = 0xfffc; //reset vector address r.vector = 0xfffc; //reset vector address
//DMA //DMA
@ -209,7 +214,6 @@ auto CPU::power() -> void {
status.interruptPending = true; status.interruptPending = true;
status.dmaActive = false; status.dmaActive = false;
status.dmaCounter = 0;
status.dmaClocks = 0; status.dmaClocks = 0;
status.dmaPending = false; status.dmaPending = false;
status.hdmaPending = false; status.hdmaPending = false;

View File

@ -2,6 +2,7 @@ struct CPU : Processor::R65816, Thread, PPUcounter {
auto interruptPending() const -> bool override; auto interruptPending() const -> bool override;
auto pio() const -> uint8; auto pio() const -> uint8;
auto joylatch() const -> bool; auto joylatch() const -> bool;
auto synchronizing() const -> bool override;
CPU(); CPU();
@ -122,7 +123,6 @@ private:
//DMA //DMA
bool dmaActive; bool dmaActive;
uint dmaCounter;
uint dmaClocks; uint dmaClocks;
bool dmaPending; bool dmaPending;
bool hdmaPending; bool hdmaPending;

View File

@ -40,7 +40,6 @@ auto CPU::serialize(serializer& s) -> void {
s.integer(status.resetPending); s.integer(status.resetPending);
s.integer(status.dmaActive); s.integer(status.dmaActive);
s.integer(status.dmaCounter);
s.integer(status.dmaClocks); s.integer(status.dmaClocks);
s.integer(status.dmaPending); s.integer(status.dmaPending);
s.integer(status.hdmaPending); s.integer(status.hdmaPending);

View File

@ -1,19 +1,13 @@
auto CPU::dmaCounter() const -> uint { auto CPU::dmaCounter() const -> uint { return clockCounter & 7; }
return clockCounter & 7; auto CPU::joypadCounter() const -> uint { return clockCounter & 255; }
//return (status.dmaCounter + hcounter()) & 7;
}
auto CPU::joypadCounter() const -> uint {
return clockCounter & 255;
}
auto CPU::step(uint clocks) -> void { auto CPU::step(uint clocks) -> void {
status.irqLock = false; status.irqLock = false;
uint ticks = clocks >> 1; uint ticks = clocks >> 1;
while(ticks--) { while(ticks--) {
clockCounter += 2;
tick(); tick();
if(hcounter() & 2) pollInterrupts(); if(hcounter() & 2) pollInterrupts();
clockCounter += 2;
if(joypadCounter() == 0) joypadEdge(); if(joypadCounter() == 0) joypadEdge();
} }
@ -37,7 +31,6 @@ auto CPU::step(uint clocks) -> void {
//called by ppu.tick() when Hcounter=0 //called by ppu.tick() when Hcounter=0
auto CPU::scanline() -> void { auto CPU::scanline() -> void {
status.dmaCounter = (status.dmaCounter + status.lineClocks) & 7;
status.lineClocks = lineclocks(); status.lineClocks = lineclocks();
//forcefully sync S-CPU to other processors, in case chips are not communicating //forcefully sync S-CPU to other processors, in case chips are not communicating

View File

@ -2,7 +2,7 @@
//keeps track of previous counter positions in history table //keeps track of previous counter positions in history table
auto PPUcounter::tick() -> void { auto PPUcounter::tick() -> void {
status.hcounter += 2; //increment by smallest unit of time status.hcounter += 2; //increment by smallest unit of time
if(status.hcounter >= 1360 && status.hcounter == lineclocks()) { if(status.hcounter == lineclocks()) {
status.hcounter = 0; status.hcounter = 0;
vcounterTick(); vcounterTick();
} }
@ -27,12 +27,12 @@ auto PPUcounter::tick(uint clocks) -> void {
auto PPUcounter::vcounterTick() -> void { auto PPUcounter::vcounterTick() -> void {
if(++status.vcounter == 128) status.interlace = ppu.interlace(); if(++status.vcounter == 128) status.interlace = ppu.interlace();
if((system.region() == System::Region::NTSC && status.interlace == false && status.vcounter == 262) if((system.region() == System::Region::NTSC && status.interlace == 0 && status.vcounter == 262)
|| (system.region() == System::Region::NTSC && status.interlace == true && status.vcounter == 263) || (system.region() == System::Region::NTSC && status.interlace == 1 && status.vcounter == 263)
|| (system.region() == System::Region::NTSC && status.interlace == true && status.vcounter == 262 && status.field == 1) || (system.region() == System::Region::NTSC && status.interlace == 1 && status.vcounter == 262 && status.field == 1)
|| (system.region() == System::Region::PAL && status.interlace == false && status.vcounter == 312) || (system.region() == System::Region::PAL && status.interlace == 0 && status.vcounter == 312)
|| (system.region() == System::Region::PAL && status.interlace == true && status.vcounter == 313) || (system.region() == System::Region::PAL && status.interlace == 1 && status.vcounter == 313)
|| (system.region() == System::Region::PAL && status.interlace == true && status.vcounter == 312 && status.field == 1) || (system.region() == System::Region::PAL && status.interlace == 1 && status.vcounter == 312 && status.field == 1)
) { ) {
status.vcounter = 0; status.vcounter = 0;
status.field = !status.field; status.field = !status.field;
@ -58,7 +58,7 @@ auto PPUcounter::hcounter(uint offset) const -> uint16 { return history.hcounter
//dot 327 range = {1310, 1312, 1314} //dot 327 range = {1310, 1312, 1314}
auto PPUcounter::hdot() const -> uint16 { auto PPUcounter::hdot() const -> uint16 {
if(system.region() == System::Region::NTSC && status.interlace == false && vcounter() == 240 && field() == 1) { if(system.region() == System::Region::NTSC && status.interlace == 0 && vcounter() == 240 && field() == 1) {
return (hcounter() >> 2); return (hcounter() >> 2);
} else { } else {
return (hcounter() - ((hcounter() > 1292) << 1) - ((hcounter() > 1310) << 1)) >> 2; return (hcounter() - ((hcounter() > 1292) << 1) - ((hcounter() > 1310) << 1)) >> 2;
@ -66,12 +66,12 @@ auto PPUcounter::hdot() const -> uint16 {
} }
auto PPUcounter::lineclocks() const -> uint16 { auto PPUcounter::lineclocks() const -> uint16 {
if(system.region() == System::Region::NTSC && status.interlace == false && vcounter() == 240 && field() == 1) return 1360; if(system.region() == System::Region::NTSC && status.interlace == 0 && vcounter() == 240 && field() == 1) return 1360;
return 1364; return 1364;
} }
auto PPUcounter::reset() -> void { auto PPUcounter::reset() -> void {
status.interlace = false; status.interlace = 0;
status.field = 0; status.field = 0;
status.vcounter = 0; status.vcounter = 0;
status.hcounter = 0; status.hcounter = 0;

View File

@ -2,30 +2,56 @@
#include "hotkeys.cpp" #include "hotkeys.cpp"
unique_pointer<InputManager> inputManager; unique_pointer<InputManager> inputManager;
//build mappings list from assignment string
auto InputMapping::bind() -> void { auto InputMapping::bind() -> void {
auto token = assignment.split("/"); mappings.reset();
if(token.size() < 3) return unbind();
uint64 id = token[0].natural();
uint group = token[1].natural();
uint input = token[2].natural();
string qualifier = token(3, "None");
for(auto& device : inputManager->devices) { auto list = assignment.split(logic() == Logic::AND ? "&" : "|");
if(id != device->id()) continue; for(auto& item : list) {
auto token = item.split("/");
if(token.size() < 3) continue; //skip invalid mappings
this->device = device; uint64 id = token[0].natural();
this->group = group; uint group = token[1].natural();
this->input = input; uint input = token[2].natural();
this->qualifier = Qualifier::None; string qualifier = token(3, "None");
if(qualifier == "Lo") this->qualifier = Qualifier::Lo;
if(qualifier == "Hi") this->qualifier = Qualifier::Hi; Mapping mapping;
if(qualifier == "Rumble") this->qualifier = Qualifier::Rumble; for(auto& device : inputManager->devices) {
break; if(id != device->id()) continue;
mapping.device = device;
mapping.group = group;
mapping.input = input;
mapping.qualifier = Qualifier::None;
if(qualifier == "Lo") mapping.qualifier = Qualifier::Lo;
if(qualifier == "Hi") mapping.qualifier = Qualifier::Hi;
if(qualifier == "Rumble") mapping.qualifier = Qualifier::Rumble;
break;
}
if(!mapping.device) continue;
mappings.append(mapping);
} }
settings[path].setValue(assignment); settings[path].setValue(assignment);
} }
//append new mapping to mappings list
auto InputMapping::bind(string mapping) -> void {
auto list = assignment.split(logic() == Logic::AND ? "&" : "|");
if(list.find(mapping)) return; //already in the mappings list
if(!assignment || assignment == "None") {
//create new mapping
assignment = mapping;
} else {
//add additional mapping
assignment.append(logic() == Logic::AND ? "&" : "|");
assignment.append(mapping);
}
bind();
}
auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool { auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool {
if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) { if(device->isNull() || (device->isKeyboard() && device->group(group).input(input).name() == "Escape")) {
return unbind(), true; return unbind(), true;
@ -38,8 +64,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|| (device->isMouse() && group == HID::Mouse::GroupID::Button) || (device->isMouse() && group == HID::Mouse::GroupID::Button)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Button)) { || (device->isJoypad() && group == HID::Joypad::GroupID::Button)) {
if(newValue) { if(newValue) {
this->assignment = encoding; return bind(encoding), true;
return bind(), true;
} }
} }
@ -47,13 +72,11 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat) || (device->isJoypad() && group == HID::Joypad::GroupID::Hat)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) { || (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) {
if(newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi if(newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi
this->assignment = {encoding, "/Lo"}; return bind({encoding, "/Lo"}), true;
return bind(), true;
} }
if(newValue > +16384) { if(newValue > +16384) {
this->assignment = {encoding, "/Hi"}; return bind({encoding, "/Hi"}), true;
return bind(), true;
} }
} }
} }
@ -63,8 +86,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
|| (device->isJoypad() && group == HID::Joypad::GroupID::Axis) || (device->isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) { || (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) {
if(newValue < -16384 || newValue > +16384) { if(newValue < -16384 || newValue > +16384) {
this->assignment = encoding; return bind(encoding), true;
return bind(), true;
} }
} }
} }
@ -72,8 +94,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
if(isRumble()) { if(isRumble()) {
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) { if(device->isJoypad() && group == HID::Joypad::GroupID::Button) {
if(newValue) { if(newValue) {
this->assignment = {encoding, "/Rumble"}; return bind({encoding, "/Rumble"}), true;
return bind(), true;
} }
} }
} }
@ -82,62 +103,86 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, uint group, uint inp
} }
auto InputMapping::poll() -> int16 { auto InputMapping::poll() -> int16 {
if(!device) return 0; if(!mappings) return 0;
auto value = device->group(group).input(input).value();
if(isDigital()) { if(isDigital()) {
if(device->isKeyboard() && group == HID::Keyboard::GroupID::Button) return value != 0; bool result = logic() == Logic::AND ? 1 : 0;
if(device->isMouse() && group == HID::Mouse::GroupID::Button) return value != 0;
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) return value != 0; for(auto& mapping : mappings) {
if((device->isJoypad() && group == HID::Joypad::GroupID::Axis) auto value = mapping.device->group(mapping.group).input(mapping.input).value();
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat) bool output = logic() == Logic::AND ? 0 : 1;
|| (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) {
if(qualifier == Qualifier::Lo) return value < -16384; if(mapping.device->isKeyboard() && mapping.group == HID::Keyboard::GroupID::Button) output = value != 0;
if(qualifier == Qualifier::Hi) return value > +16384; if(mapping.device->isMouse() && mapping.group == HID::Mouse::GroupID::Button) output = value != 0;
if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Button) output = value != 0;
if((mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Axis)
|| (mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Hat)
|| (mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Trigger)) {
if(mapping.qualifier == Qualifier::Lo) output = value < -16384;
if(mapping.qualifier == Qualifier::Hi) output = value > +16384;
}
if(logic() == Logic::AND) result &= output;
if(logic() == Logic::OR ) result |= output;
} }
return result;
} }
if(isAnalog()) { if(isAnalog()) {
if(device->isMouse() && group == HID::Mouse::GroupID::Axis) return value; int16 result = 0;
if(device->isJoypad() && group == HID::Joypad::GroupID::Axis) return value >> 8;
if(device->isJoypad() && group == HID::Joypad::GroupID::Hat) return value < 0 ? -1 : value > 0 ? +1 : 0; for(auto& mapping : mappings) {
auto value = mapping.device->group(mapping.group).input(mapping.input).value();
//logic does not apply to analog inputs ... always combinatorial
if(mapping.device->isMouse() && mapping.group == HID::Mouse::GroupID::Axis) result += value;
if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Axis) result += value >> 8;
if(mapping.device->isJoypad() && mapping.group == HID::Joypad::GroupID::Hat) result += value < 0 ? -1 : value > 0 ? +1 : 0;
}
return result;
} }
return 0; return 0;
} }
auto InputMapping::rumble(bool enable) -> void { auto InputMapping::rumble(bool enable) -> void {
if(!device) return; if(!mappings) return;
::input->rumble(device->id(), enable); for(auto& mapping : mappings) {
::input->rumble(mapping.device->id(), enable);
}
} }
auto InputMapping::unbind() -> void { auto InputMapping::unbind() -> void {
mappings.reset();
assignment = "None"; assignment = "None";
device = nullptr;
group = 0;
input = 0;
qualifier = Qualifier::None;
settings[path].setValue(assignment); settings[path].setValue(assignment);
} }
auto InputMapping::assignmentName() -> string { //create a human-readable string from mappings list for display in the user interface
if(!device) return "None"; auto InputMapping::displayName() -> string {
string path; if(!mappings) return "None";
path.append(device->name());
if(device->name() != "Keyboard") {
//keyboards only have one group; no need to append group name
path.append(".", device->group(group).name());
}
path.append(".", device->group(group).input(input).name());
if(qualifier == Qualifier::Lo) path.append(".Lo");
if(qualifier == Qualifier::Hi) path.append(".Hi");
if(qualifier == Qualifier::Rumble) path.append(".Rumble");
return path;
}
auto InputMapping::deviceName() -> string { string path;
if(!device) return ""; for(auto& mapping : mappings) {
return hex(device->id()); path.append(mapping.device->name());
if(mapping.device->name() != "Keyboard" && mapping.device->name() != "Mouse") {
//show device IDs to distinguish between multiple joypads
path.append("(", hex(mapping.device->id()), ")");
}
if(mapping.device->name() != "Keyboard") {
//keyboards only have one group; no need to append group name
path.append(".", mapping.device->group(mapping.group).name());
}
path.append(".", mapping.device->group(mapping.group).input(mapping.input).name());
if(mapping.qualifier == Qualifier::Lo) path.append(".Lo");
if(mapping.qualifier == Qualifier::Hi) path.append(".Hi");
if(mapping.qualifier == Qualifier::Rumble) path.append(".Rumble");
path.append(logic() == Logic::AND ? " and " : " or ");
}
return path.trimRight(logic() == Logic::AND ? " and " : " or ", 1L);
} }
// //

View File

@ -1,5 +1,6 @@
struct InputMapping { struct InputMapping {
auto bind() -> void; auto bind() -> void;
auto bind(string mapping) -> void;
auto bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool; auto bind(shared_pointer<HID::Device> device, uint group, uint input, int16 oldValue, int16 newValue) -> bool;
auto poll() -> int16; auto poll() -> int16;
auto rumble(bool enable) -> void; auto rumble(bool enable) -> void;
@ -9,20 +10,28 @@ struct InputMapping {
auto isAnalog() const -> bool { return type == 1; } auto isAnalog() const -> bool { return type == 1; }
auto isRumble() const -> bool { return type == 2; } auto isRumble() const -> bool { return type == 2; }
auto assignmentName() -> string; auto displayName() -> string;
auto deviceName() -> string;
string path; //configuration file key path string path; //configuration file key path
string name; //input name (human readable) string name; //input name (human readable)
uint type = 0; uint type = 0;
string assignment = "None"; string assignment = "None";
shared_pointer<HID::Device> device;
uint group = 0; enum class Logic : uint { AND, OR };
uint input = 0; enum class Qualifier : uint { None, Lo, Hi, Rumble };
enum class Qualifier : uint { None, Lo, Hi, Rumble } qualifier = Qualifier::None; virtual auto logic() const -> Logic { return Logic::OR; }
struct Mapping {
shared_pointer<HID::Device> device;
uint group = 0;
uint input = 0;
Qualifier qualifier = Qualifier::None;
};
vector<Mapping> mappings;
}; };
struct InputHotkey : InputMapping { struct InputHotkey : InputMapping {
auto logic() const -> Logic override { return Logic::AND; }
function<auto () -> void> press; function<auto () -> void> press;
function<auto () -> void> release; function<auto () -> void> release;

View File

@ -29,13 +29,11 @@ auto HotkeySettings::reloadMappings() -> void {
mappingList.append(TableViewHeader().setVisible() mappingList.append(TableViewHeader().setVisible()
.append(TableViewColumn().setText("Name")) .append(TableViewColumn().setText("Name"))
.append(TableViewColumn().setText("Mapping").setExpandable()) .append(TableViewColumn().setText("Mapping").setExpandable())
.append(TableViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0}))
); );
for(auto& hotkey : inputManager->hotkeys) { for(auto& hotkey : inputManager->hotkeys) {
mappingList.append(TableViewItem() mappingList.append(TableViewItem()
.append(TableViewCell().setText(hotkey->name)) .append(TableViewCell().setText(hotkey->name))
.append(TableViewCell()) .append(TableViewCell())
.append(TableViewCell())
); );
} }
mappingList.resizeColumns(); mappingList.resizeColumns();
@ -44,8 +42,7 @@ auto HotkeySettings::reloadMappings() -> void {
auto HotkeySettings::refreshMappings() -> void { auto HotkeySettings::refreshMappings() -> void {
uint position = 0; uint position = 0;
for(auto& hotkey : inputManager->hotkeys) { for(auto& hotkey : inputManager->hotkeys) {
mappingList.item(position).cell(1).setText(hotkey->assignmentName()); mappingList.item(position).cell(1).setText(hotkey->displayName());
mappingList.item(position).cell(2).setText(hotkey->deviceName());
position++; position++;
} }
mappingList.resizeColumns(); mappingList.resizeColumns();

View File

@ -95,13 +95,11 @@ auto InputSettings::reloadMappings() -> void {
mappingList.append(TableViewHeader().setVisible() mappingList.append(TableViewHeader().setVisible()
.append(TableViewColumn().setText("Name")) .append(TableViewColumn().setText("Name"))
.append(TableViewColumn().setText("Mapping").setExpandable()) .append(TableViewColumn().setText("Mapping").setExpandable())
.append(TableViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0}))
); );
for(auto& mapping : activeDevice().mappings) { for(auto& mapping : activeDevice().mappings) {
mappingList.append(TableViewItem() mappingList.append(TableViewItem()
.append(TableViewCell().setText(mapping.name)) .append(TableViewCell().setText(mapping.name))
.append(TableViewCell()) .append(TableViewCell())
.append(TableViewCell())
); );
} }
refreshMappings(); refreshMappings();
@ -110,8 +108,7 @@ auto InputSettings::reloadMappings() -> void {
auto InputSettings::refreshMappings() -> void { auto InputSettings::refreshMappings() -> void {
uint position = 0; uint position = 0;
for(auto& mapping : activeDevice().mappings) { for(auto& mapping : activeDevice().mappings) {
mappingList.item(position).cell(1).setText(mapping.assignmentName()); mappingList.item(position).cell(1).setText(mapping.displayName());
mappingList.item(position).cell(2).setText(mapping.deviceName());
position++; position++;
} }
mappingList.resizeColumns(); mappingList.resizeColumns();

View File

@ -24,7 +24,7 @@ struct Pair {
auto operator!() const -> bool { return !(hi || lo); } auto operator!() const -> bool { return !(hi || lo); }
auto operator++() -> Pair& { lo++; hi += lo == 0; return *this; } auto operator++() -> Pair& { lo++; hi += lo == 0; return *this; }
auto operator--() -> Pair& { hi -= lo == 0; lo--; } auto operator--() -> Pair& { hi -= lo == 0; lo--; return *this; }
auto operator++(int) -> Pair { Pair r = *this; lo++; hi += lo == 0; return r; } auto operator++(int) -> Pair { Pair r = *this; lo++; hi += lo == 0; return r; }
auto operator--(int) -> Pair { Pair r = *this; hi -= lo == 0; lo--; return r; } auto operator--(int) -> Pair { Pair r = *this; hi -= lo == 0; lo--; return r; }