From 73be2e729cedacb962c54dfb00e21fd2037491e6 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sat, 21 Dec 2013 21:45:58 +1100 Subject: [PATCH] Update to v093r11 release. byuu says: Changelog: - GBA: SOUND_CTL_H is readable, fixes sound effects in Mario&Luigi Superstar Saga [Cydrak] (note: game is still unplayable due to other bugs) - phoenix/Windows: workaround for Win32 API ListView bug, fixes slot loading behavior - ruby: added udev driver for Linux with rumble support, and added rumble support to existing RawInput driver for XInput and DirectInput - ethos: added new "Rumble" mapping to GBA input assignment, use it to tell higan which controller to rumble (clear it to disable rumble) - GBA: Game Boy Player rumble is now fully emulated - core: added new normalized raw-color palette mode for Display Emulation shaders The way rumble was added to ethos was somewhat hackish. The support doesn't really exist in nall. I need to redesign the entire input system, but that's not a change I want to make so close to a release. --- emulator/emulator.hpp | 3 +- emulator/interface.hpp | 10 +- fc/video/video.cpp | 22 ++- gb/video/video.cpp | 73 ++++--- gba/apu/mmio.cpp | 7 + gba/interface/interface.cpp | 23 +-- gba/player/player.cpp | 6 +- gba/video/video.cpp | 32 +++- nall/platform.hpp | 2 +- phoenix/windows/application.cpp | 18 +- phoenix/windows/platform.hpp | 10 + phoenix/windows/utility.cpp | 9 +- phoenix/windows/widget/list-view.cpp | 10 +- ruby/Makefile | 1 + ruby/implementation.cpp | 13 +- ruby/input.hpp | 2 + ruby/input/carbon.cpp | 3 + ruby/input/directinput.cpp | 11 +- ruby/input/joypad/udev.cpp | 254 ++++++++++++++++++++++++ ruby/input/joypad/xinput.cpp | 101 ++++++++++ ruby/input/keyboard/xlib.cpp | 277 +++++++++++++++++++++++++++ ruby/input/mouse/xlib.cpp | 128 +++++++++++++ ruby/input/rawinput.cpp | 220 +++++++++------------ ruby/input/sdl.cpp | 5 +- ruby/input/udev.cpp | 86 +++++++++ ruby/input/x.cpp | 5 +- ruby/ruby.cpp | 14 ++ ruby/ruby.hpp | 6 +- sfc/chip/icd2/icd2.cpp | 2 +- sfc/system/video.cpp | 25 ++- target-ethos/Makefile | 4 +- target-ethos/input/input.cpp | 51 ++++- target-ethos/input/input.hpp | 10 +- target-ethos/interface/interface.cpp | 57 +++--- target-ethos/interface/interface.hpp | 3 +- target-ethos/utility/utility.cpp | 7 +- 36 files changed, 1247 insertions(+), 263 deletions(-) create mode 100644 ruby/input/joypad/udev.cpp create mode 100644 ruby/input/joypad/xinput.cpp create mode 100644 ruby/input/keyboard/xlib.cpp create mode 100644 ruby/input/mouse/xlib.cpp create mode 100644 ruby/input/udev.cpp diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index 905d6f0d..da1fd25e 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "093.10"; + static const char Version[] = "093.11"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; @@ -27,6 +27,7 @@ namespace Emulator { #include #include #include +#include #include #include #include diff --git a/emulator/interface.hpp b/emulator/interface.hpp index b83ca2a6..cb2be617 100644 --- a/emulator/interface.hpp +++ b/emulator/interface.hpp @@ -31,7 +31,7 @@ struct Interface { string name; struct Input { unsigned id; - unsigned type; //0 = digital, 1 = analog (relative), 2 = analog (absolute) + unsigned type; //0 = digital, 1 = analog (relative), 2 = analog (absolute), 3 = rumble string name; unsigned guid; }; @@ -50,10 +50,11 @@ struct Interface { virtual void loadRequest(unsigned, string, string) {} virtual void loadRequest(unsigned, string) {} virtual void saveRequest(unsigned, string) {} - virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t) { return 0u; } + virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) { return 0u; } virtual void videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) {} virtual void audioSample(int16_t, int16_t) {} virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; } + virtual void inputRumble(unsigned, unsigned, unsigned, bool) {} virtual unsigned dipSettings(const Markup::Node&) { return 0; } virtual string path(unsigned) { return ""; } virtual string server() { return ""; } @@ -65,10 +66,11 @@ struct Interface { void loadRequest(unsigned id, string name, string type) { return bind->loadRequest(id, name, type); } void loadRequest(unsigned id, string path) { return bind->loadRequest(id, path); } void saveRequest(unsigned id, string path) { return bind->saveRequest(id, path); } - uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) { return bind->videoColor(source, red, green, blue); } + uint32_t videoColor(unsigned source, uint16_t alpha, uint16_t red, uint16_t green, uint16_t blue) { return bind->videoColor(source, alpha, red, green, blue); } void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(palette, data, pitch, width, height); } void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); } int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); } + void inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { return bind->inputRumble(port, device, input, enable); } unsigned dipSettings(const Markup::Node& node) { return bind->dipSettings(node); } string path(unsigned group) { return bind->path(group); } string server() { return bind->server(); } @@ -107,7 +109,7 @@ struct Interface { virtual void cheatSet(const lstring& = lstring{}) {} //utility functions - enum class PaletteMode : unsigned { None, Standard, Emulation }; + enum class PaletteMode : unsigned { Literal, Channel, Standard, Emulation }; virtual void paletteUpdate(PaletteMode mode) {} //debugger functions diff --git a/fc/video/video.cpp b/fc/video/video.cpp index 0df6e5b7..bdb476a0 100644 --- a/fc/video/video.cpp +++ b/fc/video/video.cpp @@ -7,11 +7,21 @@ namespace Famicom { Video video; void Video::generate_palette(Emulator::Interface::PaletteMode mode) { - for(unsigned n = 0; n < (1 << 9); n++) { - switch(mode) { - case Emulator::Interface::PaletteMode::None: palette[n] = n; break; - case Emulator::Interface::PaletteMode::Standard: palette[n] = generate_color(n, 2.0, 0.0, 1.0, 1.0, 2.2); break; - case Emulator::Interface::PaletteMode::Emulation: palette[n] = generate_color(n, 2.0, 0.0, 1.0, 1.0, 1.8); break; + for(unsigned color = 0; color < (1 << 9); color++) { + if(mode == Emulator::Interface::PaletteMode::Literal) { + palette[color] = color; + } else if(mode == Emulator::Interface::PaletteMode::Channel) { + unsigned emphasis = (color >> 6) & 7; + unsigned luma = (color >> 4) & 3; + unsigned chroma = (color >> 0) & 15; + emphasis = image::normalize(emphasis, 3, 16); + luma = image::normalize(luma, 2, 16); + chroma = image::normalize(chroma, 4, 16); + palette[color] = interface->videoColor(color, 0, emphasis, luma, chroma); + } else if(mode == Emulator::Interface::PaletteMode::Standard) { + palette[color] = generate_color(color, 2.0, 0.0, 1.0, 1.0, 2.2); + } else if(mode == Emulator::Interface::PaletteMode::Emulation) { + palette[color] = generate_color(color, 2.0, 0.0, 1.0, 1.0, 1.8); } } } @@ -69,7 +79,7 @@ uint32_t Video::generate_color( unsigned g = 65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q); unsigned b = 65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q); - return interface->videoColor(n, uclamp<16>(r), uclamp<16>(g), uclamp<16>(b)); + return interface->videoColor(n, 0, uclamp<16>(r), uclamp<16>(g), uclamp<16>(b)); } } diff --git a/gb/video/video.cpp b/gb/video/video.cpp index 4c0a4e78..f08a9400 100644 --- a/gb/video/video.cpp +++ b/gb/video/video.cpp @@ -21,18 +21,28 @@ Video::~Video() { } unsigned Video::palette_dmg(unsigned color) const { - if(mode == Emulator::Interface::PaletteMode::None) return color; - - if(mode == Emulator::Interface::PaletteMode::Standard) { - unsigned L = (3 - color) * 21845; - return interface->videoColor(color, L, L, L); + if(mode == Emulator::Interface::PaletteMode::Literal) { + return color; } - unsigned R = monochrome[color][0]; - unsigned G = monochrome[color][1]; - unsigned B = monochrome[color][2]; + if(mode == Emulator::Interface::PaletteMode::Channel) { + unsigned L = image::normalize(color, 2, 16); + return interface->videoColor(color, 0, 0, 0, L); + } - return interface->videoColor(color, R, G, B); + if(mode == Emulator::Interface::PaletteMode::Standard) { + unsigned L = image::normalize(3 - color, 2, 16); + return interface->videoColor(color, 0, L, L, L); + } + + if(mode == Emulator::Interface::PaletteMode::Emulation) { + unsigned R = monochrome[color][0]; + unsigned G = monochrome[color][1]; + unsigned B = monochrome[color][2]; + return interface->videoColor(color, 0, R, G, B); + } + + return 0; } unsigned Video::palette_sgb(unsigned color) const { @@ -40,32 +50,45 @@ unsigned Video::palette_sgb(unsigned color) const { } unsigned Video::palette_cgb(unsigned color) const { - if(mode == Emulator::Interface::PaletteMode::None) return color; + if(mode == Emulator::Interface::PaletteMode::Literal) { + return color; + } unsigned r = (color >> 0) & 31; unsigned g = (color >> 5) & 31; unsigned b = (color >> 10) & 31; - if(mode == Emulator::Interface::PaletteMode::Standard) { - unsigned R = (r << 11) | (r << 6) | (r << 1) | (r >> 4); - unsigned G = (g << 11) | (g << 6) | (g << 1) | (g >> 4); - unsigned B = (b << 11) | (b << 6) | (b << 1) | (b >> 4); - return interface->videoColor(color, R, G, B); + if(mode == Emulator::Interface::PaletteMode::Channel) { + r = image::normalize(r, 5, 16); + g = image::normalize(g, 5, 16); + b = image::normalize(b, 5, 16); + return interface->videoColor(color, 0, r, g, b); } - unsigned R = (r * 26 + g * 4 + b * 2); - unsigned G = ( g * 24 + b * 8); - unsigned B = (r * 6 + g * 4 + b * 22); + if(mode == Emulator::Interface::PaletteMode::Standard) { + r = image::normalize(r, 5, 16); + g = image::normalize(g, 5, 16); + b = image::normalize(b, 5, 16); + return interface->videoColor(color, 0, r, g, b); + } - R = min(960, R); - G = min(960, G); - B = min(960, B); + if(mode == Emulator::Interface::PaletteMode::Emulation) { + unsigned R = (r * 26 + g * 4 + b * 2); + unsigned G = ( g * 24 + b * 8); + unsigned B = (r * 6 + g * 4 + b * 22); - R = R << 6 | R >> 4; - G = G << 6 | G >> 4; - B = B << 6 | B >> 4; + R = min(960, R); + G = min(960, G); + B = min(960, B); - return interface->videoColor(color, R, G, B); + R = R << 6 | R >> 4; + G = G << 6 | G >> 4; + B = B << 6 | B >> 4; + + return interface->videoColor(color, 0, R, G, B); + } + + return 0; } #define DMG_PALETTE_GREEN diff --git a/gba/apu/mmio.cpp b/gba/apu/mmio.cpp index 39d7a22e..cae754af 100644 --- a/gba/apu/mmio.cpp +++ b/gba/apu/mmio.cpp @@ -45,6 +45,13 @@ uint8 APU::read(uint32 addr) { case 0x04000080: return sequencer.read(0); case 0x04000081: return sequencer.read(1); + //SOUND_CNT_H + case 0x04000082: + return (fifo[1].volume << 3) | (fifo[0].volume << 2) | (sequencer.volume << 0); + case 0x04000083: + return (fifo[1].timer << 6) | (fifo[1].lenable << 5) | (fifo[1].renable << 4) + | (fifo[0].timer << 2) | (fifo[0].lenable << 1) | (fifo[0].renable << 0); + //NR52 case 0x04000084: return sequencer.read(2); case 0x04000085: return 0u; diff --git a/gba/interface/interface.cpp b/gba/interface/interface.cpp index 50991dc3..54b2b0a3 100644 --- a/gba/interface/interface.cpp +++ b/gba/interface/interface.cpp @@ -129,17 +129,18 @@ Interface::Interface() { { Device device{0, ID::Device, "Controller"}; - device.input.append({0, 0, "A" }); - device.input.append({1, 0, "B" }); - device.input.append({2, 0, "Select"}); - device.input.append({3, 0, "Start" }); - device.input.append({4, 0, "Right" }); - device.input.append({5, 0, "Left" }); - device.input.append({6, 0, "Up" }); - device.input.append({7, 0, "Down" }); - device.input.append({8, 0, "R" }); - device.input.append({9, 0, "L" }); - device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3}; + device.input.append({ 0, 0, "A" }); + device.input.append({ 1, 0, "B" }); + device.input.append({ 2, 0, "Select"}); + device.input.append({ 3, 0, "Start" }); + device.input.append({ 4, 0, "Right" }); + device.input.append({ 5, 0, "Left" }); + device.input.append({ 6, 0, "Up" }); + device.input.append({ 7, 0, "Down" }); + device.input.append({ 8, 0, "R" }); + device.input.append({ 9, 0, "L" }); + device.input.append({10, 3, "Rumble"}); + device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3, 10}; this->device.append(device); } diff --git a/gba/player/player.cpp b/gba/player/player.cpp index 093636ae..a1229532 100644 --- a/gba/player/player.cpp +++ b/gba/player/player.cpp @@ -54,11 +54,6 @@ void Player::frame() { } cpu.regs.irq.flag.serial = true; } - - if(status.rumble) { - //todo: support actual gamepad rumble; for now, color screen red during rumble - for(unsigned n = 0; n < 240 * 160; n++) ppu.output[n] &= 0x001f; - } } optional Player::keyinput() { @@ -87,6 +82,7 @@ void Player::write(uint8 byte, uint2 addr) { if(addr == 3 && status.packet == 15) { status.rumble = (status.recv & 0xff) == 0x26; //on = 0x26, off = 0x04 + interface->inputRumble(0, 0, 10, status.rumble); } } diff --git a/gba/video/video.cpp b/gba/video/video.cpp index c9abe4ce..3b6afa97 100644 --- a/gba/video/video.cpp +++ b/gba/video/video.cpp @@ -6,7 +6,7 @@ Video video; void Video::generate_palette(Emulator::Interface::PaletteMode mode) { for(unsigned color = 0; color < (1 << 15); color++) { - if(mode == Emulator::Interface::PaletteMode::None) { + if(mode == Emulator::Interface::PaletteMode::Literal) { palette[color] = color; continue; } @@ -15,11 +15,23 @@ void Video::generate_palette(Emulator::Interface::PaletteMode mode) { unsigned G = (color >> 5) & 31; unsigned R = (color >> 0) & 31; + if(mode == Emulator::Interface::PaletteMode::Channel) { + R = image::normalize(R, 5, 16); + G = image::normalize(G, 5, 16); + B = image::normalize(B, 5, 16); + palette[color] = interface->videoColor(color, 0, R, G, B); + continue; + } + if(mode == Emulator::Interface::PaletteMode::Standard) { - R = R << 11 | R << 6 | R << 1 | R >> 4; - G = G << 11 | G << 6 | G << 1 | G >> 4; - B = B << 11 | B << 6 | B << 1 | B >> 4; - } else { + R = image::normalize(R, 5, 16); + G = image::normalize(G, 5, 16); + B = image::normalize(B, 5, 16); + palette[color] = interface->videoColor(color, 0, R, G, B); + continue; + } + + if(mode == Emulator::Interface::PaletteMode::Emulation) { R = curve[R]; G = curve[G]; B = curve[B]; @@ -52,12 +64,14 @@ void Video::generate_palette(Emulator::Interface::PaletteMode mode) { G = (((4 * Gr + 2 * Gg + Gb) * 160) >> 14) + 32; B = (((4 * Br + 2 * Bg + Bb) * 160) >> 14) + 32; - R = R << 8 | R; - G = G << 8 | G; - B = B << 8 | B; + R = image::normalize(R, 8, 16); + G = image::normalize(G, 8, 16); + B = image::normalize(B, 8, 16); + palette[color] = interface->videoColor(color, 0, R, G, B); + continue; } - palette[color] = interface->videoColor(color, R, G, B); + palette[color] = 0; } } diff --git a/nall/platform.hpp b/nall/platform.hpp index 9c7a35d6..7273076c 100644 --- a/nall/platform.hpp +++ b/nall/platform.hpp @@ -56,7 +56,7 @@ namespace Math { #endif #if defined(_WIN32) - extern "C" int _fileno(FILE*); + __declspec(dllimport) int _fileno(FILE*); inline int access(const char* path, int amode) { return _waccess(nall::utf16_t(path), amode); } inline int fileno(FILE* stream) { return _fileno(stream); } diff --git a/phoenix/windows/application.cpp b/phoenix/windows/application.cpp index cd04d5f4..063fbe98 100644 --- a/phoenix/windows/application.cpp +++ b/phoenix/windows/application.cpp @@ -2,6 +2,7 @@ namespace phoenix { static bool Application_keyboardProc(HWND, UINT, WPARAM, LPARAM); static void Application_processDialogMessage(MSG&); +static void Application_processMessageQueue(); static LRESULT CALLBACK Application_windowProc(HWND, UINT, WPARAM, LPARAM); void pApplication::run() { @@ -38,13 +39,24 @@ void Application_processDialogMessage(MSG& msg) { || msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP) { if(Application_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam)) { DispatchMessage(&msg); - return; + return Application_processMessageQueue(); } } if(!IsDialogMessage(GetForegroundWindow(), &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); + Application_processMessageQueue(); + } +} + +static void Application_processMessageQueue() { + while(!messageQueue.empty()) { + Message message = messageQueue.takeFirst(); + if(message.type == Message::Type::ListView_OnActivate) { + ListView* listView = (ListView*)message.object; + if(listView->onActivate) listView->onActivate(); + } } } @@ -134,9 +146,7 @@ static bool Application_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM if(dynamic_cast(object)) { ListView& listView = (ListView&)*object; if(wparam == VK_RETURN) { - if(listView.state.text.size() && listView.selected()) { - if(listView.onActivate) listView.onActivate(); - } + if(listView.selected()) return true; //returning true generates LVN_ITEMACTIVATE message } } else if(dynamic_cast(object)) { LineEdit& lineEdit = (LineEdit&)*object; diff --git a/phoenix/windows/platform.hpp b/phoenix/windows/platform.hpp index e8242357..a4e16ee7 100644 --- a/phoenix/windows/platform.hpp +++ b/phoenix/windows/platform.hpp @@ -2,6 +2,16 @@ namespace phoenix { typedef LRESULT CALLBACK (*WindowProc)(HWND, UINT, WPARAM, LPARAM); +struct Message { + enum class Type : unsigned { + ListView_OnActivate, + }; + Type type; + Object* object; +}; + +static vector messageQueue; + struct pApplication { static void run(); static bool pendingEvents(); diff --git a/phoenix/windows/utility.cpp b/phoenix/windows/utility.cpp index 1379f398..78c174d8 100644 --- a/phoenix/windows/utility.cpp +++ b/phoenix/windows/utility.cpp @@ -255,14 +255,7 @@ static LRESULT CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT } else if(!GetParentWidget((Sizable*)object) && window.p.brush) { SetBkColor((HDC)wparam, window.p.brushColor); return (INT_PTR)window.p.brush; - }/* else { - //this will repaint the background properly, but the foreground isn't always rendered after ... - RECT rc; - GetClientRect((HWND)lparam, &rc); - DrawThemeParentBackground((HWND)lparam, (HDC)wparam, &rc); - SetBkMode((HDC)wparam, TRANSPARENT); - return (INT_PTR)GetStockBrush(HOLLOW_BRUSH); - }*/ + } break; } diff --git a/phoenix/windows/widget/list-view.cpp b/phoenix/windows/widget/list-view.cpp index 67df09d2..a8e3c179 100644 --- a/phoenix/windows/widget/list-view.cpp +++ b/phoenix/windows/widget/list-view.cpp @@ -212,7 +212,9 @@ void pListView::buildImageList() { void pListView::onActivate(LPARAM lparam) { LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; if(listView.state.text.empty() || !listView.state.selected) return; - if(listView.onActivate) listView.onActivate(); +//LVN_ITEMACTIVATE is not re-entrant until DispatchMessage() completes +//if(listView.onActivate) listView.onActivate(); + messageQueue.append({Message::Type::ListView_OnActivate, (Object*)&listView}); } void pListView::onChange(LPARAM lparam) { @@ -228,6 +230,8 @@ void pListView::onChange(LPARAM lparam) { } } else if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { lostFocus = true; + listView.state.selected = false; + listView.state.selection = 0; } else if(!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) { lostFocus = false; listView.state.selected = true; @@ -238,6 +242,10 @@ void pListView::onChange(LPARAM lparam) { listView.state.selected = false; listView.state.selection = 0; if(!locked && listView.onChange) listView.onChange(); + } else if(listView.selected() && ListView_GetSelectedCount(hwnd) == 0) { + listView.state.selected = false; + listView.state.selection = 0; + if(!locked && listView.onChange) listView.onChange(); } } diff --git a/ruby/Makefile b/ruby/Makefile index c83a3552..ab6e8920 100644 --- a/ruby/Makefile +++ b/ruby/Makefile @@ -25,6 +25,7 @@ rubylink += $(if $(findstring audio.xaudio2,$(ruby)),-lole32) rubylink += $(if $(findstring input.directinput,$(ruby)),-ldinput8 -ldxguid) rubylink += $(if $(findstring input.rawinput,$(ruby)),-ldinput8 -ldxguid) +rubylink += $(if $(findstring input.udev,$(ruby)),-ludev) rubylink += $(if $(findstring .sdl,$(ruby)),`sdl-config --libs`) diff --git a/ruby/implementation.cpp b/ruby/implementation.cpp index 723f86bd..7b187fc1 100644 --- a/ruby/implementation.cpp +++ b/ruby/implementation.cpp @@ -143,6 +143,7 @@ using namespace nall; bool acquired() { return p.acquired(); } \ \ bool poll(int16_t* table) { return p.poll(table); } \ + void rumble(unsigned id, bool enable) { return p.rumble(id, enable); } \ bool init() { return p.init(); } \ void term() { p.term(); } \ \ @@ -153,6 +154,10 @@ using namespace nall; pInput##Name& p; \ }; +#ifdef INPUT_CARBON + #include +#endif + #ifdef INPUT_DIRECTINPUT #include #endif @@ -161,14 +166,14 @@ using namespace nall; #include #endif -#ifdef INPUT_CARBON - #include -#endif - #ifdef INPUT_SDL #include #endif +#ifdef INPUT_UDEV + #include +#endif + #ifdef INPUT_X #include #endif diff --git a/ruby/input.hpp b/ruby/input.hpp index e69d6f13..8da70de2 100644 --- a/ruby/input.hpp +++ b/ruby/input.hpp @@ -3,6 +3,7 @@ struct Input { static const char* KeyboardSupport; static const char* MouseSupport; static const char* JoypadSupport; + static const char* JoypadRumbleSupport; virtual bool cap(const nall::string& name) { return false; } virtual nall::any get(const nall::string& name) { return false; } @@ -13,6 +14,7 @@ struct Input { virtual bool acquired() { return false; } virtual bool poll(int16_t* table) { return false; } + virtual void rumble(unsigned id, bool enable) {} virtual bool init() { return true; } virtual void term() {} diff --git a/ruby/input/carbon.cpp b/ruby/input/carbon.cpp index c1f3efa3..d9fb855c 100644 --- a/ruby/input/carbon.cpp +++ b/ruby/input/carbon.cpp @@ -143,6 +143,9 @@ struct pInputCarbon { return true; } + void rumble(unsigned id, bool enable) { + } + bool init() { return true; } diff --git a/ruby/input/directinput.cpp b/ruby/input/directinput.cpp index 788945db..05a7b1e5 100644 --- a/ruby/input/directinput.cpp +++ b/ruby/input/directinput.cpp @@ -251,7 +251,10 @@ public: return true; } - bool init_joypad(const DIDEVICEINSTANCE* instance) { + void rumble(unsigned id, bool enable) { + } + + bool initJoypad(const DIDEVICEINSTANCE* instance) { unsigned n; for(n = 0; n < Joypad::Count; n++) { if(!device.gamepad[n]) break; } if(n >= Joypad::Count) return DIENUM_STOP; @@ -267,7 +270,7 @@ public: return DIENUM_CONTINUE; } - bool init_axis(const DIDEVICEOBJECTINSTANCE* instance) { + bool initAxis(const DIDEVICEOBJECTINSTANCE* instance) { signed n; for(n = Joypad::Count - 1; n >= 0; n--) { if(device.gamepad[n]) break; } if(n < 0) return DIENUM_STOP; @@ -375,11 +378,11 @@ public: }; BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) { - return ((pInputDI*)p)->init_joypad(instance); + return ((pInputDI*)p)->initJoypad(instance); } BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { - return ((pInputDI*)p)->init_axis(instance); + return ((pInputDI*)p)->initAxis(instance); } DeclareInput(DI) diff --git a/ruby/input/joypad/udev.cpp b/ruby/input/joypad/udev.cpp new file mode 100644 index 00000000..51126fb1 --- /dev/null +++ b/ruby/input/joypad/udev.cpp @@ -0,0 +1,254 @@ +namespace ruby { + +struct InputJoypadUdev { + udev* context = nullptr; + udev_monitor* monitor = nullptr; + udev_enumerate* enumerator = nullptr; + udev_list_entry* devices = nullptr; + udev_list_entry* item = nullptr; + + struct JoystickInput { + signed code = 0; + unsigned id = 0; + int16_t value = 0; + input_absinfo info; + + JoystickInput() {} + JoystickInput(signed code) : code(code) {} + JoystickInput(signed code, unsigned id) : code(code), id(id) {} + bool operator< (const JoystickInput& source) const { return code < source.code; } + bool operator==(const JoystickInput& source) const { return code == source.code; } + }; + + struct Joystick { + string path; + dev_t device = 0; + int fd = -1; + uint8_t evbit[(EV_MAX + 7) / 8] = {0}; + uint8_t keybit[(KEY_MAX + 7) / 8] = {0}; + uint8_t absbit[(ABS_MAX + 7) / 8] = {0}; + uint8_t ffbit[(FF_MAX + 7) / 8] = {0}; + unsigned effects = 0; + + string name; + string manufacturer; + string product; + string serial; + string vendorID; + string productID; + + set axes; + set hats; + set buttons; + bool rumble = false; + unsigned effectID = 0; + }; + vector joysticks; + + bool poll(int16_t* table) { + unsigned i = 0; + for(auto& js : joysticks) { + input_event events[32]; + signed length = 0; + while((length = read(js.fd, events, sizeof(events))) > 0) { + length /= sizeof(input_event); + for(unsigned i = 0; i < length; i++) { + signed code = events[i].code; + signed type = events[i].type; + signed value = events[i].value; + + if(type == EV_ABS) { + if(auto input = js.axes.find({code})) { + signed range = input().info.maximum - input().info.minimum; + signed axis = (value - input().info.minimum) * 65535ll / range - 32767; + if(axis > +32767) axis = +32767; + if(axis < -32768) axis = -32768; + input().value = axis; + } + if(auto input = js.hats.find({code})) { + input().value = value; + } + } + + if(type == EV_KEY) { + if(code >= BTN_MISC) { + if(auto input = js.buttons.find({code})) { + input().value = value; + } + } + } + } + } + + for(auto input : js.axes) { + table[joypad(i).axis(input.id)] = input.value; + } + + for(unsigned id = 0; id < (js.hats.size() + 1) / 2; id++) { + table[joypad(i).hat(id)] = 0; + } + + for(auto input : js.hats) { + unsigned hat = 0; + if(input.code == ABS_HAT0X || input.code == ABS_HAT0Y) hat = 0; + if(input.code == ABS_HAT1X || input.code == ABS_HAT1Y) hat = 1; + if(input.code == ABS_HAT2X || input.code == ABS_HAT2Y) hat = 2; + if(input.code == ABS_HAT3X || input.code == ABS_HAT3Y) hat = 3; + + bool orientation = 0; + if(input.code == ABS_HAT0X || input.code == ABS_HAT1X || input.code == ABS_HAT2X || input.code == ABS_HAT3X) orientation = 0; + if(input.code == ABS_HAT0Y || input.code == ABS_HAT1Y || input.code == ABS_HAT2Y || input.code == ABS_HAT3Y) orientation = 1; + + signed value = 0; + if(orientation == 0) { + if(input.value < 0) value |= Joypad::HatLeft; + if(input.value > 0) value |= Joypad::HatRight; + } else { + if(input.value < 0) value |= Joypad::HatUp; + if(input.value > 0) value |= Joypad::HatDown; + } + + table[joypad(i).hat(hat)] |= value; + } + + for(auto input : js.buttons) { + table[joypad(i).button(input.id)] = input.value; + } + + i++; + } + + return true; + } + + void rumble(unsigned id, bool enable) { + if(id >= joysticks.size()) return; + + Joystick& js = joysticks[id]; + if(js.rumble == false) return; + + input_event play; + memset(&play, 0, sizeof(input_event)); + play.type = EV_FF; + play.code = js.effectID; + play.value = enable; + write(js.fd, &play, sizeof(input_event)); + } + + bool init() { + context = udev_new(); + if(context == nullptr) return false; + + monitor = udev_monitor_new_from_netlink(context, "udev"); + if(monitor) { + udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", nullptr); + udev_monitor_enable_receiving(monitor); + } + + enumerator = udev_enumerate_new(context); + if(enumerator) { + udev_enumerate_add_match_property(enumerator, "ID_INPUT_JOYSTICK", "1"); + udev_enumerate_scan_devices(enumerator); + devices = udev_enumerate_get_list_entry(enumerator); + for(udev_list_entry* item = devices; item != nullptr; item = udev_list_entry_get_next(item)) { + const char* name = udev_list_entry_get_name(item); + struct udev_device* device = udev_device_new_from_syspath(context, name); + const char* deviceNode = udev_device_get_devnode(device); + if(deviceNode) createJoystick(device, deviceNode); + udev_device_unref(device); + } + } + + return true; + } + + void term() { + if(enumerator) { udev_enumerate_unref(enumerator); enumerator = nullptr; } + } + +private: + void createJoystick(udev_device* device, const char* path) { + Joystick js; + js.path = path; + + struct stat st; + if(stat(path, &st) < 0) return; + js.device = st.st_rdev; + + js.fd = open(path, O_RDWR | O_NONBLOCK); + if(js.fd < 0) return; + + uint8_t evbit[(EV_MAX + 7) / 8] = {0}; + uint8_t keybit[(KEY_MAX + 7) / 8] = {0}; + uint8_t absbit[(ABS_MAX + 7) / 8] = {0}; + + ioctl(js.fd, EVIOCGBIT(0, sizeof(js.evbit)), js.evbit); + ioctl(js.fd, EVIOCGBIT(EV_KEY, sizeof(js.keybit)), js.keybit); + ioctl(js.fd, EVIOCGBIT(EV_ABS, sizeof(js.absbit)), js.absbit); + ioctl(js.fd, EVIOCGBIT(EV_FF, sizeof(js.ffbit)), js.ffbit); + ioctl(js.fd, EVIOCGEFFECTS, &js.effects); + + #define testBit(buffer, bit) (buffer[(bit) >> 3] & 1 << ((bit) & 7)) + + if(testBit(js.evbit, EV_KEY)) { + if(udev_device* parent = udev_device_get_parent_with_subsystem_devtype(device, "input", nullptr)) { + js.name = udev_device_get_sysattr_value(parent, "name"); + js.vendorID = udev_device_get_sysattr_value(parent, "id/vendor"); + js.productID = udev_device_get_sysattr_value(parent, "id/product"); + if(udev_device* root = udev_device_get_parent_with_subsystem_devtype(parent, "usb", "usb_device")) { + if(js.vendorID == udev_device_get_sysattr_value(root, "idVendor") + && js.productID == udev_device_get_sysattr_value(root, "idProduct") + ) { + js.manufacturer = udev_device_get_sysattr_value(root, "manufacturer"); + js.product = udev_device_get_sysattr_value(root, "product"); + js.serial = udev_device_get_sysattr_value(root, "serial"); + } + } + } + + unsigned axes = 0; + unsigned hats = 0; + unsigned buttons = 0; + for(signed i = 0; i < ABS_MISC; i++) { + if(testBit(js.absbit, i)) { + if(i >= ABS_HAT0X && i <= ABS_HAT3Y) { + if(auto hat = js.hats.insert({i, hats++})) { + ioctl(js.fd, EVIOCGABS(i), &hat().info); + } + } else { + if(auto axis = js.axes.insert({i, axes++})) { + ioctl(js.fd, EVIOCGABS(i), &axis().info); + } + } + } + } + for(signed i = BTN_JOYSTICK; i < KEY_MAX; i++) { + if(testBit(js.keybit, i)) { + js.buttons.insert({i, buttons++}); + } + } + for(signed i = BTN_MISC; i < BTN_JOYSTICK; i++) { + if(testBit(js.keybit, i)) { + js.buttons.insert({i, buttons++}); + } + } + js.rumble = js.effects >= 2 && testBit(js.ffbit, FF_RUMBLE); + if(js.rumble) { + ff_effect effect; + memset(&effect, 0, sizeof(ff_effect)); + effect.type = FF_RUMBLE; + effect.id = -1; + effect.u.rumble.strong_magnitude = 65535; + effect.u.rumble.weak_magnitude = 65535; + ioctl(js.fd, EVIOCSFF, &effect); + js.effectID = effect.id; + } + + joysticks.append(js); + } + + #undef testBit + } +}; + +} diff --git a/ruby/input/joypad/xinput.cpp b/ruby/input/joypad/xinput.cpp new file mode 100644 index 00000000..e9dbbe37 --- /dev/null +++ b/ruby/input/joypad/xinput.cpp @@ -0,0 +1,101 @@ +#include + +namespace ruby { + +struct InputJoypadXInput { + HMODULE libxinput = nullptr; + DWORD WINAPI (*XInputGetState)(DWORD, XINPUT_STATE*) = nullptr; + DWORD WINAPI (*XInputSetState)(DWORD, XINPUT_VIBRATION*) = nullptr; + + struct Joystick { + unsigned id; + + int16_t hat = 0; + int16_t axis[6] = {0}; + bool button[10] = {0}; + }; + vector joysticks; + + bool poll(int16_t* table) { + if(!XInputGetState) return false; + + for(auto& js : joysticks) { + XINPUT_STATE state; + if(XInputGetState(js.id, &state) != ERROR_SUCCESS) continue; + + int16_t hat = 0; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hat |= Joypad::HatUp; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hat |= Joypad::HatDown; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hat |= Joypad::HatLeft; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hat |= Joypad::HatRight; + + //scale trigger ranges from (0 to 255) to (-32768 to +32767) + uint16_t triggerL = state.Gamepad.bLeftTrigger; + uint16_t triggerR = state.Gamepad.bRightTrigger; + triggerL = triggerL << 8 | triggerL << 0; + triggerR = triggerR << 8 | triggerR << 0; + + table[joypad(js.id).axis(0)] = (int16_t)state.Gamepad.sThumbLX; + table[joypad(js.id).axis(1)] = (int16_t)state.Gamepad.sThumbLY; + table[joypad(js.id).axis(2)] = (int16_t)state.Gamepad.sThumbRX; + table[joypad(js.id).axis(3)] = (int16_t)state.Gamepad.sThumbRY; + table[joypad(js.id).axis(4)] = (int16_t)((~triggerL) - 32768); + table[joypad(js.id).axis(5)] = (int16_t)((~triggerR) - 32768); + table[joypad(js.id).hat(0)] = (int16_t)hat; + table[joypad(js.id).button(0)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A); + table[joypad(js.id).button(1)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B); + table[joypad(js.id).button(2)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X); + table[joypad(js.id).button(3)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); + table[joypad(js.id).button(4)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); + table[joypad(js.id).button(5)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START); + table[joypad(js.id).button(6)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); + table[joypad(js.id).button(7)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); + table[joypad(js.id).button(8)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); + table[joypad(js.id).button(9)] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); + } + + return true; + } + + void rumble(unsigned id, bool enable) { + if(!XInputSetState) return; + if(id >= joysticks.size()) return; + + XINPUT_VIBRATION vibration; + memset(&vibration, 0, sizeof(XINPUT_VIBRATION)); + vibration.wLeftMotorSpeed = enable ? 65535 : 0; //low-frequency motor (0 = off, 65535 = max) + vibration.wRightMotorSpeed = enable ? 65535 : 0; //high-frequency motor (0 = off, 65535 = max) + XInputSetState(joysticks(id).id, &vibration); + } + + bool init() { + if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll"); + if(!libxinput) libxinput = LoadLibraryA("xinput1_2.dll"); + if(!libxinput) libxinput = LoadLibraryA("xinput1_1.dll"); + if(!libxinput) return false; + + XInputGetState = (DWORD WINAPI (*)(DWORD, XINPUT_STATE*))GetProcAddress(libxinput, "XInputGetState"); + XInputSetState = (DWORD WINAPI (*)(DWORD, XINPUT_VIBRATION*))GetProcAddress(libxinput, "XInputSetState"); + + //XInput supports a maximum of four controllers + for(unsigned id = 0; id < 4; id++) { + XINPUT_STATE state; + if(XInputGetState(id, &state) == ERROR_SUCCESS) { + Joystick js; + js.id = id; + joysticks.append(js); + } + } + + return true; + } + + void term() { + if(libxinput) { + FreeLibrary(libxinput); + libxinput = nullptr; + } + } +}; + +} diff --git a/ruby/input/keyboard/xlib.cpp b/ruby/input/keyboard/xlib.cpp new file mode 100644 index 00000000..b5f3ef5d --- /dev/null +++ b/ruby/input/keyboard/xlib.cpp @@ -0,0 +1,277 @@ +namespace ruby { + +struct InputKeyboardXlib { + Display* display = nullptr; + uint8_t scancode[256] = {0}; + + enum XScancode : unsigned { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt, LeftSuper, RightSuper, + }; + + bool poll(int16_t* table) { + char state[32]; + XQueryKeymap(display, state); + + #define key(id) table[keyboard(0)[id]] + #define pressed(id) (bool)(state[scancode[id] >> 3] & (1 << (scancode[id] & 7))) + + key(Keyboard::Escape) = pressed(Escape); + + key(Keyboard::F1) = pressed(F1); + key(Keyboard::F2) = pressed(F2); + key(Keyboard::F3) = pressed(F3); + key(Keyboard::F4) = pressed(F4); + key(Keyboard::F5) = pressed(F5); + key(Keyboard::F6) = pressed(F6); + key(Keyboard::F7) = pressed(F7); + key(Keyboard::F8) = pressed(F8); + key(Keyboard::F9) = pressed(F9); + key(Keyboard::F10) = pressed(F10); + key(Keyboard::F11) = pressed(F11); + key(Keyboard::F12) = pressed(F12); + + key(Keyboard::ScrollLock) = pressed(ScrollLock); + key(Keyboard::Pause) = pressed(Pause); + key(Keyboard::Tilde) = pressed(Tilde); + + key(Keyboard::Num1) = pressed(Num1); + key(Keyboard::Num2) = pressed(Num2); + key(Keyboard::Num3) = pressed(Num3); + key(Keyboard::Num4) = pressed(Num4); + key(Keyboard::Num5) = pressed(Num5); + key(Keyboard::Num6) = pressed(Num6); + key(Keyboard::Num7) = pressed(Num7); + key(Keyboard::Num8) = pressed(Num8); + key(Keyboard::Num9) = pressed(Num9); + key(Keyboard::Num0) = pressed(Num0); + + key(Keyboard::Dash) = pressed(Dash); + key(Keyboard::Equal) = pressed(Equal); + key(Keyboard::Backspace) = pressed(Backspace); + + key(Keyboard::Insert) = pressed(Insert); + key(Keyboard::Delete) = pressed(Delete); + key(Keyboard::Home) = pressed(Home); + key(Keyboard::End) = pressed(End); + key(Keyboard::PageUp) = pressed(PageUp); + key(Keyboard::PageDown) = pressed(PageDown); + + key(Keyboard::A) = pressed(A); + key(Keyboard::B) = pressed(B); + key(Keyboard::C) = pressed(C); + key(Keyboard::D) = pressed(D); + key(Keyboard::E) = pressed(E); + key(Keyboard::F) = pressed(F); + key(Keyboard::G) = pressed(G); + key(Keyboard::H) = pressed(H); + key(Keyboard::I) = pressed(I); + key(Keyboard::J) = pressed(J); + key(Keyboard::K) = pressed(K); + key(Keyboard::L) = pressed(L); + key(Keyboard::M) = pressed(M); + key(Keyboard::N) = pressed(N); + key(Keyboard::O) = pressed(O); + key(Keyboard::P) = pressed(P); + key(Keyboard::Q) = pressed(Q); + key(Keyboard::R) = pressed(R); + key(Keyboard::S) = pressed(S); + key(Keyboard::T) = pressed(T); + key(Keyboard::U) = pressed(U); + key(Keyboard::V) = pressed(V); + key(Keyboard::W) = pressed(W); + key(Keyboard::X) = pressed(X); + key(Keyboard::Y) = pressed(Y); + key(Keyboard::Z) = pressed(Z); + + key(Keyboard::LeftBracket) = pressed(LeftBracket); + key(Keyboard::RightBracket) = pressed(RightBracket); + key(Keyboard::Backslash) = pressed(Backslash); + key(Keyboard::Semicolon) = pressed(Semicolon); + key(Keyboard::Apostrophe) = pressed(Apostrophe); + key(Keyboard::Comma) = pressed(Comma); + key(Keyboard::Period) = pressed(Period); + key(Keyboard::Slash) = pressed(Slash); + + key(Keyboard::Keypad1) = pressed(Keypad1); + key(Keyboard::Keypad2) = pressed(Keypad2); + key(Keyboard::Keypad3) = pressed(Keypad3); + key(Keyboard::Keypad4) = pressed(Keypad4); + key(Keyboard::Keypad5) = pressed(Keypad5); + key(Keyboard::Keypad6) = pressed(Keypad6); + key(Keyboard::Keypad7) = pressed(Keypad7); + key(Keyboard::Keypad8) = pressed(Keypad8); + key(Keyboard::Keypad9) = pressed(Keypad9); + key(Keyboard::Keypad0) = pressed(Keypad0); + + key(Keyboard::Point) = pressed(Point); + key(Keyboard::Enter) = pressed(Enter); + key(Keyboard::Add) = pressed(Add); + key(Keyboard::Subtract) = pressed(Subtract); + key(Keyboard::Multiply) = pressed(Multiply); + key(Keyboard::Divide) = pressed(Divide); + + key(Keyboard::Up) = pressed(Up); + key(Keyboard::Down) = pressed(Down); + key(Keyboard::Left) = pressed(Left); + key(Keyboard::Right) = pressed(Right); + + key(Keyboard::Tab) = pressed(Tab); + key(Keyboard::Return) = pressed(Return); + key(Keyboard::Spacebar) = pressed(Spacebar); + key(Keyboard::Menu) = pressed(Menu); + + key(Keyboard::Shift) = pressed(LeftShift) || pressed(RightShift); + key(Keyboard::Control) = pressed(LeftControl) || pressed(RightControl); + key(Keyboard::Alt) = pressed(LeftAlt) || pressed(RightAlt); + key(Keyboard::Super) = pressed(LeftSuper) || pressed(RightSuper); + + #undef key + #undef pressed + + return true; + } + + bool init() { + display = XOpenDisplay(0); + + #define assign(x, y) scancode[x] = XKeysymToKeycode(display, y) + assign(Escape, XK_Escape); + + assign(F1, XK_F1); + assign(F2, XK_F2); + assign(F3, XK_F3); + assign(F4, XK_F4); + assign(F5, XK_F5); + assign(F6, XK_F6); + assign(F7, XK_F7); + assign(F8, XK_F8); + assign(F9, XK_F9); + assign(F10, XK_F10); + assign(F11, XK_F11); + assign(F12, XK_F12); + + assign(ScrollLock, XK_Scroll_Lock); + assign(Pause, XK_Pause); + + assign(Tilde, XK_asciitilde); + + assign(Num0, XK_0); + assign(Num1, XK_1); + assign(Num2, XK_2); + assign(Num3, XK_3); + assign(Num4, XK_4); + assign(Num5, XK_5); + assign(Num6, XK_6); + assign(Num7, XK_7); + assign(Num8, XK_8); + assign(Num9, XK_9); + + assign(Dash, XK_minus); + assign(Equal, XK_equal); + assign(Backspace, XK_BackSpace); + + assign(Insert, XK_Insert); + assign(Delete, XK_Delete); + assign(Home, XK_Home); + assign(End, XK_End); + assign(PageUp, XK_Prior); + assign(PageDown, XK_Next); + + assign(A, XK_A); + assign(B, XK_B); + assign(C, XK_C); + assign(D, XK_D); + assign(E, XK_E); + assign(F, XK_F); + assign(G, XK_G); + assign(H, XK_H); + assign(I, XK_I); + assign(J, XK_J); + assign(K, XK_K); + assign(L, XK_L); + assign(M, XK_M); + assign(N, XK_N); + assign(O, XK_O); + assign(P, XK_P); + assign(Q, XK_Q); + assign(R, XK_R); + assign(S, XK_S); + assign(T, XK_T); + assign(U, XK_U); + assign(V, XK_V); + assign(W, XK_W); + assign(X, XK_X); + assign(Y, XK_Y); + assign(Z, XK_Z); + + assign(LeftBracket, XK_bracketleft); + assign(RightBracket, XK_bracketright); + assign(Backslash, XK_backslash); + assign(Semicolon, XK_semicolon); + assign(Apostrophe, XK_apostrophe); + assign(Comma, XK_comma); + assign(Period, XK_period); + assign(Slash, XK_slash); + + assign(Keypad0, XK_KP_0); + assign(Keypad1, XK_KP_1); + assign(Keypad2, XK_KP_2); + assign(Keypad3, XK_KP_3); + assign(Keypad4, XK_KP_4); + assign(Keypad5, XK_KP_5); + assign(Keypad6, XK_KP_6); + assign(Keypad7, XK_KP_7); + assign(Keypad8, XK_KP_8); + assign(Keypad9, XK_KP_9); + + assign(Add, XK_KP_Add); + assign(Subtract, XK_KP_Subtract); + assign(Multiply, XK_KP_Multiply); + assign(Divide, XK_KP_Divide); + assign(Enter, XK_KP_Enter); + + assign(Up, XK_Up); + assign(Down, XK_Down); + assign(Left, XK_Left); + assign(Right, XK_Right); + + assign(Tab, XK_Tab); + assign(Return, XK_Return); + assign(Spacebar, XK_space); + + assign(LeftControl, XK_Control_L); + assign(RightControl, XK_Control_R); + assign(LeftAlt, XK_Alt_L); + assign(RightAlt, XK_Alt_R); + assign(LeftShift, XK_Shift_L); + assign(RightShift, XK_Shift_R); + assign(LeftSuper, XK_Super_L); + assign(RightSuper, XK_Super_R); + assign(Menu, XK_Menu); + + #undef assign + + return true; + } + + void term() { + if(display) { + XCloseDisplay(display); + display = nullptr; + } + } +}; + +} diff --git a/ruby/input/mouse/xlib.cpp b/ruby/input/mouse/xlib.cpp new file mode 100644 index 00000000..52f77970 --- /dev/null +++ b/ruby/input/mouse/xlib.cpp @@ -0,0 +1,128 @@ +namespace ruby { + +struct InputMouseXlib { + uintptr_t handle = 0; + + Display* display = nullptr; + Window rootWindow; + Cursor invisibleCursor; + unsigned screenWidth = 0; + unsigned screenHeight = 0; + + struct Mouse { + bool acquired = false; + signed numerator = 0; + signed denominator = 0; + signed threshold = 0; + unsigned relativeX = 0; + unsigned relativeY = 0; + } ms; + + bool acquire() { + if(acquired()) return true; + + if(XGrabPointer(display, handle, True, 0, GrabModeAsync, GrabModeAsync, rootWindow, invisibleCursor, CurrentTime) == GrabSuccess) { + //backup existing cursor acceleration settings + XGetPointerControl(display, &ms.numerator, &ms.denominator, &ms.threshold); + + //disable cursor acceleration + XChangePointerControl(display, True, False, 1, 1, 0); + + //center cursor (so that first relative poll returns 0, 0 if mouse has not moved) + XWarpPointer(display, None, rootWindow, 0, 0, 0, 0, screenWidth / 2, screenHeight / 2); + + return ms.acquired = true; + } else { + return ms.acquired = false; + } + } + + bool unacquire() { + if(acquired()) { + //restore cursor acceleration and release cursor + XChangePointerControl(display, True, True, ms.numerator, ms.denominator, ms.threshold); + XUngrabPointer(display, CurrentTime); + ms.acquired = false; + } + return true; + } + + bool acquired() { + return ms.acquired; + } + + bool poll(int16_t* table) { + Window rootReturn; + Window childReturn; + signed rootXReturn = 0; + signed rootYReturn = 0; + signed windowXReturn = 0; + signed windowYReturn = 0; + unsigned maskReturn = 0; + XQueryPointer(display, handle, &rootReturn, &childReturn, &rootXReturn, &rootYReturn, &windowXReturn, &windowYReturn, &maskReturn); + + if(acquired()) { + XWindowAttributes attributes; + XGetWindowAttributes(display, handle, &attributes); + + //absolute -> relative conversion + table[mouse(0).axis(0)] = (int16_t)(rootXReturn - screenWidth / 2); + table[mouse(0).axis(1)] = (int16_t)(rootYReturn - screenHeight / 2); + + if(table[mouse(0).axis(0)] != 0 || table[mouse(0).axis(1)] != 0) { + //if mouse moved, re-center mouse for next poll + XWarpPointer(display, None, rootWindow, 0, 0, 0, 0, screenWidth / 2, screenHeight / 2); + } + } else { + table[mouse(0).axis(0)] = (int16_t)(rootXReturn - ms.relativeX); + table[mouse(0).axis(1)] = (int16_t)(rootYReturn - ms.relativeY); + + ms.relativeX = rootXReturn; + ms.relativeY = rootYReturn; + } + + table[mouse(0).button(0)] = (bool)(maskReturn & Button1Mask); + table[mouse(0).button(1)] = (bool)(maskReturn & Button2Mask); + table[mouse(0).button(2)] = (bool)(maskReturn & Button3Mask); + table[mouse(0).button(3)] = (bool)(maskReturn & Button4Mask); + table[mouse(0).button(4)] = (bool)(maskReturn & Button5Mask); + + return true; + } + + bool init(uintptr_t handle) { + this->handle = handle; + display = XOpenDisplay(0); + rootWindow = DefaultRootWindow(display); + + XWindowAttributes attributes; + XGetWindowAttributes(display, rootWindow, &attributes); + screenWidth = attributes.width; + screenHeight = attributes.height; + + //create invisible cursor for use when mouse is acquired + Pixmap pixmap; + XColor black, unused; + static char invisibleData[8] = {0}; + Colormap colormap = DefaultColormap(display, DefaultScreen(display)); + XAllocNamedColor(display, colormap, "black", &black, &unused); + pixmap = XCreateBitmapFromData(display, handle, invisibleData, 8, 8); + invisibleCursor = XCreatePixmapCursor(display, pixmap, pixmap, &black, &black, 0, 0); + XFreePixmap(display, pixmap); + XFreeColors(display, colormap, &black.pixel, 1, 0); + + ms.acquired = false; + ms.relativeX = 0; + ms.relativeY = 0; + + return true; + } + + void term() { + unacquire(); + XFreeCursor(display, invisibleCursor); + XCloseDisplay(display); + } +}; + +} diff --git a/ruby/input/rawinput.cpp b/ruby/input/rawinput.cpp index d1551952..518431f7 100644 --- a/ruby/input/rawinput.cpp +++ b/ruby/input/rawinput.cpp @@ -20,15 +20,15 @@ #define DIRECTINPUT_VERSION 0x0800 #include -#include + +#include "joypad/xinput.cpp" namespace ruby { static DWORD WINAPI RawInputThreadProc(void*); static LRESULT CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM); -class RawInput { -public: +struct RawInput { HANDLE mutex; HWND hwnd; bool initialized; @@ -393,107 +393,11 @@ LRESULT CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l return rawinput.window_proc(hwnd, msg, wparam, lparam); } -class XInput { -public: - HMODULE libxinput; - DWORD WINAPI (*pXInputGetState)(DWORD, XINPUT_STATE*); - - struct Gamepad { - unsigned id; - - int16_t hat; - int16_t axis[6]; - bool button[10]; - - void poll(XINPUT_STATE &state) { - hat = Joypad::HatCenter; - if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hat |= Joypad::HatUp; - if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hat |= Joypad::HatRight; - if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hat |= Joypad::HatDown; - if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hat |= Joypad::HatLeft; - - axis[0] = (int16_t)state.Gamepad.sThumbLX; - axis[1] = (int16_t)state.Gamepad.sThumbLY; - axis[2] = (int16_t)state.Gamepad.sThumbRX; - axis[3] = (int16_t)state.Gamepad.sThumbRY; - - //transform left and right trigger ranges: - //from: 0 (low, eg released) to 255 (high, eg pressed all the way down) - //to: +32767 (low) to -32768 (high) - uint16_t triggerX = state.Gamepad.bLeftTrigger; - uint16_t triggerY = state.Gamepad.bRightTrigger; - - triggerX = (triggerX << 8) | triggerX; - triggerY = (triggerY << 8) | triggerY; - - axis[4] = (~triggerX) - 32768; - axis[5] = (~triggerY) - 32768; - - button[0] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A); - button[1] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B); - button[2] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X); - button[3] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); - button[4] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); - button[5] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START); - button[6] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); - button[7] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); - button[8] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); - button[9] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); - } - - Gamepad() { - hat = Joypad::HatCenter; - for(unsigned n = 0; n < 6; n++) axis[n] = 0; - for(unsigned n = 0; n < 10; n++) button[n] = false; - } - }; - - vector lgamepad; - - void poll() { - if(!pXInputGetState) return; - - for(unsigned i = 0; i < lgamepad.size(); i++) { - XINPUT_STATE state; - DWORD result = pXInputGetState(lgamepad(i).id, &state); - if(result == ERROR_SUCCESS) lgamepad(i).poll(state); - } - } - - void init() { - if(!pXInputGetState) return; - - //XInput only supports up to four controllers - for(unsigned i = 0; i <= 3; i++) { - XINPUT_STATE state; - DWORD result = pXInputGetState(i, &state); - if(result == ERROR_SUCCESS) { - //valid controller detected, add to gamepad list - unsigned n = lgamepad.size(); - lgamepad(n).id = i; - } - } - } - - XInput() : pXInputGetState(0) { - //bind xinput1 dynamically, as it does not ship with Windows Vista or below - libxinput = LoadLibraryA("xinput1_3.dll"); - if(!libxinput) libxinput = LoadLibraryA("xinput1_2.dll"); - if(!libxinput) libxinput = LoadLibraryA("xinput1_1.dll"); - if(!libxinput) return; - pXInputGetState = (DWORD WINAPI (*)(DWORD, XINPUT_STATE*))GetProcAddress(libxinput, "XInputGetState"); - } - - ~XInput() { - if(libxinput) FreeLibrary(libxinput); - } -}; - static BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); static BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*); +static BOOL CALLBACK DirectInput_EnumJoypadFeedbacksCallback(const DIDEVICEOBJECTINSTANCE*, void*); -class DirectInput { -public: +struct DirectInput { HWND handle; LPDIRECTINPUT8 context; struct Gamepad { @@ -502,6 +406,7 @@ public: int16_t hat[4]; int16_t axis[6]; bool button[128]; + LPDIRECTINPUTEFFECT effect = nullptr; void poll(DIJOYSTATE2& state) { //POV hats @@ -557,7 +462,17 @@ public: } } - bool init_joypad(const DIDEVICEINSTANCE* instance) { + void rumble(unsigned id, bool enable) { + if(lgamepad(id).effect == nullptr) return; + + if(enable) { + lgamepad(id).effect->Start(1, 0); + } else { + lgamepad(id).effect->Stop(); + } + } + + bool initJoypad(const DIDEVICEINSTANCE* instance) { //if this is an XInput device, do not acquire it via DirectInput ... //the XInput driver above will handle said device. for(unsigned i = 0; i < rawinput.lgamepad.size(); i++) { @@ -573,15 +488,53 @@ public: return DIENUM_CONTINUE; } - device->SetDataFormat(&c_dfDIJoystick2); - device->SetCooperativeLevel(handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); - device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); unsigned n = lgamepad.size(); lgamepad(n).handle = device; + + device->SetDataFormat(&c_dfDIJoystick2); + device->SetCooperativeLevel(handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + + forceFeedbackAxes = 0; + device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); + device->EnumObjects(DirectInput_EnumJoypadFeedbacksCallback, (void*)this, DIDFT_FFACTUATOR); + if(forceFeedbackAxes == 0) return DIENUM_CONTINUE; + + //disable auto-centering spring for rumble support + DIPROPDWORD property; + memset(&property, 0, sizeof(DIPROPDWORD)); + property.diph.dwSize = sizeof(DIPROPDWORD); + property.diph.dwHeaderSize = sizeof(DIPROPHEADER); + property.diph.dwObj = 0; + property.diph.dwHow = DIPH_DEVICE; + property.dwData = false; + device->SetProperty(DIPROP_AUTOCENTER, &property.diph); + + DWORD dwAxes[2] = {DIJOFS_X, DIJOFS_Y}; + LONG lDirection[2] = {0, 0}; + DICONSTANTFORCE force; + force.lMagnitude = DI_FFNOMINALMAX; //full force + DIEFFECT effect; + memset(&effect, 0, sizeof(DIEFFECT)); + effect.dwSize = sizeof(DIEFFECT); + effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + effect.dwDuration = INFINITE; + effect.dwSamplePeriod = 0; + effect.dwGain = DI_FFNOMINALMAX; + effect.dwTriggerButton = DIEB_NOTRIGGER; + effect.dwTriggerRepeatInterval = 0; + effect.cAxes = 2; + effect.rgdwAxes = dwAxes; + effect.rglDirection = lDirection; + effect.lpEnvelope = 0; + effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); + effect.lpvTypeSpecificParams = &force; + effect.dwStartDelay = 0; + device->CreateEffect(GUID_ConstantForce, &effect, &lgamepad(n).effect, NULL); + return DIENUM_CONTINUE; } - bool init_axis(const DIDEVICEOBJECTINSTANCE* instance) { + bool initAxis(const DIDEVICEOBJECTINSTANCE* instance) { DIPROPRANGE range; range.diph.dwSize = sizeof(DIPROPRANGE); range.diph.dwHeaderSize = sizeof(DIPROPHEADER); @@ -593,6 +546,11 @@ public: return DIENUM_CONTINUE; } + bool initFeedback(const DIDEVICEOBJECTINSTANCE* instance) { + forceFeedbackAxes++; + return DIENUM_CONTINUE; + } + void init(HWND handle_) { handle = handle_; DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&context, 0); @@ -602,37 +560,42 @@ public: void term() { for(unsigned i = 0; i < lgamepad.size(); i++) { lgamepad(i).handle->Unacquire(); + if(lgamepad(i).effect) lgamepad(i).effect->Release(); lgamepad(i).handle->Release(); } lgamepad.reset(); if(context) { context->Release(); - context = 0; + context = nullptr; } } private: LPDIRECTINPUTDEVICE8 device; + unsigned forceFeedbackAxes; }; BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) { - return ((DirectInput*)p)->init_joypad(instance); + return ((DirectInput*)p)->initJoypad(instance); } BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { - return ((DirectInput*)p)->init_axis(instance); + return ((DirectInput*)p)->initAxis(instance); } -class pInputRaw { -public: - XInput xinput; +BOOL CALLBACK DirectInput_EnumJoypadFeedbacksCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { + return ((DirectInput*)p)->initFeedback(instance); +} + +struct pInputRaw { + InputJoypadXInput xinput; DirectInput dinput; bool acquire_mouse; bool cursor_visible; - struct { + struct Settings { HWND handle; } settings; @@ -641,6 +604,7 @@ public: if(name == Input::KeyboardSupport) return true; if(name == Input::MouseSupport) return true; if(name == Input::JoypadSupport) return true; + if(name == Input::JoypadRumbleSupport) return true; return false; } @@ -720,31 +684,15 @@ public: ReleaseMutex(rawinput.mutex); - unsigned joy = 0; - //================== //XInput controllers //================== - xinput.poll(); - for(unsigned i = 0; i < xinput.lgamepad.size(); i++) { - if(joy >= Joypad::Count) break; - - table[joypad(joy).hat(0)] = xinput.lgamepad(i).hat; - - for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) { - table[joypad(joy).axis(axis)] = xinput.lgamepad(i).axis[axis]; - } - - for(unsigned button = 0; button < min(10U, (unsigned)Joypad::Buttons); button++) { - table[joypad(joy).button(button)] = xinput.lgamepad(i).button[button]; - } - - joy++; - } + xinput.poll(table); //======================= //DirectInput controllers //======================= + unsigned joy = xinput.joysticks.size(); dinput.poll(); for(unsigned i = 0; i < dinput.lgamepad.size(); i++) { if(joy >= Joypad::Count) break; @@ -767,6 +715,15 @@ public: return true; } + void rumble(unsigned id, bool enable) { + //id is the nall joypad# to rumble; but we have two lists of joypads + //XInput joypads are enumerated first, and then DirectInput joypads + if(id < xinput.joysticks.size()) return xinput.rumble(id, enable); + id -= xinput.joysticks.size(); + if(id < dinput.lgamepad.size()) return dinput.rumble(id, enable); + id -= dinput.lgamepad.size(); + } + bool init() { //only spawn RawInput processing thread one time if(rawinput.initialized == false) { @@ -795,6 +752,7 @@ public: void term() { unacquire(); + xinput.term(); dinput.term(); } diff --git a/ruby/input/sdl.cpp b/ruby/input/sdl.cpp index 738eef04..05fbaa8e 100644 --- a/ruby/input/sdl.cpp +++ b/ruby/input/sdl.cpp @@ -171,6 +171,9 @@ struct pInputSDL { return true; } + void rumble(unsigned id, bool enable) { + } + bool init() { SDL_InitSubSystem(SDL_INIT_JOYSTICK); SDL_JoystickEventState(SDL_IGNORE); @@ -227,4 +230,4 @@ struct pInputSDL { DeclareInput(SDL) -}; +} diff --git a/ruby/input/udev.cpp b/ruby/input/udev.cpp new file mode 100644 index 00000000..c3fea635 --- /dev/null +++ b/ruby/input/udev.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keyboard/xlib.cpp" +#include "mouse/xlib.cpp" +#include "joypad/udev.cpp" + +namespace ruby { + +struct pInputUdev { + InputKeyboardXlib xlibKeyboard; + InputMouseXlib xlibMouse; + InputJoypadUdev udev; + + struct Settings { + uintptr_t handle = 0; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + if(name == Input::JoypadRumbleSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + return false; + } + + bool acquire() { + return xlibMouse.acquire(); + } + + bool unacquire() { + return xlibMouse.unacquire(); + } + + bool acquired() { + return xlibMouse.acquired(); + } + + bool poll(int16_t* table) { + xlibKeyboard.poll(table); + xlibMouse.poll(table); + udev.poll(table); + } + + void rumble(unsigned id, bool enable) { + udev.rumble(id, enable); + } + + bool init() { + if(xlibKeyboard.init() == false) return false; + if(xlibMouse.init(settings.handle) == false) return false; + if(udev.init() == false) return false; + return true; + } + + void term() { + xlibKeyboard.term(); + xlibMouse.term(); + udev.term(); + } +}; + +DeclareInput(Udev) + +} diff --git a/ruby/input/x.cpp b/ruby/input/x.cpp index beadf704..be85e075 100644 --- a/ruby/input/x.cpp +++ b/ruby/input/x.cpp @@ -34,6 +34,9 @@ public: return true; } + void rumble(unsigned id, bool enable) { + } + bool init() { display = XOpenDisplay(0); x_init(display); @@ -47,4 +50,4 @@ public: DeclareInput(X) -}; +} diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp index 2d98dacd..b6f34657 100644 --- a/ruby/ruby.cpp +++ b/ruby/ruby.cpp @@ -366,6 +366,7 @@ const char* Input::Handle = "Handle"; const char* Input::KeyboardSupport = "KeyboardSupport"; const char* Input::MouseSupport = "MouseSupport"; const char* Input::JoypadSupport = "JoypadSupport"; +const char* Input::JoypadRumbleSupport = "JoypadRumbleSupport"; void InputInterface::driver(const char* driver) { if(p) term(); @@ -386,6 +387,10 @@ void InputInterface::driver(const char* driver) { else if(!strcmp(driver, "Carbon")) p = new InputCarbon(); #endif + #ifdef INPUT_UDEV + else if(!strcmp(driver, "udev")) p = new InputUdev(); + #endif + #ifdef INPUT_SDL else if(!strcmp(driver, "SDL")) p = new InputSDL(); #endif @@ -406,6 +411,8 @@ const char* InputInterface::optimalDriver() { #elif defined(INPUT_CARBON) return "Carbon"; + #elif defined(INPUT_UDEV) + return "udev"; #elif defined(INPUT_SDL) return "SDL"; #elif defined(INPUT_X) @@ -425,6 +432,8 @@ const char* InputInterface::safestDriver() { #elif defined(INPUT_CARBON) return "Carbon"; + #elif defined(INPUT_UDEV) + return "udev"; #elif defined(INPUT_SDL) return "SDL"; #elif defined(INPUT_X) @@ -456,6 +465,10 @@ const char* InputInterface::availableDrivers() { //Linux + #if defined(INPUT_UDEV) + "udev;" + #endif + #if defined(INPUT_SDL) "SDL;" #endif @@ -487,6 +500,7 @@ bool InputInterface::acquire() { return p ? p->acquire() : false; } bool InputInterface::unacquire() { return p ? p->unacquire() : false; } bool InputInterface::acquired() { return p ? p->acquired() : false; } bool InputInterface::poll(int16_t* table) { return p ? p->poll(table) : false; } +void InputInterface::rumble(unsigned id, bool enable) { if(p) return p->rumble(id, enable); } InputInterface::InputInterface() : p(nullptr) {} InputInterface::~InputInterface() { term(); } diff --git a/ruby/ruby.hpp b/ruby/ruby.hpp index d50adf94..8bcc495e 100644 --- a/ruby/ruby.hpp +++ b/ruby/ruby.hpp @@ -1,6 +1,6 @@ /* ruby - version: 0.10 (2013-07-27) + version: 0.11 (2013-12-19) license: public domain */ @@ -32,6 +32,7 @@ struct VideoInterface { void unlock(); void clear(); void refresh(); + VideoInterface(); ~VideoInterface(); @@ -53,6 +54,7 @@ struct AudioInterface { void sample(uint16_t left, uint16_t right); void clear(); + AudioInterface(); ~AudioInterface(); @@ -77,6 +79,8 @@ struct InputInterface { bool acquired(); bool poll(int16_t* table); + void rumble(unsigned id, bool enable); + InputInterface(); ~InputInterface(); diff --git a/sfc/chip/icd2/icd2.cpp b/sfc/chip/icd2/icd2.cpp index 4bb619e5..02ba74ad 100644 --- a/sfc/chip/icd2/icd2.cpp +++ b/sfc/chip/icd2/icd2.cpp @@ -73,7 +73,7 @@ void ICD2::reset() { joyp14lock = 0; pulselock = true; - GameBoy::video.generate_palette(Emulator::Interface::PaletteMode::None); + GameBoy::video.generate_palette(Emulator::Interface::PaletteMode::Literal); GameBoy::system.init(); GameBoy::system.power(); } diff --git a/sfc/system/video.cpp b/sfc/system/video.cpp index f086c1ad..c59fc74f 100644 --- a/sfc/system/video.cpp +++ b/sfc/system/video.cpp @@ -4,7 +4,7 @@ Video video; void Video::generate_palette(Emulator::Interface::PaletteMode mode) { for(unsigned color = 0; color < (1 << 19); color++) { - if(mode == Emulator::Interface::PaletteMode::None) { + if(mode == Emulator::Interface::PaletteMode::Literal) { palette[color] = color; continue; } @@ -14,23 +14,32 @@ void Video::generate_palette(Emulator::Interface::PaletteMode mode) { unsigned g = (color >> 5) & 31; unsigned r = (color >> 0) & 31; + if(mode == Emulator::Interface::PaletteMode::Channel) { + l = image::normalize(l, 4, 16); + r = image::normalize(r, 5, 16); + g = image::normalize(g, 5, 16); + b = image::normalize(b, 5, 16); + palette[color] = interface->videoColor(color, l, r, g, b); + continue; + } + if(mode == Emulator::Interface::PaletteMode::Emulation) { r = gamma_ramp[r]; g = gamma_ramp[g]; b = gamma_ramp[b]; } else { - r = (r << 3) | (r >> 2); - g = (g << 3) | (g >> 2); - b = (b << 3) | (b >> 2); + r = image::normalize(r, 5, 8); + g = image::normalize(g, 5, 8); + b = image::normalize(b, 5, 8); } double L = (1.0 + l) / 16.0; if(l == 0) L *= 0.5; - unsigned R = L * (r << 8 | r << 0); - unsigned G = L * (g << 8 | g << 0); - unsigned B = L * (b << 8 | b << 0); + unsigned R = L * image::normalize(r, 8, 16); + unsigned G = L * image::normalize(g, 8, 16); + unsigned B = L * image::normalize(b, 8, 16); - palette[color] = interface->videoColor(color, R, G, B); + palette[color] = interface->videoColor(color, 0, R, G, B); } } diff --git a/target-ethos/Makefile b/target-ethos/Makefile index 367320c3..2624ab06 100644 --- a/target-ethos/Makefile +++ b/target-ethos/Makefile @@ -23,9 +23,9 @@ else ifeq ($(platform),macosx) ruby += audio.openal ruby += input.carbon else ifeq ($(platform),linux) - ruby := video.glx video.xv video.sdl + ruby := video.glx video.xv video.xshm video.sdl ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao - ruby += input.sdl input.x + ruby += input.udev input.sdl input.x else ifeq ($(platform),bsd) ruby := video.glx ruby += audio.openal audio.oss diff --git a/target-ethos/input/input.cpp b/target-ethos/input/input.cpp index 4c661d67..d7b22838 100644 --- a/target-ethos/input/input.cpp +++ b/target-ethos/input/input.cpp @@ -15,15 +15,20 @@ void AbstractInput::bind() { else if(mapping.endsWith(".Lo")) type = Input::Type::AxisLo; else if(mapping.endsWith(".Hi")) type = Input::Type::AxisHi; else if(mapping.beginsWith("JP") && mapping.find("Axis")) type = Input::Type::Axis; + else if(mapping.beginsWith("JP") && mapping.endsWith("Rumble")) type = Input::Type::Rumble; else if(mapping.beginsWith("MS") && mapping.endsWith("axis")) type = Input::Type::MouseAxis; else if(mapping.beginsWith("MS")) type = Input::Type::MouseButton; else type = Input::Type::Button; - string decode = mapping; - if(auto position = decode.find(".")) decode.resize(position()); - unsigned scancode = Scancode::decode(decode); - - inputList.append({type, scancode}); + if(type == Input::Type::Rumble) { + unsigned joypad = mapping[2] - '0'; + inputList.append({type, joypad}); + } else { + string decode = mapping; + if(auto position = decode.find(".")) decode.resize(position()); + unsigned scancode = Scancode::decode(decode); + inputList.append({type, scancode}); + } } } @@ -208,6 +213,39 @@ int16_t AbsoluteInput::poll() { return result; } +bool RumbleInput::bind(unsigned scancode, int16_t value) { + using nall::Keyboard; + + if(scancode == Scancode::None || scancode == keyboard(0)[Keyboard::Escape]) { + inputList.reset(); + mapping = "None"; + return true; + } + + string encode = Scancode::encode(scancode); + + if(Joypad::isAnyButton(scancode)) { + if(value == 0) return false; + if(auto position = encode.find("::")) encode.resize(position()); + encode.append("::Rumble"); + return append(encode); + } + + return false; +} + +int16_t RumbleInput::poll() { + return false; +} + +void RumbleInput::rumble(bool enable) { + if(program->focused() == false) return; + + for(auto& item : inputList) { + input.rumble(item.scancode, enable); + } +} + // HotkeyInput::HotkeyInput() { @@ -279,7 +317,8 @@ void InputManager::bootstrap() { if(input.type == 0) abstract = new DigitalInput; if(input.type == 1) abstract = new RelativeInput; if(input.type == 2) abstract = new AbsoluteInput; - if(input.type >= 3) continue; + if(input.type == 3) abstract = new RumbleInput; + if(abstract == nullptr) continue; abstract->name = string{input.name}.replace(" ", ""); abstract->mapping = "None"; diff --git a/target-ethos/input/input.hpp b/target-ethos/input/input.hpp index 3a72ae63..2d11f03c 100644 --- a/target-ethos/input/input.hpp +++ b/target-ethos/input/input.hpp @@ -5,7 +5,7 @@ struct AbstractInput { bool state; struct Input { - enum class Type : unsigned { Button, MouseButton, MouseAxis, HatUp, HatDown, HatLeft, HatRight, Axis, AxisLo, AxisHi } type; + enum class Type : unsigned { Button, MouseButton, MouseAxis, HatUp, HatDown, HatLeft, HatRight, Axis, AxisLo, AxisHi, Rumble } type; unsigned scancode; }; vector inputList; @@ -14,6 +14,7 @@ struct AbstractInput { bool append(string mapping); virtual bool bind(unsigned scancode, int16_t value) = 0; virtual int16_t poll() = 0; + virtual void rumble(bool enable) {} AbstractInput(); }; @@ -35,6 +36,13 @@ struct AbsoluteInput : AbstractInput { int16_t poll(); }; +struct RumbleInput : AbstractInput { + using AbstractInput::bind; + bool bind(unsigned scancode, int16_t value); + int16_t poll(); + void rumble(bool enable); +}; + struct HotkeyInput : DigitalInput { function press; function release; diff --git a/target-ethos/interface/interface.cpp b/target-ethos/interface/interface.cpp index cd74947f..703f283e 100644 --- a/target-ethos/interface/interface.cpp +++ b/target-ethos/interface/interface.cpp @@ -13,39 +13,41 @@ void Interface::saveRequest(unsigned id, string path) { return utility->saveRequest(id, path); } -uint32_t Interface::videoColor(unsigned source, uint16_t r, uint16_t g, uint16_t b) { - if(config->video.saturation != 100) { - uint16_t grayscale = uclamp<16>((r + g + b) / 3); - double saturation = config->video.saturation * 0.01; - double inverse = max(0.0, 1.0 - saturation); - r = uclamp<16>(r * saturation + grayscale * inverse); - g = uclamp<16>(g * saturation + grayscale * inverse); - b = uclamp<16>(b * saturation + grayscale * inverse); - } +uint32_t Interface::videoColor(unsigned source, uint16_t a, uint16_t r, uint16_t g, uint16_t b) { + if(config->video.shader != "Display Emulation") { + if(config->video.saturation != 100) { + uint16_t grayscale = uclamp<16>((r + g + b) / 3); + double saturation = config->video.saturation * 0.01; + double inverse = max(0.0, 1.0 - saturation); + r = uclamp<16>(r * saturation + grayscale * inverse); + g = uclamp<16>(g * saturation + grayscale * inverse); + b = uclamp<16>(b * saturation + grayscale * inverse); + } - if(config->video.gamma != 100) { - double exponent = config->video.gamma * 0.01; - double reciprocal = 1.0 / 32767.0; - r = r > 32767 ? r : 32767 * pow(r * reciprocal, exponent); - g = g > 32767 ? g : 32767 * pow(g * reciprocal, exponent); - b = b > 32767 ? b : 32767 * pow(b * reciprocal, exponent); - } + if(config->video.gamma != 100) { + double exponent = config->video.gamma * 0.01; + double reciprocal = 1.0 / 32767.0; + r = r > 32767 ? r : 32767 * pow(r * reciprocal, exponent); + g = g > 32767 ? g : 32767 * pow(g * reciprocal, exponent); + b = b > 32767 ? b : 32767 * pow(b * reciprocal, exponent); + } - if(config->video.luminance != 100) { - double luminance = config->video.luminance * 0.01; - r = r * luminance; - g = g * luminance; - b = b * luminance; + if(config->video.luminance != 100) { + double luminance = config->video.luminance * 0.01; + r = r * luminance; + g = g * luminance; + b = b * luminance; + } } if(program->depth == 30) { - r >>= 6, g >>= 6, b >>= 6; - return r << 20 | g << 10 | b << 0; + a >>= 14, r >>= 6, g >>= 6, b >>= 6; + return a << 30 | r << 20 | g << 10 | b << 0; } if(program->depth == 24) { - r >>= 8, g >>= 8, b >>= 8; - return r << 16 | g << 8 | b << 0; + a >>= 8, r >>= 8, g >>= 8, b >>= 8; + return a << 24 | r << 16 | g << 8 | b << 0; } return 0u; @@ -111,6 +113,11 @@ int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) { return inputManager->inputMap[guid]->poll(); } +void Interface::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { + unsigned guid = system().port[port].device[device].input[input].guid; + return inputManager->inputMap[guid]->rumble(enable); +} + unsigned Interface::dipSettings(const Markup::Node& node) { return dipSwitches->run(node); } diff --git a/target-ethos/interface/interface.hpp b/target-ethos/interface/interface.hpp index b7083229..485c831f 100644 --- a/target-ethos/interface/interface.hpp +++ b/target-ethos/interface/interface.hpp @@ -2,10 +2,11 @@ struct Interface : Emulator::Interface::Bind { void loadRequest(unsigned id, string name, string type); void loadRequest(unsigned id, string path); void saveRequest(unsigned id, string path); - uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue); + uint32_t videoColor(unsigned source, uint16_t alpha, uint16_t red, uint16_t green, uint16_t blue); void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height); void audioSample(int16_t lsample, int16_t rsample); int16_t inputPoll(unsigned port, unsigned device, unsigned input); + void inputRumble(unsigned port, unsigned device, unsigned input, bool enable); unsigned dipSettings(const Markup::Node& node); string path(unsigned group); string server(); diff --git a/target-ethos/utility/utility.cpp b/target-ethos/utility/utility.cpp index dd702078..a848f5db 100644 --- a/target-ethos/utility/utility.cpp +++ b/target-ethos/utility/utility.cpp @@ -182,8 +182,8 @@ void Utility::synchronizeRuby() { void Utility::updatePalette() { if(program->active == nullptr) return; - if(config->video.shader == "Display Emulation") { - system().paletteUpdate(Emulator::Interface::PaletteMode::None); + if(config->video.shader == "Display Emulation" && config->video.driver == "OpenGL") { + system().paletteUpdate(Emulator::Interface::PaletteMode::Channel); } else if(config->video.colorEmulation) { system().paletteUpdate(Emulator::Interface::PaletteMode::Emulation); } else { @@ -198,6 +198,9 @@ void Utility::updateShader() { } else if(config->video.shader == "Blur") { video.set(Video::Shader, (const char*)""); video.set(Video::Filter, Video::FilterLinear); + } else if(config->video.shader == "Display Emulation" && config->video.driver != "OpenGL") { + video.set(Video::Shader, (const char*)""); + video.set(Video::Filter, Video::FilterLinear); } else if(config->video.shader == "Display Emulation") { if(program->active) { string pathname = program->path("Video Shaders/");