diff --git a/autobuild.xml b/autobuild.xml index 9284d39d7b2..9e8a386463d 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1333,11 +1333,11 @@ archive hash - c9928095cf40377e901458f5240305a7af2c554d + 54bea6ca65c57eda548fb7f57a7eeb17e2cadf05 hash_algorithm sha1 url - https://github.com/secondlife/lsl-definitions/releases/download/v0.6.12/lsl_definitions-0.6.12-common-29281156609.tar.zst + https://github.com/secondlife/lsl-definitions/releases/download/v0.6.13.2/lsl_definitions-0.6.13-common-32865298211.tar.zst name common @@ -1348,9 +1348,9 @@ license_file LICENSES/lsl_definitions.txt copyright - Copyright (c) 2026, Linden Research, Inc. + Copyright (c) 2026, Linden Lab version - 0.6.12 + 0.6.13 use_scm_version true name @@ -2248,11 +2248,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - d1584b3a0011dbb741bc64d32e8bd28845ddd4da + 64581484d87b5180465ab3957b5b98acddf39852 hash_algorithm sha1 url - https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r2/viewer_fonts-1.0.0.25128287087-common-25128287087.tar.zst + https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.1-pre-r1/viewer_fonts-1.0.0.32996553300-common-32996553300.tar.zst name common @@ -2263,9 +2263,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors license_file LICENSES/fonts.txt copyright - Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT, Copyright (c) 2003 by Bitstream, Inc., Copyright (c) 2006 by Tavmjong Bah. + Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT, Copyright (c) 2003 by Bitstream, Inc., Copyright (c) 2006 by Tavmjong Bah, Copyright Yukari Hafner (Shinmera) SIL-OFL-1.1. version - 1.0.0.25128287087 + 1.0.0.32996553300 name viewer-fonts description diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index def3feaa712..ec4d4141cb3 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -34,6 +34,7 @@ if(WINDOWS) set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") set(release_files openjp2.dll + SDL3.dll ) # Filenames are different for 32/64 bit BugSplat file and we don't @@ -146,6 +147,7 @@ elseif(DARWIN) set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") set(release_files libndofdev.dylib + libSDL3.dylib ) if (TARGET ll::discord_sdk) diff --git a/indra/cmake/SDL3.cmake b/indra/cmake/SDL3.cmake index fe9012de171..0a2b54e1ad9 100644 --- a/indra/cmake/SDL3.cmake +++ b/indra/cmake/SDL3.cmake @@ -11,16 +11,11 @@ else() set(USE_SDL_WINDOW OFF CACHE BOOL "Build with SDL window backend") endif (LINUX) -if(USE_SDL_WINDOW) - use_system_binary(SDL3) - use_prebuilt_binary(SDL3) +use_prebuilt_binary(SDL3) - find_library( SDL3_LIBRARY - NAMES SDL3 SDL3.lib libSDL3.so libSDL3.dylib - PATHS "${LIBS_PREBUILT_DIR}/lib/release" REQUIRED) - - target_link_libraries(ll::SDL3 INTERFACE ${SDL3_LIBRARY}) - target_include_directories(ll::SDL3 SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include/") - -endif(USE_SDL_WINDOW) +find_library( SDL3_LIBRARY + NAMES SDL3 SDL3.lib libSDL3.so libSDL3.dylib + PATHS "${LIBS_PREBUILT_DIR}/lib/release" REQUIRED) +target_link_libraries(ll::SDL3 INTERFACE ${SDL3_LIBRARY}) +target_include_directories(ll::SDL3 SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include/") diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index b220afadfca..43d027a0c5d 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -1767,8 +1767,13 @@ namespace LLInitParam // implicit conversion operator const container_t&() const { return mValues; } + operator container_t&() { return mValues; } // explicit conversion const container_t& operator()() const { return mValues; } + container_t& operator()() { return mValues; } + // direct Nth item access + const typename NAME_VALUE_LOOKUP::type_value_t& operator()(size_t index) const { return mValues[index]; } + typename NAME_VALUE_LOOKUP::type_value_t& operator()(size_t index) { return mValues[index]; } iterator begin() { return mValues.begin(); } iterator end() { return mValues.end(); } @@ -2098,13 +2103,13 @@ namespace LLInitParam class Multiple : public TypedParam { typedef TypedParam super_t; - typedef Multiple self_t; - typedef typename super_t::container_t container_t; + typedef Multiple self_t; + typedef typename super_t::container_t container_t; typedef typename super_t::value_t value_t; public: - typedef typename super_t::iterator iterator; - typedef typename super_t::const_iterator const_iterator; + typedef typename super_t::iterator iterator; + typedef typename super_t::const_iterator const_iterator; using super_t::operator(); using super_t::operator const container_t&; @@ -2832,3 +2837,4 @@ namespace LLInitParam #endif // LL_LLPARAM_H + diff --git a/indra/llcommon/llkeybind.cpp b/indra/llcommon/llkeybind.cpp index abb0df0b87b..8b633ad1975 100644 --- a/indra/llcommon/llkeybind.cpp +++ b/indra/llcommon/llkeybind.cpp @@ -37,7 +37,9 @@ LLKeyData::LLKeyData() mMouse(CLICK_NONE), mKey(KEY_NONE), mMask(MASK_NONE), - mIgnoreMasks(false) + mIgnoreMasks(false), + mControllerActionType(255), + mControllerAction(0) { } @@ -46,7 +48,9 @@ LLKeyData::LLKeyData(EMouseClickType mouse, KEY key, MASK mask) mMouse(mouse), mKey(key), mMask(mask), - mIgnoreMasks(false) + mIgnoreMasks(false), + mControllerActionType(255), + mControllerAction(0) { } @@ -55,7 +59,9 @@ LLKeyData::LLKeyData(EMouseClickType mouse, KEY key, bool ignore_mask) mMouse(mouse), mKey(key), mMask(MASK_NONE), - mIgnoreMasks(ignore_mask) + mIgnoreMasks(ignore_mask), + mControllerActionType(255), + mControllerAction(0) { } @@ -64,10 +70,233 @@ LLKeyData::LLKeyData(EMouseClickType mouse, KEY key, MASK mask, bool ignore_mask mMouse(mouse), mKey(key), mMask(mask), - mIgnoreMasks(ignore_mask) + mIgnoreMasks(ignore_mask), + mControllerActionType(255), + mControllerAction(0) { } +LLKeyData::LLKeyData(U8 actionType, U8 action) + : + mMouse(CLICK_NONE), + mKey(KEY_NONE), + mMask(MASK_NONE), + mIgnoreMasks(true), + mControllerActionType(actionType), + mControllerAction(action) +{ + +} + +static std::string controllerStringFromKeyData(const U8 actionType, const U8 action) +{ + switch (actionType) + { + case 0: + { + switch(action) + { + case 0: return "axis_left"; + case 1: return "axis_right"; + case 2: return "axis_forward"; + case 3: return "axis_backward"; + case 4: return "axis_turn_left"; + case 5: return "axis_turn_right"; + case 6: return "axis_look_up"; + case 7: return "axis_look_down"; + case 8: return "axis_up"; + case 9: return "axis_down"; + case 10: return "axis_roll_left"; + case 11: return "axis_roll_right"; + } + } + case 1: + { + switch(action) + { + case 0: return "button_a"; + case 1: return "button_b"; + case 2: return "button_x"; + case 3: return "button_y"; + case 4: return "button_back"; + case 5: return "button_start"; + case 6: return "button_guide"; + case 7: return "button_leftstick"; + case 8: return "button_rightstick"; + case 9: return "button_leftshoulder"; + case 10: return "button_rightshoulder"; + case 11: return "button_dpad_up"; + case 12: return "button_dpad_down"; + case 13: return "button_dpad_left"; + case 14: return "button_dpad_right"; + case 15: return "button_misc1"; + case 16: return "button_paddle1"; + case 17: return "button_paddle2"; + case 18: return "button_paddle3"; + case 19: return "button_paddle4"; + case 20: return "button_touchpad"; + } + } + } + return "NONE"; +} + +static bool controllerActionFromString(const std::string& string, U8& actionType, U8& action) +{ + actionType = 255; + if(LLStringUtil::startsWith(string, "axis_")) { + actionType = 0; + if(string == "axis_left") + { + action = 0; + } + else if(string == "axis_right") + { + action = 1; + } + else if(string == "axis_forward") + { + action = 2; + } + else if(string == "axis_backward") + { + action = 3; + } + else if(string == "axis_turn_left") + { + action = 4; + } + else if(string == "axis_turn_right") + { + action = 5; + } + else if(string == "axis_look_up") + { + action = 6; + } + else if(string == "axis_look_down") + { + action = 7; + } + else if(string == "axis_up") + { + action = 8; + } + else if(string == "axis_down") + { + action = 9; + } + else if(string == "axis_roll_left") + { + action = 10; + } + else if(string == "axis_roll_right") + { + action = 11; + } + else + { + actionType = 255; + return false; + } + return true; + } + else if(LLStringUtil::startsWith(string, "button_")) { + actionType = 1; + if(string == "button_a") + { + action = 9; + } + else if(string == "button_b") + { + action = 1; + } + else if(string == "button_x") + { + action = 2; + } + else if(string == "button_y") + { + action = 3; + } + else if(string == "button_back") + { + action = 4; + } + else if(string == "button_start") + { + action = 5; + } + else if(string == "button_guide") + { + action = 6; + } + else if(string == "button_leftstick") + { + action = 7; + } + else if(string == "button_rightstick") + { + action = 8; + } + else if(string == "button_leftshoulder") + { + action = 9; + } + else if(string == "button_rightshoulder") + { + action = 10; + } + else if(string == "button_dpad_up") + { + action = 11; + } + else if(string == "button_dpad_down") + { + action = 12; + } + else if(string == "button_dpad_left") + { + action = 13; + } + else if(string == "button_dpad_right") + { + action = 14; + } + else if(string == "button_misc1") + { + action = 15; + } + else if(string == "button_paddle1") + { + action = 16; + } + else if(string == "button_paddle2") + { + action = 17; + } + else if(string == "button_paddle3") + { + action = 18; + } + else if(string == "button_paddle4") + { + action = 19; + } + else if(string == "button_touchpad") + { + action = 20; + } + else + { + actionType = 255; + return false; + } + return true; + } + return false; +} + LLKeyData::LLKeyData(const LLSD &key_data) { if (key_data.has("mouse")) @@ -86,6 +315,14 @@ LLKeyData::LLKeyData(const LLSD &key_data) { mMask = key_data["mask"].asInteger(); } + if (key_data.has("controller")) + { + U8 actionType = 255; + U8 action = 0; + controllerActionFromString(key_data["controller"],actionType, action); + mControllerActionType = actionType; + mControllerAction = action; + } } LLSD LLKeyData::asLLSD() const @@ -98,6 +335,7 @@ LLSD LLKeyData::asLLSD() const { data["ignore_accelerators"] = (LLSD::Boolean)mIgnoreMasks; } + data["controller"] = (LLSD::String)controllerStringFromKeyData(mControllerActionType, mControllerAction); return data; } @@ -112,23 +350,29 @@ void LLKeyData::reset() mKey = KEY_NONE; mMask = MASK_NONE; mIgnoreMasks = false; + mControllerActionType = 255; + mControllerAction = 0; } -bool LLKeyData::operator==(const LLKeyData& rhs) +bool LLKeyData::operator==(const LLKeyData& rhs) const { if (mMouse != rhs.mMouse) return false; if (mKey != rhs.mKey) return false; if (mMask != rhs.mMask) return false; if (mIgnoreMasks != rhs.mIgnoreMasks) return false; + if (mControllerActionType != rhs.mControllerActionType) return false; + if (mControllerAction != rhs.mControllerAction) return false; return true; } -bool LLKeyData::operator!=(const LLKeyData& rhs) +bool LLKeyData::operator!=(const LLKeyData& rhs) const { if (mMouse != rhs.mMouse) return true; if (mKey != rhs.mKey) return true; if (mMask != rhs.mMask) return true; if (mIgnoreMasks != rhs.mIgnoreMasks) return true; + if (mControllerActionType != rhs.mControllerActionType) return true; + if (mControllerAction != rhs.mControllerAction) return true; return false; } @@ -136,7 +380,9 @@ bool LLKeyData::canHandle(const LLKeyData& data) const { if (data.mKey == mKey && data.mMouse == mMouse - && ((mIgnoreMasks && (data.mMask & mMask) == mMask) || data.mMask == mMask)) + && ((mIgnoreMasks && (data.mMask & mMask) == mMask) || data.mMask == mMask) + && (mControllerActionType == data.mControllerActionType && mControllerAction == data.mControllerAction) + ) { return true; } @@ -170,7 +416,7 @@ LLKeyBind::LLKeyBind(const LLSD &key_bind) } } -bool LLKeyBind::operator==(const LLKeyBind& rhs) +bool LLKeyBind::operator==(const LLKeyBind& rhs) const { auto size = mData.size(); if (size != rhs.mData.size()) return false; @@ -183,7 +429,7 @@ bool LLKeyBind::operator==(const LLKeyBind& rhs) return true; } -bool LLKeyBind::operator!=(const LLKeyBind& rhs) +bool LLKeyBind::operator!=(const LLKeyBind& rhs) const { auto size = mData.size(); if (size != rhs.mData.size()) return true; diff --git a/indra/llcommon/llkeybind.h b/indra/llcommon/llkeybind.h index 0a9d4600cca..2e623c4dd78 100644 --- a/indra/llcommon/llkeybind.h +++ b/indra/llcommon/llkeybind.h @@ -37,14 +37,15 @@ class LL_COMMON_API LLKeyData LLKeyData(EMouseClickType mouse, KEY key, MASK mask); LLKeyData(EMouseClickType mouse, KEY key, bool ignore_mask); LLKeyData(EMouseClickType mouse, KEY key, MASK mask, bool ignore_mask); + LLKeyData(U8 actionType, U8 action); LLKeyData(const LLSD &key_data); LLSD asLLSD() const; bool isEmpty() const; bool empty() const { return isEmpty(); }; void reset(); - bool operator==(const LLKeyData& rhs); - bool operator!=(const LLKeyData& rhs); + bool operator==(const LLKeyData& rhs) const; + bool operator!=(const LLKeyData& rhs) const; bool canHandle(const LLKeyData& data) const; bool canHandle(EMouseClickType mouse, KEY key, MASK mask) const; @@ -52,6 +53,8 @@ class LL_COMMON_API LLKeyData EMouseClickType mMouse; KEY mKey; MASK mMask; + U8 mControllerActionType; + U8 mControllerAction; // Either to expect exact match or ignore not expected masks as long as expected mask-bit is present bool mIgnoreMasks; }; @@ -63,8 +66,8 @@ class LLKeyBind LLKeyBind() {} LLKeyBind(const LLSD &key_bind); - bool operator==(const LLKeyBind& rhs); - bool operator!=(const LLKeyBind& rhs); + bool operator==(const LLKeyBind& rhs) const; + bool operator!=(const LLKeyBind& rhs) const; bool isEmpty() const; bool empty() const { return isEmpty(); }; diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 86e1f48377e..a25baac6849 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -186,7 +186,9 @@ class WritePipeImpl : public LLProcess::WritePipe std::shared_ptr pipe) : mDesc(desc), mPipe(pipe), - mStream(&mStreambuf), + mFill(&mBufA), + mFlush(&mBufB), + mStream(mFill), mWritePending(false) {} @@ -196,7 +198,9 @@ class WritePipeImpl : public LLProcess::WritePipe virtual size_type size() const override { - return mStreambuf.size(); + // Bytes not yet handed to the OS: whatever is queued in the fill + // buffer plus whatever is still draining from the flush buffer. + return mFill->size() + mFlush->size(); } // Called from LLProcess::tick() to initiate writing buffered data. @@ -210,18 +214,24 @@ class WritePipeImpl : public LLProcess::WritePipe private: void startAsyncWrite() { - if (mWritePending || !mPipe || !mPipe->is_open() || mStreambuf.size() == 0) + if (mWritePending || !mPipe || !mPipe->is_open() || mFill->size() == 0) return; mWritePending = true; - // Snapshot the number of bytes currently buffered so the async_write - // operates on a fixed-size, stable view. Any data written to - // get_ostream() while this write is in flight lands in the streambuf's - // put area and is excluded from the current operation; it will be sent - // on the next tick(). Without this snapshot, a concurrent write to - // get_ostream() could reallocate/invalidate the buffer sequence. - std::size_t writeSize = mStreambuf.size(); + // Zero-copy double buffering. async_write must reference storage that + // stays valid AND immovable for the entire operation, but get_ostream() + // writes arriving while a write is in flight can grow the streambuf's + // put area and REALLOCATE its storage -- which would invalidate the + // buffer sequence mid-write and corrupt the data sent to the child. + // + // So we keep two buffers: get_ostream() always fills mFill; async_write + // always drains mFlush. Swapping them here, and rebinding mStream to the + // new (empty) mFill, means the in-flight write owns mFlush exclusively -- + // no subsequent get_ostream() write can touch it until this write + // completes and mWritePending goes false again. No copy, no aliasing. + std::swap(mFill, mFlush); + mStream.rdbuf(mFill); // Write all buffered data asynchronously. Do NOT self-chain in the // completion handler: sending the next buffer immediately can cause @@ -230,14 +240,16 @@ class WritePipeImpl : public LLProcess::WritePipe // was the root cause of test 18 "more than 3 events" and test 9 // "many small messages" failures. tick() calls mWritePipe->tick() on // every mainloop frame, so any newly-queued data will be sent then. - asio::async_write(*mPipe, asio::buffer(mStreambuf.data(), writeSize), + asio::async_write(*mPipe, mFlush->data(), [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { mWritePending = false; if (!ec) { - mStreambuf.consume(bytes_transferred); + // Drain the bytes we just sent, leaving mFlush empty and ready + // to serve as the next fill buffer after a future swap. + mFlush->consume(bytes_transferred); LL_DEBUGS("LLProcess") << "Wrote " << bytes_transferred << " bytes to " << mDesc << LL_ENDL; } @@ -251,7 +263,13 @@ class WritePipeImpl : public LLProcess::WritePipe std::string mDesc; std::shared_ptr mPipe; - asio::streambuf mStreambuf; + // Double buffering: get_ostream() fills one buffer while async_write drains + // the other. mFill/mFlush point at mBufA/mBufB and swap on each write; see + // startAsyncWrite(). mStream is always bound to *mFill. + asio::streambuf mBufA; + asio::streambuf mBufB; + asio::streambuf* mFill; + asio::streambuf* mFlush; std::ostream mStream; bool mWritePending; }; diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index ad2ce231fae..19ed6d9bd88 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -1043,6 +1043,31 @@ void LLSD::clear() { Impl::assignUndefined(impl); } LLSD::Type LLSD::type() const { return safe(impl).type(); } +bool LLSD::isEmpty() const +{ + switch (type()) + { + case TypeUndefined: + return true; // Always empty + case TypeBinary: + return !asBoolean(); // Empty when default + case TypeInteger: + return !asInteger(); // Empty when default + case TypeReal: + return !asReal(); // Empty when default + case TypeString: + case TypeURI: + return asString().empty(); // Empty natively + case TypeArray: + case TypeMap: + return !size(); // Empty natively + default:; + } + // All other value types (TypeDate) don't have default values so can't be empty + return false; +} + + // Scalar Constructors LLSD::LLSD(Boolean v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } LLSD::LLSD(Integer v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index afe35a65ba0..0b2768dc8ac 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -453,6 +453,7 @@ class LL_COMMON_API LLSD bool isBinary() const { return type() == TypeBinary; } bool isMap() const { return type() == TypeMap; } bool isArray() const { return type() == TypeArray; } + bool isEmpty() const; //@} /** @name Automatic Cast Protection diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 07adf71d18f..892671099e7 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1119,18 +1119,27 @@ void LLStringOps::setupDayFormat(const std::string& data) } -std::string LLStringOps::getDatetimeCode (std::string key) +std::string LLStringOps::getDatetimeCode(const std::string& key) { - std::map::iterator iter; + std::map::iterator iter = datetimeToCodes.find(key); + return iter == datetimeToCodes.end() ? LLStringUtil::null : iter->second; +} - iter = datetimeToCodes.find (key); - if (iter != datetimeToCodes.end()) +void LLStringOps::splitString(const std::string& text, char delimiter, + std::function handler) +{ + std::size_t from = 0; + for (std::size_t i = 0; i < text.size(); ++i) { - return iter->second; + if (text[i] == delimiter) + { + handler(text.substr(from, i - from)); + from = i + 1; + } } - else + if (from <= text.size()) { - return std::string(""); + handler(text.substr(from)); } } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 7dd8256e72c..fdbaaaac539 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -36,6 +36,7 @@ //#include #include #include +#include #include #include #include "llformat.h" @@ -210,7 +211,10 @@ class LL_COMMON_API LLStringOps // currently in daylight savings time? static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} - static std::string getDatetimeCode (std::string key); + static std::string getDatetimeCode (const std::string& key); + + static void splitString(const std::string& text, char delimiter, + std::function handler); // Express a value like 1234567 as "1.23M" static std::string getReadableNumber(F64 num); @@ -433,6 +437,65 @@ class LLStringUtilBase static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } + // Join non-empty strings from values using value itself and delimiter + template + static std::string join(const C& values, T delimiter = ',') + { + std::string result; + for (const std::string& value : values) + { + if (!value.empty()) + { + if (!result.empty()) + { + result += delimiter; + } + result += value; + } + } + return result; + } + + // Join non-empty strings from values using stringify(value) and delimiter + template + static std::string join(const C& values, std::function stringify, T delimiter = ',') + { + std::string result; + for (const V& value : values) + { + std::string string = stringify(value); + if (!string.empty()) + { + if (!result.empty()) + { + result += delimiter; + } + result += string; + } + } + return result; + } + + // Join non-empty strings from values using stringify(index, value) and delimiter + template + static std::string join(const C& values, std::function stringify, T delimiter = ',') + { + std::string result; + for (size_t i = 0; i < values.size(); ++i) + { + std::string string = stringify(i, values[i]); + if (!string.empty()) + { + if (!result.empty()) + { + result += delimiter; + } + result += string; + } + } + return result; + } + #ifdef _DEBUG LL_COMMON_API static void testHarness(); #endif diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp index f42cbfdac7a..161001ff246 100644 --- a/indra/llmessage/message_prehash.cpp +++ b/indra/llmessage/message_prehash.cpp @@ -1406,3 +1406,10 @@ char const* const _PREHASH_Experience = LLMessageStringTable::getInstance()->get char const* const _PREHASH_ExperienceID = LLMessageStringTable::getInstance()->getString("ExperienceID"); char const* const _PREHASH_LargeGenericMessage = LLMessageStringTable::getInstance()->getString("LargeGenericMessage"); char const* const _PREHASH_MetaData = LLMessageStringTable::getInstance()->getString("MetaData"); +char const* const _PREHASH_GameControlInput = LLMessageStringTable::getInstance()->getString("GameControlInput"); +char const* const _PREHASH_AxisData = LLMessageStringTable::getInstance()->getString("AxisData"); +char const* const _PREHASH_GameControlData = LLMessageStringTable::getInstance()->getString("GameControlData"); +char const* const _PREHASH_ActionMode = LLMessageStringTable::getInstance()->getString("ActionMode"); +char const* const _PREHASH_Axes = LLMessageStringTable::getInstance()->getString("Axes"); +char const* const _PREHASH_ModeAxes = LLMessageStringTable::getInstance()->getString("ModeAxes"); +char const* const _PREHASH_ModeButtons = LLMessageStringTable::getInstance()->getString("ModeButtons"); diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h index 53f8123b353..c9f11fd5129 100644 --- a/indra/llmessage/message_prehash.h +++ b/indra/llmessage/message_prehash.h @@ -1407,5 +1407,12 @@ extern char const* const _PREHASH_Experience; extern char const* const _PREHASH_ExperienceID; extern char const* const _PREHASH_LargeGenericMessage; extern char const* const _PREHASH_MetaData; +extern char const* const _PREHASH_GameControlInput; +extern char const* const _PREHASH_AxisData; +extern char const* const _PREHASH_GameControlData; +extern char const* const _PREHASH_ActionMode; +extern char const* const _PREHASH_Axes; +extern char const* const _PREHASH_ModeAxes; +extern char const* const _PREHASH_ModeButtons; #endif diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 7535295312c..907cfe822e9 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -120,7 +120,6 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) mButton = LLUICtrlFactory::create(button_params); - if (mAllowTextEntry) { //redo to compensate for button hack that leaves space for a character @@ -142,16 +141,12 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) // Grab the mouse-up event and make sure the button state is correct mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this)); - for (LLInitParam::ParamIterator::const_iterator it = p.items.begin(); - it != p.items.end(); - ++it) + for (LLComboBox::ItemParams item_params : p.items) { - LLScrollListItem::Params item_params = *it; - if (it->label.isProvided()) + if (item_params.label.isProvided()) { - item_params.columns.add().value(it->label()); + item_params.columns.add().value(item_params.label()); } - mList->addRow(item_params); } @@ -235,7 +230,7 @@ void LLComboBox::onCommit() bool LLComboBox::isDirty() const { bool grubby = false; - if ( mList ) + if (mList) { grubby = mList->isDirty(); } @@ -243,9 +238,9 @@ bool LLComboBox::isDirty() const } // virtual Clear dirty state -void LLComboBox::resetDirty() +void LLComboBox::resetDirty() { - if ( mList ) + if (mList) { mList->resetDirty(); } @@ -314,7 +309,7 @@ LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddP { LLScrollListItem* item = mList->addSimpleElement(name, pos); item->setEnabled(enabled); - item->setUserdata( userdata ); + item->setUserdata(userdata); if (!mAllowTextEntry && mLabel.empty()) { if (mControlVariable) @@ -358,7 +353,6 @@ void LLComboBox::sortByName(bool ascending) mList->sortOnce(0, ascending); } - // Choose an item with a given name in the menu. // Returns true if the item was found. bool LLComboBox::setSimple(const LLStringExplicit& name) @@ -384,11 +378,9 @@ void LLComboBox::setValue(const LLSD& value) return; } - bool found = mList->selectByValue(value); - if (found) + if (mList->selectByValue(value)) { - LLScrollListItem* item = mList->getFirstSelected(); - if (item) + if (mList->getFirstSelected()) { updateLabel(); } @@ -419,7 +411,8 @@ const std::string LLComboBox::getSelectedItemLabel(S32 column) const // virtual LLSD LLComboBox::getValue() const { - if (LLScrollListItem* item = mList->getFirstSelected()) + LLScrollListItem* item = mList->getFirstSelected(); + if(item) { return item->getValue(); } @@ -605,8 +598,7 @@ bool LLComboBox::selectPrevItem() void LLComboBox::setEnabledByValue(const LLSD& value, bool enabled) { - LLScrollListItem *found = mList->getItem(value); - if (found) + if (LLScrollListItem* found = mList->getItem(value)) { found->setEnabled(enabled); } @@ -669,7 +661,7 @@ void LLComboBox::setLeftTextPadding(S32 pad) void* LLComboBox::getCurrentUserdata() { LLScrollListItem* item = mList->getFirstSelected(); - if( item ) + if (item) { return item->getUserdata(); } @@ -775,15 +767,19 @@ void LLComboBox::hideList() if (mList->getVisible()) { // assert selection in list - if(mAllowNewValues) + if (mAllowNewValues) { // mLastSelectedIndex = -1 means that we entered a new value, don't select // any of existing items in this case. - if(mLastSelectedIndex >= 0) + if (mLastSelectedIndex >= 0) + { mList->selectNthItem(mLastSelectedIndex); + } } - else if(mLastSelectedIndex >= 0) + else if (mLastSelectedIndex >= 0) + { mList->selectNthItem(mLastSelectedIndex); + } mButton->setToggleState(false); mList->setVisible(false); @@ -869,7 +865,7 @@ bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) { std::string tool_tip; - if(LLUICtrl::handleToolTip(x, y, mask)) + if (LLUICtrl::handleToolTip(x, y, mask)) { return true; } @@ -886,6 +882,7 @@ bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) .message(tool_tip) .sticky_rect(calcScreenRect())); } + return true; } diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index bdf88768c3c..e214bd87b06 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -31,6 +31,7 @@ #include "llcheckboxctrl.h" #include "llfontvertexbuffer.h" +#include "llrender.h" // gGL #include "llui.h" // LLUIImage #include "lluictrlfactory.h" @@ -499,6 +500,30 @@ LLScrollListCheck::LLScrollListCheck(const LLScrollListCell::Params& p) LLRect rect(mCheckBox->getRect()); if (p.width) { + // The checkbox glyph is drawn at the left of the control (see the + // check_button defaults in widgets/check_box.xml: left=2, width=13), so + // by default the box hugs the left edge of the cell. Honor the cell's + // horizontal alignment by sliding the control so the box sits centered + // or against the right edge instead. + const S32 box_inset = 2; // check_button left inset + const S32 box_width = 13; // check_button width + const S32 box_footprint = box_inset + box_width; + S32 left = 0; + switch (p.font_halign) + { + case LLFontGL::RIGHT: + // mirror the left inset on the right so the box sits box_inset px in + left = llmax(0, p.width - box_footprint - box_inset); + break; + case LLFontGL::HCENTER: + left = llmax(0, (p.width - box_footprint) / 2); + break; + case LLFontGL::LEFT: + default: + left = 0; + break; + } + rect.mLeft = left; rect.mRight = rect.mLeft + p.width; mCheckBox->setRect(rect); setWidth(p.width); @@ -520,7 +545,18 @@ LLScrollListCheck::~LLScrollListCheck() void LLScrollListCheck::draw(const LLColor4& color, const LLColor4& highlight_color) { - mCheckBox->draw(); + // mCheckBox is a standalone control, not parented into the view hierarchy, + // so nothing else applies the translate its own rect implies (unlike + // LLView::drawChild(), which does this for normal children). Without this, + // the horizontal offset computed in the constructor (for font_halign) has no + // visible effect: the checkbox always renders flush with the cell's origin. + gGL.matrixMode(LLRender::MM_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)mCheckBox->getRect().mLeft, (F32)mCheckBox->getRect().mBottom); + mCheckBox->draw(); + } + LLUI::popMatrix(); } bool LLScrollListCheck::handleClick() diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 662fe976896..2dc74ccb49f 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -326,17 +326,11 @@ S32 LLScrollListCtrl::getSearchColumn() } return llclamp(mSearchColumn, 0, getNumColumns()); } -/*virtual*/ + +// virtual bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child) { - if (child->hasName("column") || child->hasName("row")) - { - return true; // skip - } - else - { - return false; - } + return child->hasName("column") || child->hasName("row"); } LLScrollListCtrl::~LLScrollListCtrl() @@ -356,7 +350,6 @@ LLScrollListCtrl::~LLScrollListCtrl() } } - bool LLScrollListCtrl::setMaxItemCount(S32 max_count) { if (max_count >= getItemCount()) @@ -379,10 +372,9 @@ S32 LLScrollListCtrl::getItemCount() const bool LLScrollListCtrl::hasSelectedItem() const { item_list::iterator iter; - for (iter = mItemList.begin(); iter < mItemList.end(); ) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem* itemp = *iter; - if (itemp && itemp->getSelected()) + if (item->getSelected()) { return true; } @@ -396,7 +388,6 @@ void LLScrollListCtrl::clearRows() { std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); mItemList.clear(); - //mItemCount = 0; // Scroll the bar back up to the top. mScrollbar->setDocParams(0, 0); @@ -407,7 +398,6 @@ void LLScrollListCtrl::clearRows() mDirty = false; } - LLScrollListItem* LLScrollListCtrl::getFirstSelected() const { for (LLScrollListItem* item : mItemList) @@ -444,14 +434,12 @@ S32 LLScrollListCtrl::getNumSelected() const ++numSelected; } } - return numSelected; } S32 LLScrollListCtrl::getFirstSelectedIndex() const { S32 CurSelectedIndex = 0; - // make sure sort is up to date before returning an index updateSort(); @@ -463,7 +451,6 @@ S32 LLScrollListCtrl::getFirstSelectedIndex() const } CurSelectedIndex++; } - return -1; } @@ -477,6 +464,15 @@ LLScrollListItem* LLScrollListCtrl::getLastData() const return mItemList.empty() ? NULL : mItemList.back(); } +LLScrollListItem* LLScrollListCtrl::getNthData(size_t index) const +{ + if (mItemList.size() <= index) + { + return NULL; + } + return mItemList[index]; +} + std::vector LLScrollListCtrl::getAllData() const { std::vector ret; @@ -1052,7 +1048,7 @@ S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) { LLScrollListItem* item = *iter; uuid_vec_t::iterator iditr; - for(iditr = ids.begin(); iditr != ids.end(); ++iditr) + for (iditr = ids.begin(); iditr != ids.end(); ++iditr) { if (item->getEnabled() && (item->getUUID() == (*iditr))) { @@ -1062,7 +1058,10 @@ S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) break; } } - if(ids.end() != iditr) ids.erase(iditr); + if (ids.end() != iditr) + { + ids.erase(iditr); + } } if (mCommitOnSelectionChange) @@ -1077,11 +1076,9 @@ S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const updateSort(); S32 index = 0; - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem *itemp = *iter; - if (target_item == itemp) + if (target_item == item) { return index; } @@ -1095,11 +1092,9 @@ S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const updateSort(); S32 index = 0; - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem *itemp = *iter; - if (target_id == itemp->getUUID()) + if (target_id == item->getUUID()) { return index; } @@ -1122,10 +1117,8 @@ void LLScrollListCtrl::selectPrevItem( bool extend_selection) updateSort(); item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* cur_item : mItemList) { - LLScrollListItem* cur_item = *iter; - if (cur_item->getSelected()) { if (prev_item) @@ -1164,11 +1157,8 @@ void LLScrollListCtrl::selectNextItem( bool extend_selection) { updateSort(); - item_list::reverse_iterator iter; - for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++) + for (LLScrollListItem* cur_item : mItemList) { - LLScrollListItem* cur_item = *iter; - if (cur_item->getSelected()) { if (next_item) @@ -1197,10 +1187,8 @@ void LLScrollListCtrl::selectNextItem( bool extend_selection) void LLScrollListCtrl::deselectAllItems(bool no_commit_on_change) { - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem* item = *iter; deselectItem(item); } @@ -1738,6 +1726,16 @@ void LLScrollListCtrl::setEnabled(bool enabled) mCanSelect = enabled; setTabStop(enabled); mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize()); + + // when the table is disabled also disable its items + for (LLScrollListItem* item : mItemList) + { + item->setEnabled(enabled); + if (!enabled) + { + item->setSelected(false); + } + } } bool LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) @@ -2157,9 +2155,10 @@ bool LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask) for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) { LLScrollListItem* item = *iter; - if (item->getSelected()) + if (item && item->getSelected()) { LLScrollListCell* cellp = item->getColumn(column_index); + if (!cellp) continue; cellp->setValue(item_value); cellp->onCommit(); if (mLastSelected == NULL) @@ -2637,7 +2636,9 @@ bool LLScrollListCtrl::isRepeatedChars(const LLWString& string) const void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, S32 cell, bool select_single_item) { - if (itemp && !itemp->getSelected()) + if (!itemp) return; + + if (!itemp->getSelected() || itemp->getSelectedCell() != cell) { if (mLastSelected) { @@ -2913,11 +2914,9 @@ bool LLScrollListCtrl::canCut() const void LLScrollListCtrl::selectAll() { // Deselects all other items - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* itemp : mItemList) { - LLScrollListItem *itemp = *iter; - if( itemp->getEnabled() ) + if (itemp->getEnabled()) { selectItem(itemp, -1, false); } @@ -2957,7 +2956,8 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos) { - if (!column_params.validateBlock()) return; + if (!column_params.validateBlock()) + return; std::string name = column_params.name; // if no column name provided, just use ordinal as name @@ -2984,7 +2984,7 @@ void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params { new_column->setWidth((S32)ll_round(new_column->mRelWidth*mItemListRect.getWidth())); } - else if(new_column->mDynamicWidth) + else if (new_column->mDynamicWidth) { mNumDynamicWidthColumns++; new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns); @@ -2992,14 +2992,12 @@ void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params S32 top = mItemListRect.mTop; S32 left = mItemListRect.mLeft; - for (column_map_t::iterator itor = mColumns.begin(); - itor != mColumns.end(); - ++itor) + for (const auto& data_pair : mColumns) { - if (itor->second->mIndex < new_column->mIndex && - itor->second->getWidth() > 0) + if (data_pair.second->mIndex < new_column->mIndex && + data_pair.second->getWidth() > 0) { - left += itor->second->getWidth() + mColumnPadding; + left += data_pair.second->getWidth() + mColumnPadding; } } @@ -3165,17 +3163,16 @@ LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - if (!item_p.validateBlock() || !new_item) return NULL; - new_item->setNumColumns(static_cast(mColumns.size())); + if (!item_p.validateBlock() || !new_item) + return nullptr; + + new_item->setNumColumns((S32)mColumns.size()); // Add any columns we don't already have S32 col_index = 0; - for(LLInitParam::ParamIterator::const_iterator itor = item_p.columns.begin(); - itor != item_p.columns.end(); - ++itor) + for (LLScrollListCell::Params cell_p : item_p.columns) { - LLScrollListCell::Params cell_p = *itor; std::string column = cell_p.column; // empty columns strings index by ordinal @@ -3238,8 +3235,7 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS new_item->setNumColumns(static_cast(mColumns.size())); } - LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value)); - if (cell) + if (LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value))) { LLScrollListColumn* columnp = mColumns.begin()->second; @@ -3254,15 +3250,13 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS } // add dummy cells for missing columns - for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it) + for (const auto& column_it : mColumns) { - S32 column_idx = column_it->second->mIndex; + S32 column_idx = column_it.second->mIndex; if (new_item->getColumn(column_idx) == NULL) { - LLScrollListColumn* column_ptr = column_it->second; LLScrollListCell::Params cell_p; - cell_p.width = column_ptr->getWidth(); - + cell_p.width = column_it.second->getWidth(); new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p)); } } diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index b8d63972a51..1640fcf1f0e 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -178,6 +178,7 @@ class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, virtual LLScrollListColumn* getColumn(S32 index); virtual LLScrollListColumn* getColumn(const std::string& name); virtual S32 getNumColumns() const { return static_cast(mColumnsIndexed.size()); } + virtual S32 getPageLines() const { return mPageLines; } // Adds a single element, from an array of: // "columns" => [ "column" => column name, "value" => value, "type" => type, "font" => font, "font-style" => style ], "id" => uuid @@ -291,6 +292,7 @@ class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, // iterate over all items LLScrollListItem* getFirstData() const; LLScrollListItem* getLastData() const; + LLScrollListItem* getNthData(size_t index) const; std::vector getAllData() const; LLScrollListItem* getItem(const LLSD& sd) const; diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 27f1dcb03df..0afc4de9462 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -1258,7 +1258,6 @@ void LLTabContainer::removeTabPanel(LLPanel* child) bool has_focus = gFocusMgr.childHasKeyboardFocus(this); - // If the tab being deleted is the selected one, select a different tab. for(std::vector::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { LLTabTuple* tuple = *iter; @@ -1296,6 +1295,7 @@ void LLTabContainer::removeTabPanel(LLPanel* child) // make sure we don't have more locked tabs than we have tabs mLockedTabCount = llmin(getTabCount(), mLockedTabCount); + // If the tab being deleted is the selected one, select a different tab. if (mCurrentTabIdx >= (S32)mTabList.size()) { mCurrentTabIdx = static_cast(mTabList.size()) - 1; @@ -1737,7 +1737,7 @@ void LLTabContainer::reshapeTuple(LLTabTuple* tuple) { S32 image_overlay_width = 0; - if(mCustomIconCtrlUsed) + if (mCustomIconCtrlUsed) { LLCustomButtonIconCtrl* button = dynamic_cast(tuple->mButton); LLIconCtrl* icon_ctrl = button ? button->getIconCtrl() : NULL; @@ -2195,12 +2195,22 @@ S32 LLTabContainer::getTotalTabWidth() const void LLTabContainer::setTabVisibility( LLPanel const *aPanel, bool aVisible ) { - for( tuple_list_t::const_iterator itr = mTabList.begin(); itr != mTabList.end(); ++itr ) + S32 num_tabs = S32(mTabList.size()); + for (S32 i = 0; i < num_tabs; ++i) { - LLTabTuple const *pTT = *itr; - if( pTT->mTabPanel == aPanel ) + LLTabTuple* tuple = mTabList[i]; + if( tuple->mTabPanel == aPanel ) { - pTT->mVisible = aVisible; + if (tuple->mVisible != aVisible) + { + tuple->mVisible = aVisible; + if (aVisible) + { + this->selectTab(i); + this->setVisible(true); + } + updateMaxScrollPos(); + } break; } } @@ -2216,11 +2226,7 @@ void LLTabContainer::setTabVisibility( LLPanel const *aPanel, bool aVisible ) break; } } - - if( foundTab ) - this->setVisible( true ); - else - this->setVisible( false ); + this->setVisible( foundTab ); updateMaxScrollPos(); } diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index bba80dd20eb..fb6d90f86f0 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -34,6 +34,7 @@ #include "llavatarnamecache.h" #include "llcachename.h" +#include "llgamecontrol.h" #include "llkeyboard.h" #include "llregex.h" #include "llscrolllistctrl.h" // for LLUrlEntryKeybinding file parsing @@ -403,9 +404,9 @@ bool LLUrlEntryInvalidSLURL::isSLURLvalid(const std::string &url) const if (path_parts == actual_parts) { // handle slurl with (X,Y,Z) coordinates - LLStringUtil::convertToS32(path_array[path_parts-3],x); - LLStringUtil::convertToS32(path_array[path_parts-2],y); - LLStringUtil::convertToS32(path_array[path_parts-1],z); + LLStringUtil::convertToS32(path_array[path_parts - 3].asStringRef(), x); + LLStringUtil::convertToS32(path_array[path_parts - 2].asStringRef(), y); + LLStringUtil::convertToS32(path_array[path_parts - 1].asStringRef(), z); if((x>= 0 && x<= 256) && (y>= 0 && y<= 256) && (z>= 0)) { @@ -416,8 +417,8 @@ bool LLUrlEntryInvalidSLURL::isSLURLvalid(const std::string &url) const { // handle slurl with (X,Y) coordinates - LLStringUtil::convertToS32(path_array[path_parts-2],x); - LLStringUtil::convertToS32(path_array[path_parts-1],y); + LLStringUtil::convertToS32(path_array[path_parts - 2].asStringRef(), x); + LLStringUtil::convertToS32(path_array[path_parts - 1].asStringRef(), y); ; if((x>= 0 && x<= 256) && (y>= 0 && y<= 256)) { @@ -427,7 +428,7 @@ bool LLUrlEntryInvalidSLURL::isSLURLvalid(const std::string &url) const else if (path_parts == (actual_parts-2)) { // handle slurl with (X) coordinate - LLStringUtil::convertToS32(path_array[path_parts-1],x); + LLStringUtil::convertToS32(path_array[path_parts - 1].asStringRef(), x); if(x>= 0 && x<= 256) { return true; @@ -1749,6 +1750,7 @@ void LLUrlEntryKeybinding::initLocalization() initLocalizationFromFile("control_table_contents_camera.xml"); initLocalizationFromFile("control_table_contents_editing.xml"); initLocalizationFromFile("control_table_contents_media.xml"); + initLocalizationFromFile("control_table_contents_game_control.xml"); } void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename) @@ -1784,3 +1786,138 @@ void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename) } } } + + +// +// LLUrlEntryGameController Displays currently assigned game control binding +// +LLUrlEntryGameController::LLUrlEntryGameController() + : LLUrlEntryBase() + , pKeyHandler(NULL) + , pControllerHandler(NULL) +{ + mPattern = boost::regex(APP_HEADER_REGEX "/gamecontroller/[\\w+-]+(\\?mode=\\w+)?$", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_experience.xml"; + + initLocalization(); +} + +std::string LLUrlEntryGameController::getLabel(const std::string& url, const LLUrlLabelCallback& cb) +{ + std::string control = getControlName(url); + + std::string keybind = ""; + if (pControllerHandler && pControllerHandler->hasHandlingDevice()) + { + keybind = pControllerHandler->getBindingAsString(control); + LL_WARNS() << "Game Control Translate:" << control << " = " << keybind << LL_ENDL; + if(!keybind.empty()) + { + std::map::iterator iter = mLocalizations.find(keybind); + if(iter != mLocalizations.end()) { + keybind = iter->second.mLocalization; + } + // Debug text change for testing + keybind = keybind + " (Game Control)"; + return keybind; + } + } + // Fallback to key binding if game control binding is not found + if (pKeyHandler && keybind.empty()) + { + LLStringUtil::toLower(control); + keybind = pKeyHandler->getKeyBindingAsString(getMode(url), "game_control_" + control); + LL_WARNS() << "Key Binding Translate:" << control << " = " << keybind << LL_ENDL; + if(!keybind.empty()) + { + // Debug text change for testing + keybind = keybind + " (Key Binding)"; + return keybind; + } + } + + return "Unbound Control: " + control; +} + +std::string LLUrlEntryGameController::getTooltip(const std::string& url) const +{ + std::string control = getControlName(url); + + std::map::const_iterator iter = mLocalizations.find(control); + if (iter != mLocalizations.end()) + { + return iter->second.mTooltip; + } + return url; +} + +std::string LLUrlEntryGameController::getControlName(const std::string& url) const +{ + std::string search = "/gamecontroller/"; + size_t pos_start = url.find(search); + if (pos_start == std::string::npos) + { + return std::string(); + } + pos_start += search.size(); + + size_t pos_end = url.find("?mode="); + if (pos_end == std::string::npos) + { + pos_end = url.size(); + } + return url.substr(pos_start, pos_end - pos_start); +} + +std::string LLUrlEntryGameController::getMode(const std::string& url) const +{ + std::string search = "?mode="; + size_t pos_start = url.find(search); + if (pos_start == std::string::npos) + { + return std::string(); + } + pos_start += search.size(); + return url.substr(pos_start, url.size() - pos_start); +} + +void LLUrlEntryGameController::initLocalization() +{ + initLocalizationFromFile("control_table_contents_game_control.xml"); + // TODO : Need to add localization for other game control bindings specifically axes +} + +void LLUrlEntryGameController::initLocalizationFromFile(const std::string& filename) +{ + LLXMLNodePtr xmlNode; + LLScrollListCtrl::Contents contents; + if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) + { + LL_WARNS() << "Failed to load " << filename << LL_ENDL; + return; + } + LLXUIParser parser; + parser.readXUI(xmlNode, contents, filename); + + if (!contents.validateBlock()) + { + LL_WARNS() << "Failed to validate " << filename << LL_ENDL; + return; + } + + for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); + row_it != contents.rows.end(); + ++row_it) + { + std::string control = row_it->value.getValue().asString(); + if (!control.empty() && control != "menu_separator") + { + mLocalizations[control] = + LLLocalizationData( + row_it->columns.begin()->value.getValue().asString(), + row_it->columns.begin()->tool_tip.getValue() + ); + } + } +} diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index efb5081103d..4ac6624bc47 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -610,4 +610,40 @@ class LLUrlEntryKeybinding: public LLUrlEntryBase LLKeyBindingToStringHandler* pHandler; }; + +class LLGameControllerBindingToStringHandler; +/// LLUrlEntryGameController A way to access game controller bindings and show +/// currently used one in text. +/// secondlife:///app/gamecontroller/{control_name} +class LLUrlEntryGameController: public LLUrlEntryBase +{ +public: + LLUrlEntryGameController(); + /*virtual*/ std::string getLabel(const std::string& url, const LLUrlLabelCallback& cb); + /*virtual*/ std::string getTooltip(const std::string& url) const; + void setKeyHandler(LLKeyBindingToStringHandler* handler) {pKeyHandler = handler;} + void setControllerHandler(LLGameControllerBindingToStringHandler* handler) {pControllerHandler = handler;} + +private: + std::string getControlName(const std::string& url) const; + std::string getMode(const std::string& url) const; + void initLocalization(); + void initLocalizationFromFile(const std::string& filename); + + struct LLLocalizationData + { + LLLocalizationData() {} + LLLocalizationData(const std::string& localization, const std::string& tooltip) + : mLocalization(localization) + , mTooltip(tooltip) + {} + std::string mLocalization; + std::string mTooltip; + }; + + std::map mLocalizations; + LLKeyBindingToStringHandler* pKeyHandler; + LLGameControllerBindingToStringHandler* pControllerHandler; +}; + #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 8a21222b3cd..8a2f959a427 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -77,6 +77,8 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryExperienceProfile()); mUrlEntryKeybinding = new LLUrlEntryKeybinding(); registerUrl(mUrlEntryKeybinding); + mUrlEntryGameController = new LLUrlEntryGameController(); + registerUrl(mUrlEntryGameController); //LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern, //so it should be registered in the end of list registerUrl(new LLUrlEntrySL()); @@ -389,8 +391,16 @@ bool LLUrlRegistry::isUrl(const LLWString &text) void LLUrlRegistry::setKeybindingHandler(LLKeyBindingToStringHandler* handler) { - LLUrlEntryKeybinding *entry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding; - entry->setHandler(handler); + LLUrlEntryKeybinding *keyEntry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding; + keyEntry->setHandler(handler); + LLUrlEntryGameController *controllerEntry = (LLUrlEntryGameController*)mUrlEntryGameController; + controllerEntry->setKeyHandler(handler); +} + +void LLUrlRegistry::setGameControllerHandler(LLGameControllerBindingToStringHandler* handler) +{ + LLUrlEntryGameController *controllerEntry = (LLUrlEntryGameController*)mUrlEntryGameController; + controllerEntry->setControllerHandler(handler); } bool LLUrlRegistry::containsAgentMention(const std::string& text) diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index 592e422487c..fa1e743569c 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -36,6 +36,7 @@ #include class LLKeyBindingToStringHandler; +class LLGameControllerBindingToStringHandler; /// This default callback for findUrl() simply ignores any label updates void LLUrlRegistryNullCallback(const std::string &url, @@ -92,6 +93,10 @@ class LLUrlRegistry : public LLSingleton // Set handler for url registry to be capable of parsing and populating keybindings void setKeybindingHandler(LLKeyBindingToStringHandler* handler); + // Set handler for url registry to be capable of parsing and populating + // game-controller bindings. + void setGameControllerHandler(LLGameControllerBindingToStringHandler* handler); + bool containsAgentMention(const std::string& text); private: @@ -103,6 +108,7 @@ class LLUrlRegistry : public LLSingleton LLUrlEntryBase* mUrlEntrySLLabel; LLUrlEntryBase* mUrlEntryNoLink; LLUrlEntryBase* mUrlEntryKeybinding; + LLUrlEntryBase* mUrlEntryGameController; LLUrlEntryBase* mUrlEntryAgentMention; }; diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 7d6c937b857..053b0c5b274 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -642,10 +642,10 @@ void LLView::onVisibilityChange ( bool new_visibility ) if(log_visibility_change) { - if (old_visibility!=new_visibility) - { - LLViewerEventRecorder::instance().logVisibilityChange( viewp->getPathname(), viewp->getName(), new_visibility,"widget"); - } + if (old_visibility!=new_visibility) + { + LLViewerEventRecorder::instance().logVisibilityChange( viewp->getPathname(), viewp->getName(), new_visibility,"widget"); + } } if (old_visibility) diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 5f8a9a64ebb..9c6bb3e7ddc 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -23,8 +23,11 @@ include(SDL3) set(llwindow_SOURCE_FILES llcursortypes.cpp + llgamecontrol.cpp + llgamecontroltranslator.cpp llkeyboard.cpp llkeyboardheadless.cpp + llsdl.cpp llwindowheadless.cpp llwindowcallbacks.cpp llwindow.cpp @@ -34,8 +37,11 @@ set(llwindow_HEADER_FILES CMakeLists.txt llcursortypes.h + llgamecontrol.h + llgamecontroltranslator.h llkeyboard.h llkeyboardheadless.h + llsdl.h llwindowheadless.h llwindowcallbacks.h ) @@ -68,13 +74,11 @@ set(macosx_HEADER_FILES ) set(linux_SOURCE_FILES - llsdl.cpp llkeyboardsdl.cpp llwindowsdl.cpp ) set(linux_HEADER_FILES - llsdl.h llkeyboardsdl.h llwindowsdl.h ) @@ -103,6 +107,7 @@ set(llwindow_LINK_LIBRARIES ll::glm ll::glext ll::uilibraries + ll::SDL3 ) if (USE_SDL_WINDOW) @@ -191,7 +196,7 @@ if (BUILD_HEADLESS) dl ) target_include_directories(llwindowheadless INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) - + if (USE_PRECOMPILED_HEADERS) target_precompile_headers(llwindowheadless REUSE_FROM llprecompiled) endif () diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp new file mode 100755 index 00000000000..f91660511d6 --- /dev/null +++ b/indra/llwindow/llgamecontrol.cpp @@ -0,0 +1,4328 @@ +/** + * @file llgamecontrol.h + * @brief GameController detection and management + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llgamecontrol.h" + +#include +#include + +#include "SDL3/SDL.h" +#include "SDL3/SDL_gamepad.h" +#include "SDL3/SDL_joystick.h" + +#include "indra_constants.h" +#include "llfile.h" +#include "llgamecontroltranslator.h" +#include "llsd.h" +#include "llsdl.h" +#include "lltimer.h" + +namespace std +{ + string to_string(const SDL_GUID& guid) + { + char buffer[33] = { 0 }; + SDL_GUIDToString(guid, buffer, sizeof(guid)); + return buffer; + } + + string to_string(SDL_JoystickType type) + { + switch (type) + { + case SDL_JOYSTICK_TYPE_GAMEPAD: + return "GAMECONTROLLER"; + case SDL_JOYSTICK_TYPE_WHEEL: + return "WHEEL"; + case SDL_JOYSTICK_TYPE_ARCADE_STICK: + return "ARCADE_STICK"; + case SDL_JOYSTICK_TYPE_FLIGHT_STICK: + return "FLIGHT_STICK"; + case SDL_JOYSTICK_TYPE_DANCE_PAD: + return "DANCE_PAD"; + case SDL_JOYSTICK_TYPE_GUITAR: + return "GUITAR"; + case SDL_JOYSTICK_TYPE_DRUM_KIT: + return "DRUM_KIT"; + case SDL_JOYSTICK_TYPE_ARCADE_PAD: + return "ARCADE_PAD"; + case SDL_JOYSTICK_TYPE_THROTTLE: + return "THROTTLE"; + default:; + } + return "UNKNOWN"; + } + + string to_string(SDL_GamepadType type) + { + switch (type) + { + case SDL_GAMEPAD_TYPE_STANDARD: + return "STANDARD"; + case SDL_GAMEPAD_TYPE_XBOX360: + return "XBOX360"; + case SDL_GAMEPAD_TYPE_XBOXONE: + return "XBOXONE"; + case SDL_GAMEPAD_TYPE_PS3: + return "PS3"; + case SDL_GAMEPAD_TYPE_PS4: + return "PS4"; + case SDL_GAMEPAD_TYPE_PS5: + return "PS5"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + return "NINTENDO_SWITCH_PRO"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + return "NINTENDO_SWITCH_JOYCON_LEFT"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + return "NINTENDO_SWITCH_JOYCON_RIGHT"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return "NINTENDO_SWITCH_JOYCON_PAIR"; + default:; + } + return "UNKNOWN"; + } + + string to_string(SDL_GamepadButton button) + { + return SDL_GetGamepadStringForButton(button); + } + + string to_string(SDL_GamepadAxis axis) + { + return SDL_GetGamepadStringForAxis(axis); + } + + string to_string(SDL_GamepadButtonLabel label) + { + switch (label) + { + case SDL_GAMEPAD_BUTTON_LABEL_A: + return "a"; + case SDL_GAMEPAD_BUTTON_LABEL_B: + return "b"; + case SDL_GAMEPAD_BUTTON_LABEL_X: + return "x"; + case SDL_GAMEPAD_BUTTON_LABEL_Y: + return "y"; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + return "cross"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: + return "circle"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: + return "square"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: + return "triangle"; + default: + return "UNKOWN"; + } + } +} + +// Util for dumping SDL_JoystickGUID info +std::ostream& operator<<(std::ostream& out, SDL_GUID& guid) +{ + return out << std::to_string(guid); +} + +// Util for dumping SDL_JoystickType type name +std::ostream& operator<<(std::ostream& out, SDL_JoystickType type) +{ + return out << std::to_string(type); +} + +// Util for dumping SDL_GameControllerType type name +std::ostream& operator<<(std::ostream& out, SDL_GamepadType type) +{ + return out << std::to_string(type); +} + +namespace std +{ + string to_string(SDL_Joystick* joystick) + { + if (!joystick) + { + return "nullptr"; + } + + std::stringstream ss; + + ss << "{id:" << SDL_GetJoystickID(joystick); + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + ss << ",guid:'" << guid << "'"; + ss << ",type:'" << SDL_GetJoystickType(joystick) << "'"; + ss << ",name:'" << ll_safe_string(SDL_GetJoystickName(joystick)) << "'"; + ss << ",vendor:" << SDL_GetJoystickVendor(joystick); + ss << ",product:" << SDL_GetJoystickProduct(joystick); + if (U16 version = SDL_GetJoystickProductVersion(joystick)) + { + ss << ",version:" << version; + } + if (const char* serial = SDL_GetJoystickSerial(joystick)) + { + ss << ",serial:'" << serial << "'"; + } + ss << ",num_axes:" << SDL_GetNumJoystickAxes(joystick); + ss << ",num_balls:" << SDL_GetNumJoystickBalls(joystick); + ss << ",num_hats:" << SDL_GetNumJoystickHats(joystick); + ss << ",num_buttons:" << SDL_GetNumJoystickButtons(joystick); + ss << "}"; + + return ss.str(); + } + + string to_string(SDL_Gamepad* controller) + { + if (!controller) + { + return "nullptr"; + } + + stringstream ss; + + ss << "{type:'" << SDL_GetGamepadType(controller) << "'"; + ss << ",name:'" << ll_safe_string(SDL_GetGamepadName(controller)) << "'"; + ss << ",vendor:" << SDL_GetGamepadVendor(controller); + ss << ",product:" << SDL_GetGamepadProduct(controller); + if (U16 version = SDL_GetGamepadProductVersion(controller)) + { + ss << ",version:" << version; + } + if (const char* serial = SDL_GetGamepadSerial(controller)) + { + ss << ",serial:'" << serial << "'"; + } + ss << "}"; + + return ss.str(); + } +} + +// Util for dumping SDL_Joystick info +std::ostream& operator<<(std::ostream& out, SDL_Joystick* joystick) +{ + return out << std::to_string(joystick); +} + +// Util for dumping SDL_GameController info +std::ostream& operator<<(std::ostream& out, SDL_Gamepad* controller) +{ + return out << std::to_string(controller); +} + +std::string LLGameControl::InputChannel::getLocalName() const +{ + // HACK: we hard-code English channel names, but + // they should be loaded from localized XML config files. + + if (isAxis() && mIndex < NUM_AXES) + { + return "AXIS_" + std::to_string((U32)mIndex); + } + + if (isButton() && mIndex < NUM_BUTTONS) + { + return "BUTTON_" + std::to_string((U32)mIndex); + } + + return "NONE"; +} + +std::string LLGameControl::InputChannel::getSignedLocalName() const +{ + std::string name = getLocalName(); + if (isAxis() && mIndex < NUM_AXES) + { + name.append(mSign < 0 ? "-" : mSign > 0 ? "+" : ""); + } + return name; +} + +std::string LLGameControl::InputChannel::getRemoteName() const +{ + // HACK: we hard-code English channel names, but + // they should be loaded from localized XML config files. + std::string name = " "; + // AXIS_LEFTX, BUTTON_SOUTH, etc + if (isAxis()) + { + switch (mIndex) + { + case 0: + name = "AXIS_LEFTX"; + break; + case 1: + name = "AXIS_LEFTY"; + break; + case 2: + name = "AXIS_RIGHTX"; + break; + case 3: + name = "AXIS_RIGHTY"; + break; + case 4: + name = "AXIS_LEFT_TRIGGER"; + break; + case 5: + name = "AXIS_RIGHT_TRIGGER"; + break; + default: + break; + } + } + else if (isButton()) + { + switch(mIndex) + { + case 0: + name = "BUTTON_SOUTH"; + break; + case 1: + name = "BUTTON_EAST"; + break; + case 2: + name = "BUTTON_WEST"; + break; + case 3: + name = "BUTTON_NORTH"; + break; + case 4: + name = "BUTTON_SELECT"; + break; + case 5: + name = "BUTTON_HOME"; + break; + case 6: + name = "BUTTON_START"; + break; + case 7: + name = "BUTTON_LEFT_STICK"; + break; + case 8: + name = "BUTTON_RIGHT_STICK"; + break; + case 9: + name = "BUTTON_LEFT_SHOULDER"; + break; + case 10: + name = "BUTTON_RIGHT_SHOULDER"; + break; + case 11: + name = "BUTTON_DPAD_UP"; + break; + case 12: + name = "BUTTON_DPAD_DOWN"; + break; + case 13: + name = "BUTTON_DPAD_LEFT"; + break; + case 14: + name = "BUTTON_DPAD_RIGHT"; + break; + case 15: + name = "BUTTON_MISC1"; + break; + case 16: + name = "BUTTON_PADDLE1"; + break; + case 17: + name = "BUTTON_PADDLE2"; + break; + case 18: + name = "BUTTON_PADDLE3"; + break; + case 19: + name = "BUTTON_PADDLE4"; + break; + case 20: + name = "BUTTON_TOUCHPAD"; + break; + default: + break; + } + } + return name; +} + + +// How a physical/canonical axis feeds the analog action it is bound to. A normal +// bidirectional stick axis drives both directions (HALF_FULL). A single trigger, +// used as one side of the "Triggers left/right" pair, is positive-only and drives +// just one direction of the action: the left trigger the negative side, the right +// trigger the positive side. +enum AxisHalf : S8 +{ + HALF_NEGATIVE = -1, + HALF_FULL = 0, + HALF_POSITIVE = 1, +}; + +struct AxisActionBinding +{ + std::string label; // bound UI action label, empty when unbound + S8 half { HALF_FULL }; + bool invert { false }; // per-mode, per-action Invert flag (see getAxisInvert) +}; + +// internal class for managing list of controllers and per-controller state +class LLGameControllerManager +{ +public: + using ActionToChannelMap = std::map< std::string, LLGameControl::InputChannel >; + LLGameControllerManager(); + + void resetDeviceOptionsToDefaults(); + void applyRememberedDeviceOptions(); + void rememberDeviceOptions() const; + void setDeviceOptions(const std::string& guid, const LLGameControl::Options& options); + + void addController(SDL_JoystickID id, const std::string& guid, const std::string& name); + void removeController(SDL_JoystickID id); + + const LLGameControl::Device* getLastActiveDevice() const; + + void onAxis(SDL_JoystickID id, U8 axis, S16 value); + void onButton(SDL_JoystickID id, U8 button, bool pressed); + + // Re-derives a device's live (post-fix) axis state from its last-known raw + // values using its *current* Options, instead of waiting for the next + // hardware axis event. + void recomputeDeviceAxesFromRaw(LLGameControl::Device& device); + + void clearAllStates(); + + void accumulateInternalState(); + void computeFinalState(); + void accumulateFinalState(); + void computeSemanticState(LLGameControl::ServerState& state); + + LLGameControl::ActionNameType getActionNameType(const std::string& action) const; + + LLGameControl::AgentActions computeAgentActions(); + void getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out, U32& modifiers_out); + void setExternalInput(U32 action_flags, U32 buttons, bool is_running); + void setMouseCursorPosition(S32 pixel_x, S32 pixel_y, S32 rect_width, S32 rect_height); + + // Rebuild mAxisActionBindings/mButtonActionLabels from the global ModeMappings + // for the currently-active AgentControlMode. Cheap; called on settings change + // and on mode change. Safe to call every frame (early-outs when up to date). + void rebuildActionLookup(bool force = false); + + void clear(); + +private: + // Invert one mode's ModeMappings[Axes|Buttons] (action label -> input name) + // into canonical input index -> action binding/label, merging into + // mAxisActionBindings/mButtonActionLabels ("last write wins" on collision). + // Shared by rebuildActionLookup()'s plain per-mode lookup and its FlyCam/Cursor + // merge (see there). 'mode' is used only for per-mode action validation/Invert + // lookup -- it need not equal g_agentControlMode. + void applyAxisMappings(LLGameControl::AgentControlMode mode, const LLSD& axes); + void applyButtonMappings(const LLSD& buttons); + + + std::list mDevices; // all connected devices + using device_it = std::list::iterator; + device_it findDevice(SDL_JoystickID id) + { + return std::find_if(mDevices.begin(), mDevices.end(), + [id](LLGameControl::Device& device) + { + return device.getJoystickID() == id; + }); + } + + // Keyboard-only contribution (from AGENT_CONTROL_* bits, via setExternalInput()), + // already reduced to the same wire format as g_finalState. Kept separate from + // the controller-only g_controllerState and combined into g_finalState only in + // accumulateFinalState(), so keyboard input never flows through the + // controller's own axis/button pipeline and vice-versa. + LLGameControl::ServerState mExternalServerState; + LLGameControlTranslator mActionTranslator; + std::map mActions; + + // Raw copies of setExternalInput()'s arguments, kept for computeSemanticState()'s + // keyboard fold-in: it maps 'mExternalActionFlags' (AGENT_CONTROL_* bits) directly + // onto the avatar semantic axes, which needs the flags themselves rather than + // mExternalServerState's already-reduced (and mapping-order-agnostic) axes/buttons. + U32 mExternalActionFlags { 0 }; + bool mExternalIsRunning { false }; + + // Fed in via setMouseCursorPosition(); consumed by computeSemanticState() to fill + // CONTROL_MODE_CURSOR's CURSOR_PX/PY/NX/NY slots. See setMouseCursorPosition()'s + // header comment for the coordinate convention. + S32 mMouseCursorPixelX { 0 }; + S32 mMouseCursorPixelY { 0 }; + S32 mMouseCursorRectWidth { 0 }; + S32 mMouseCursorRectHeight { 0 }; + + // Runtime action lookup for the active AgentControlMode, rebuilt from the + // global ModeMappings whenever settings or the active mode change. Each entry + // holds the UI action label (e.g. "Fly up/down") bound to that canonical + // input, or "" if unbound. mAxisActionBindings is indexed by canonical axis + // (KeyboardAxis, NUM_AXES) and also records which half of the action a trigger + // half-axis feeds; mButtonActionLabels by Button (NUM_BUTTONS). + std::vector mAxisActionBindings; + std::vector mButtonActionLabels; + LLGameControl::AgentControlMode mLookupMode { LLGameControl::CONTROL_MODE_NONE }; + + // Whether the lookup currently in mAxisActionBindings/mButtonActionLabels is the + // FlyCam/Cursor merge (see rebuildActionLookup()) rather than mLookupMode's plain + // mapping; part of the "is this still current" check alongside mLookupMode. + bool mLookupCursorOverFlycam { false }; + + U32 mLastActiveFlags { 0 }; + U32 mLastFlycamActionFlags { 0 }; + SDL_JoystickID mlastActiveControllerID { 0 }; + + // Persistent "is running" state, folded into AgentActions::mIsRunning by + // computeAgentActions(): follows how hard the movement axes are pushed, with + // hysteresis so it doesn't flicker at the boundary. + bool mIsAnalogRunning { false }; + + friend class LLGameControl; +}; + +// local globals +namespace +{ + LLGameControl* g_gameControl = nullptr; + LLGameControllerManager g_manager; + + // The GameControlData message is sent via UDP which is lossy, and every message + // packs the complete current state (all canonical axes/buttons and all of the + // active mode's semantic axes/buttons), not just what changed. So we resend + // the last final state a few times and then continue to send it with a geometrically + // expanding period. + // + // To ensure the data eventually arives we resend the last final state forever + // but with an expanding resend period. + constexpr U64 USEC_PER_MSEC = 1000; + constexpr U64 FIRST_RESEND_PERIOD = 100 * USEC_PER_MSEC; // 100 msec, in usec + constexpr U64 RESEND_EXPANSION_RATE = 4; + + constexpr F32 DEFAULT_SERVER_FRAME_RATE = 45.f; // Hz + constexpr F32 MIN_SERVER_FRAME_RATE = 1.f; // Hz: clamp against div-by-near-zero on a stalled/lagged sim + constexpr U64 MIN_RESEND_PERIOD = (U64)(USEC_PER_SEC / DEFAULT_SERVER_FRAME_RATE); + F32 g_serverFrameRate = DEFAULT_SERVER_FRAME_RATE; + + U64 getMinSendPeriod() + { + // We don't want to flood the server faster than it can handle GameControlData events + // so we track its reported frame rate and adjust accordingly. + U64 period = (U64)((F32)(USEC_PER_SEC) / g_serverFrameRate); + return llmax(period, MIN_RESEND_PERIOD); + } + + LLGameControl::State g_innerState; // state from all game controllers + LLGameControl::State g_mappedState; // state after user mapping is applied + LLGameControl::State g_flycamMappedState; // state for flycam after user mapping is applied + LLGameControl::ServerState g_controllerState; // controller-only, pre-keyboard-accumulation + LLGameControl::ServerState g_finalState; // g_controllerState + mExternalServerState (see accumulateFinalState) + + LLTimer g_buttonLevelTimer[LLGameControl::Button::NUM_BUTTONS]; + LLTimer g_axisHeldTimer[LLGameControl::MovementDirection::NUM_MOVE_DIRS]; + S32 g_buttonLevelFrames[LLGameControl::Button::NUM_BUTTONS]; + S32 g_axisHeldFrames[LLGameControl::MovementDirection::NUM_MOVE_DIRS]; + + // g_nextSend is an absolute expiry timestamp (usec, same clock as totalTime()): + // once it's in the past ('g_nextSend < now') the next GameControlData is due. + // Keeping the deadline itself (rather than a last-sent time + period to add at + // check time) means the check is a single comparison; see updateResendPeriod() + // and scheduleImmediateResend() for the only two places it's computed. + U64 g_nextSend = 0; + U64 g_nextResendPeriod = 0; + U8 g_packetNum = 0; // GameControlData's AgentData.Packet sequence number; wraps at 255 + + // Call whenever outgoing state changes: restarts the resend backoff and pulls + // the send deadline up so the change goes out as soon as the min-send-period + // floor allows. Uses std::min (never pushes the deadline later) so that calling + // this every frame during continuous input (e.g. an analog stick held off-center) + // does not repeatedly reset an already-pending near-term deadline out to + // "now + floor" forever -- once the deadline is closer than a full floor period, + // the candidate computed from the (advancing) 'now' is always later than it, + // and std::min leaves the pending deadline alone. + void scheduleImmediateResend() + { + g_nextResendPeriod = 0; + g_nextSend = std::min(g_nextSend, (U64)totalTime() + getMinSendPeriod()); + } + + bool g_sendToServer = false; + LLGameControl::AgentControlMode g_agentControlMode = LLGameControl::CONTROL_MODE_AVATAR; + + // Set alongside g_agentControlMode by LLAgent::updateGameControlMode(); see + // LLGameControl::setFlycamEngaged()'s header comment. + bool g_flycamEngaged = false; + + // g_gameControlSettings is the nested GameControl structure stored under the + // single "GameControl" setting key: global per-mode action mappings plus + // per-device hardware config. See buildDefaultGameControlSettings() below. + LLSD g_gameControlSettings; + + // g_deviceOptions is a map of [guid,deviceOptions] pairs for known devices + // its values are expected to agree with connected device + std::map g_deviceOptions; + + LLGameControl::LoadSettingsFn s_loadSettings; + LLGameControl::SaveSettingsFn s_saveSettings; + std::function s_updateUI; + + std::string SETTING_GAMECONTROL("GameControl"); + + // Keys used within the nested GameControl LLSD structure + // (see buildDefaultGameControlSettings()). + const std::string GC_COMMENT("Comment"); + const std::string GC_SENDTOSERVER("SendDataToServer"); + const std::string GC_DEVICES("Devices"); + const std::string GC_DEFAULT_DEVICE("Default"); + const std::string GC_CONFIG("Config"); + const std::string GC_MODEMAPPINGS("ModeMappings"); + const std::string GC_AXES("Axes"); + const std::string GC_BUTTONS("Buttons"); + // Per-mode axis-action -> Invert(bool) map, sibling of GC_AXES. Buttons have no + // polarity so there is no button counterpart. + const std::string GC_AXES_INVERT("AxesInvert"); + // Per-mode flag gating whether game-control input is converted to that mode's + // actions. When false the mode's mappings are locked and no actions fire. + const std::string GC_ENABLED("Enabled"); + // FlyCam-only flag gating whether Roll input rotates the flycam about its + // forward axis (see LLFlycam::mAllowRoll). Absent/false means Roll is ignored. + const std::string GC_ALLOW_ROLL("AllowRoll"); + // FlyCam-only camera move-speed multiplier (see LLFlycam::mSpeedFactor). + // Absent means the default (1.0, i.e. unscaled) is used. + const std::string GC_SPEED_FACTOR("SpeedFactor"); + // FlyCam-only flag gating whether a centered crosshair is drawn over the + // 3D view while flycam is engaged (see LLViewerWindow::draw()). + // Absent/false means no crosshair is drawn. + const std::string GC_SHOW_CROSSHAIR("ShowCrosshair"); + const std::string GC_MODE_AVATAR("Avatar"); + const std::string GC_MODE_MOUSELOOK("Mouselook"); + const std::string GC_MODE_FLYCAM("FlyCam"); + const std::string GC_MODE_CAPTIVE("Captive"); + const std::string GC_MODE_CURSOR("Cursor"); + + // Symbolic input name for the combined trigger pair, used both as an axis-action + // The two triggers form a single bidirectional axis: left trigger drives the + // negative direction, right trigger the positive. + const std::string INPUT_AXIS_TRIGGERS("AXIS_TRIGGERS"); + +#ifdef TEMPORARILY_DISABLED + std::string ENUM_AGENTCONTROLMODE_FLYCAM("flycam"); + std::string ENUM_AGENTCONTROLMODE_NONE("none"); + + LLGameControl::AgentControlMode convertStringToAgentControlMode(const std::string& mode) + { + if (mode == ENUM_AGENTCONTROLMODE_NONE) + return LLGameControl::CONTROL_MODE_NONE; + if (mode == ENUM_AGENTCONTROLMODE_FLYCAM) + return LLGameControl::CONTROL_MODE_FLYCAM; + // All values except NONE and FLYCAM are treated as default (AVATAR) + return LLGameControl::CONTROL_MODE_AVATAR; + } + + const std::string& convertAgentControlModeToString(LLGameControl::AgentControlMode mode) + { + if (mode == LLGameControl::CONTROL_MODE_NONE) + return ENUM_AGENTCONTROLMODE_NONE; + if (mode == LLGameControl::CONTROL_MODE_FLYCAM) + return ENUM_AGENTCONTROLMODE_FLYCAM; + // All values except NONE and FLYCAM are treated as default (AVATAR) + return LLStringUtil::null; + } +#endif // TEMPORARILY_DISABLED + + const std::string& getDeviceOptionsString(const std::string& guid) + { + const auto& it = g_deviceOptions.find(guid); + return it == g_deviceOptions.end() ? LLStringUtil::null : it->second; + } + + // Map an AgentControlMode to the mode key used in the GameControl LLSD. + // Returns empty for modes without a mapping (e.g. CONTROL_MODE_NONE). + const std::string& modeToString(LLGameControl::AgentControlMode mode) + { + switch (mode) + { + case LLGameControl::CONTROL_MODE_AVATAR: return GC_MODE_AVATAR; + case LLGameControl::CONTROL_MODE_MOUSELOOK: return GC_MODE_MOUSELOOK; + case LLGameControl::CONTROL_MODE_FLYCAM: return GC_MODE_FLYCAM; + case LLGameControl::CONTROL_MODE_CAPTIVE: return GC_MODE_CAPTIVE; + case LLGameControl::CONTROL_MODE_CURSOR: return GC_MODE_CURSOR; + default: return LLStringUtil::null; + } + } + + // Build the default global per-mode mapping structure. Layout: + // { : { "Axes": {action:input...}, "Buttons": {action:input...} } } + // Action keys match the combo-box labels in panel_preferences_game_control.xml; + // input values match the axis_input_selector / button_input_selector values. + LLSD buildDefaultModeMappings() + { + // Axes: action label -> axis input + LLSD avatar_axes; + avatar_axes["Strafe left/right"] = "AXIS_LEFTX"; + avatar_axes["Advance forward/back"] = "AXIS_LEFTY"; + avatar_axes["Turn left/right"] = "AXIS_RIGHTX"; + avatar_axes["Look up/down"] = "AXIS_RIGHTY"; + avatar_axes["Fly up/down"] = "AXIS_TRIGGERS"; + // Unbound by default: every physical axis is already claimed above (the + // trigger pair covers Fly up/down), same as FlyCam's Roll axis below. + avatar_axes["Zoom +/-"] = "AXIS_NONE"; + + // Buttons: action label -> button input + LLSD avatar_buttons; + avatar_buttons["Jump"] = "BUTTON_SOUTH"; + avatar_buttons["Crouch"] = "BUTTON_EAST"; + avatar_buttons["Toggle sit"] = "BUTTON_WEST"; + // TODO: implement Interact feature + //avatar_buttons["Interact"] = "BUTTON_NORTH"; + avatar_buttons["Toggle mouse cursor"] = "BUTTON_SELECT"; + avatar_buttons["Toggle speak"] = "BUTTON_HOME"; + avatar_buttons["Toggle mouselook"] = "BUTTON_START"; + avatar_buttons["Toggle flycam"] = "BUTTON_RIGHT_STICK"; + avatar_buttons["Advance forward"] = "BUTTON_DPAD_UP"; + avatar_buttons["Advance back"] = "BUTTON_DPAD_DOWN"; + avatar_buttons["Strafe left"] = "BUTTON_DPAD_LEFT"; + avatar_buttons["Strafe right"] = "BUTTON_DPAD_RIGHT"; + + // Default per-axis-action Invert flags + LLSD avatar_axes_invert; + avatar_axes_invert["Strafe left/right"] = true; + avatar_axes_invert["Advance forward/back"] = true; + avatar_axes_invert["Turn left/right"] = true; + + // Mouselook shares the avatar action set (movement/look/jump/etc. still + // apply while the camera is in mouselook), but is initialized as its own + // independent copy so its defaults can be tuned separately later. + LLSD mouselook_axes; + mouselook_axes["Strafe left/right"] = "AXIS_LEFTX"; + mouselook_axes["Advance forward/back"] = "AXIS_LEFTY"; + mouselook_axes["Turn left/right"] = "AXIS_RIGHTX"; + mouselook_axes["Look up/down"] = "AXIS_RIGHTY"; + mouselook_axes["Fly up/down"] = "AXIS_TRIGGERS"; + mouselook_axes["Zoom +/-"] = "AXIS_NONE"; + + LLSD mouselook_buttons; + mouselook_buttons["Jump"] = "BUTTON_SOUTH"; + mouselook_buttons["Crouch"] = "BUTTON_EAST"; + mouselook_buttons["Toggle sit"] = "BUTTON_WEST"; + mouselook_buttons["Toggle mouse cursor"] = "BUTTON_SELECT"; + mouselook_buttons["Toggle speak"] = "BUTTON_HOME"; + mouselook_buttons["Mouse click left"] = "BUTTON_LEFT_SHOULDER"; + mouselook_buttons["Mouse click right"] = "BUTTON_RIGHT_SHOULDER"; + mouselook_buttons["Toggle mouselook"] = "BUTTON_START"; + mouselook_buttons["Toggle flycam"] = "BUTTON_RIGHT_STICK"; + // Unbound by default; see the "ESC" entry under flycam_buttons below. + mouselook_buttons["ESC"] = "BUTTON_NONE"; + mouselook_buttons["Advance forward"] = "BUTTON_DPAD_UP"; + mouselook_buttons["Advance back"] = "BUTTON_DPAD_DOWN"; + mouselook_buttons["Strafe left"] = "BUTTON_DPAD_LEFT"; + mouselook_buttons["Strafe right"] = "BUTTON_DPAD_RIGHT"; + + LLSD mouselook_axes_invert; + mouselook_axes_invert["Strafe left/right"] = true; + mouselook_axes_invert["Advance forward/back"] = true; + mouselook_axes_invert["Turn left/right"] = true; + + // Cursor mode defaults are the same as Avatar mode but with the left + // stick repurposed to move the on-screen cursor, and shoulder buttons + // mapped to mouse clicks left and right. + LLSD cursor_axes; + cursor_axes["Mouse left/right"] = "AXIS_LEFTX"; + cursor_axes["Mouse up/down"] = "AXIS_LEFTY"; + cursor_axes["Turn left/right"] = "AXIS_RIGHTX"; + cursor_axes["Look up/down"] = "AXIS_RIGHTY"; + cursor_axes["Fly up/down"] = "AXIS_TRIGGERS"; + + LLSD cursor_buttons; + cursor_buttons["Jump"] = "BUTTON_SOUTH"; + cursor_buttons["Crouch"] = "BUTTON_EAST"; + cursor_buttons["Toggle sit"] = "BUTTON_WEST"; + cursor_buttons["Toggle mouse cursor"] = "BUTTON_SELECT"; + cursor_buttons["Toggle speak"] = "BUTTON_HOME"; + cursor_buttons["Toggle mouselook"] = "BUTTON_START"; + cursor_buttons["Toggle flycam"] = "BUTTON_RIGHT_STICK"; + // Unbound by default; see the "ESC" entry under flycam_buttons below. + cursor_buttons["ESC"] = "BUTTON_NONE"; + cursor_buttons["Mouse click left"] = "BUTTON_LEFT_SHOULDER"; + cursor_buttons["Mouse click right"] = "BUTTON_RIGHT_SHOULDER"; + cursor_buttons["Advance forward"] = "BUTTON_DPAD_UP"; + cursor_buttons["Advance back"] = "BUTTON_DPAD_DOWN"; + cursor_buttons["Strafe left"] = "BUTTON_DPAD_LEFT"; + cursor_buttons["Strafe right"] = "BUTTON_DPAD_RIGHT"; + + LLSD cursor_axes_invert; + // Kept in case the user re-binds Strafe/Advance back onto an axis; matches + // the Avatar/Mouselook default so re-binding doesn't silently feel different. + cursor_axes_invert["Strafe left/right"] = true; + cursor_axes_invert["Advance forward/back"] = true; + cursor_axes_invert["Turn left/right"] = true; + // "Mouse up/down": by the same raw-hardware-vs-target-sign reasoning as + // "Advance forward/back" above (push-up reads as a negative raw deflection, + // same physical axis/convention), invert so pushing the stick up moves the + // cursor up (screen/GL Y increases upward) rather than down. "Mouse left/ + // right" needs no invert: unlike Strafe (which wants left=positive to match + // SL's world frame), cursor X wants right=positive, which is what the raw + // hardware already gives uninverted. NOTE: not yet verified in-world -- + // flip this (or the other axis) if the cursor moves the wrong way on a + // real controller. + cursor_axes_invert["Mouse up/down"] = true; + + // FlyCam adds a Roll axis and uses a distinct button set. + LLSD flycam_axes; + flycam_axes["Truck left/right"] = "AXIS_LEFTX"; + flycam_axes["Dolly forward/back"] = "AXIS_LEFTY"; + flycam_axes["Pan left/right"] = "AXIS_RIGHTX"; + flycam_axes["Tilt up/down"] = "AXIS_RIGHTY"; + flycam_axes["Boom up/down"] = "AXIS_TRIGGERS"; + flycam_axes["Roll left/right"] = "AXIS_NONE"; + + LLSD flycam_buttons; + flycam_buttons["Zoom out"] = "BUTTON_SOUTH"; + flycam_buttons["Zoom in"] = "BUTTON_NORTH"; + flycam_buttons["Toggle mouse cursor"] = "BUTTON_SELECT"; + flycam_buttons["Toggle flycam" ] = "BUTTON_RIGHT_STICK"; + flycam_buttons["Unroll"] = "BUTTON_LEFT_STICK"; + flycam_buttons["Roll CCW"] = "BUTTON_WEST"; + flycam_buttons["Roll CW"] = "BUTTON_EAST"; + flycam_buttons["Orbit"] = "BUTTON_RIGHT_SHOULDER"; + // Unbound by default -- every physical button above is already claimed, + // same reasoning as "Zoom +/-"/"Roll left/right" being unbound axes. + // Resets straight back to Avatar mode (see AVATAR_ACTION_ESCAPE/ + // FLYCAM_ACTION_ESCAPE and LLAgent::resetToAvatarMode()). + flycam_buttons["ESC"] = "BUTTON_NONE"; + + LLSD flycam_axes_invert; + flycam_axes_invert["Truck left/right"] = true; + flycam_axes_invert["Dolly forward/back"] = true; + flycam_axes_invert["Pan left/right"] = true; + + auto makeMode = [](const LLSD& axes, const LLSD& buttons, const LLSD& axes_invert) + { + LLSD mode; + mode[GC_AXES] = axes; + mode[GC_BUTTONS] = buttons; + mode[GC_AXES_INVERT] = axes_invert; + // Conversion for each mode is enabled by default. + mode[GC_ENABLED] = true; + return mode; + }; + + LLSD mappings; + mappings[GC_MODE_AVATAR] = makeMode(avatar_axes, avatar_buttons, avatar_axes_invert); + mappings[GC_MODE_MOUSELOOK] = makeMode(mouselook_axes, mouselook_buttons, mouselook_axes_invert); + mappings[GC_MODE_FLYCAM] = makeMode(flycam_axes, flycam_buttons, flycam_axes_invert); + mappings[GC_MODE_CAPTIVE] = makeMode(avatar_axes, avatar_buttons, avatar_axes_invert); + mappings[GC_MODE_CURSOR] = makeMode(cursor_axes, cursor_buttons, cursor_axes_invert); + return mappings; + } + + // Build the full default GameControl structure: global per-mode action mappings + // plus a single empty "Default" device template. ModeMappings are global (not + // per-device): the Devices layer normalizes hardware to canonical inputs, while + // the global ModeMappings layer binds canonical inputs to logical actions per mode. + LLSD buildDefaultGameControlSettings() + { + LLSD device; + device[GC_CONFIG] = ""; + + LLSD settings; + settings[GC_COMMENT] = "GameControl settings"; + settings[GC_SENDTOSERVER] = false; + settings[GC_MODEMAPPINGS] = buildDefaultModeMappings(); + settings[GC_DEVICES][GC_DEFAULT_DEVICE] = device; + return settings; + } + + // Ensure g_gameControlSettings holds a usable structure; seed from defaults + // if it is empty or malformed. The global ModeMappings block is seeded + // independently so an older config that lacks it still gets sane defaults. + void ensureGameControlSettings() + { + if (!g_gameControlSettings.isMap() || !g_gameControlSettings.has(GC_DEVICES)) + { + g_gameControlSettings = buildDefaultGameControlSettings(); + } + if (!g_gameControlSettings[GC_MODEMAPPINGS].isMap()) + { + g_gameControlSettings[GC_MODEMAPPINGS] = buildDefaultModeMappings(); + } + } + + // Ensure a per-device entry exists before mutating it. Devices now hold only + // their hardware Config; the action mappings live in the global ModeMappings. + void ensureDeviceEntry(const std::string& guid) + { + ensureGameControlSettings(); + LLSD& devices = g_gameControlSettings[GC_DEVICES]; + if (!devices.has(guid)) + { + LLSD device; + device[GC_CONFIG] = ""; + devices[guid] = device; + } + } + +} + +LLGameControl::~LLGameControl() +{ + terminate(); +} + +LLGameControl::State::State() + : + mButtons(0), + mPhysicalButtons(0), + mPrevButtons(0) + +{ + mAxes.resize(NUM_MOVE_DIRS, 0); + mRawAxes.resize(NUM_MOVE_DIRS, 0); + mPrevAxes.resize(NUM_MOVE_DIRS, 0); + mPhysicalRawAxes.resize(NUM_AXES, 0); + mPhysicalFixedAxes.resize(NUM_AXES, 0); +} + +void LLGameControl::State::clear() +{ + std::fill(mAxes.begin(), mAxes.end(), 0); + std::fill(mRawAxes.begin(), mRawAxes.end(), 0); + std::fill(mPhysicalRawAxes.begin(), mPhysicalRawAxes.end(), 0); + std::fill(mPhysicalFixedAxes.begin(), mPhysicalFixedAxes.end(), 0); + mButtons = 0; + mPhysicalButtons = 0; + + // // DO NOT clear mPrevAxes because those are managed by external logic. + // std::fill(mPrevAxes.begin(), mPrevAxes.end(), 0); + // mPrevButtons = 0; +} + +void LLGameControl::State::storePrevious() +{ + mPrevButtons = mButtons; + for(size_t i = 0; i < mAxes.size(); i++) + { + mPrevAxes[i] = mAxes[i]; + } +} + +bool LLGameControl::State::onButton(U8 button, bool pressed) +{ + U32 old_buttons = mButtons; + if (button < NUM_BUTTONS) + { + if (pressed) + { + mButtons |= (0x01 << button); + } + else + { + mButtons &= ~(0x01 << button); + } + } + return mButtons != old_buttons; +} + +LLGameControl::ServerState::ServerState() +: mButtons(0) +, mPrevButtons(0) +, mSemanticButtons(0) +, mPrevSemanticButtons(0) +, mActionMode(LLGameControl::CONTROL_MODE_AVATAR) +, mPrevActionMode(LLGameControl::CONTROL_MODE_AVATAR) +{ + mAxes.resize(NUM_AXES,0); + mPrevAxes.resize(NUM_AXES,0); + mSemanticAxes.resize(NUM_SEMANTIC_SLOTS,0); + mPrevSemanticAxes.resize(NUM_SEMANTIC_SLOTS,0); +} + +void LLGameControl::ServerState::clear() +{ + std::fill(mAxes.begin(), mAxes.end(), 0); + mButtons = 0; + std::fill(mSemanticAxes.begin(), mSemanticAxes.end(), 0); + mSemanticButtons = 0; + + // // DO NOT clear mPrevAxes/mPrevSemanticAxes because those are managed by external logic. + // std::fill(mPrevAxes.begin(), mPrevAxes.end(), 0); + // mPrevButtons = 0; +} + +LLGameControl::Device::Device(int joystickID, const std::string& guid, const std::string& name) +: mJoystickID(joystickID) +, mGUID(guid) +, mName(name) +{ +} + +S16 LLGameControl::Options::AxisOptions::computeModifiedValue(S16 raw_value) const +{ + S16 new_value = (S16)(std::clamp(((S32)raw_value + S32(mOffset)) * mMultiplier, -32768, 32767)); + if (abs(new_value) < mDeadZone) + { + new_value = 0; + } + return new_value; +} + +void LLGameControl::Options::AxisOptions::resetToDefaults() +{ + mMultiplier = 1; + mDeadZone = DEFAULT_AXIS_DEAD_ZONE; + mOffset = 0; +} + +LLGameControl::Options::Options() +{ + mAxisOptions.resize(NUM_AXES); + mAxisMap.resize(NUM_AXES); + mButtonMap.resize(NUM_BUTTONS); + resetToDefaults(); +} + +void LLGameControl::Options::resetToDefaults() +{ + for (size_t i = 0; i < NUM_AXES; ++i) + { + mAxisOptions[i].resetToDefaults(); + mAxisMap[i] = (U8)i; + } + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + mButtonMap[i] = (U8)i; + } +} + +U8 LLGameControl::Options::mapAxis(U8 axis) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL3") << "Invalid input axis: " << axis << LL_ENDL; + return axis; + } + return mAxisMap[axis]; +} + +U8 LLGameControl::Options::mapButton(U8 button) const +{ + if (button >= NUM_BUTTONS) + { + LL_WARNS("SDL3") << "Invalid input button: " << button << LL_ENDL; + return button; + } + return mButtonMap[button]; +} + +U8 LLGameControl::Options::unmapAxis(U8 axis) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL3") << "Invalid unmap axis: " << axis << LL_ENDL; + return axis; + } + for(size_t i=0; i < mAxisMap.size(); i++) + { + if(mAxisMap[i] == axis) + { + return (U8)i; + } + } + return axis; +} + +U8 LLGameControl::Options::unmapButton(U8 button) const +{ + + if (button >= NUM_BUTTONS) + { + LL_WARNS("SDL3") << "Invalid unmap button: " << button << LL_ENDL; + return button; + } + + for(size_t i=0; i < mButtonMap.size(); i++) + { + if(mButtonMap[i] == button) + { + return (U8)i; + } + } + return button; +} + +S16 LLGameControl::Options::fixAxisValue(U8 axis, S16 value) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL3") << "Invalid input axis: " << axis << LL_ENDL; + } + else + { + value = mAxisOptions[axis].computeModifiedValue(value); + } + return value; +} + +std::string LLGameControl::Options::AxisOptions::saveToString() const +{ + std::list options; + + if (mMultiplier == -1) + { + options.push_back("invert:1"); + } + if (mDeadZone != DEFAULT_AXIS_DEAD_ZONE) + { + options.push_back(llformat("dead_zone:%u", mDeadZone)); + } + if (mOffset) + { + options.push_back(llformat("offset:%d", mOffset)); + } + + std::string result = LLStringUtil::join(options); + + return result.empty() ? result : "{" + result + "}"; +} + +// Parse string "{key:value,key:{key:value,key:value}}" and fill the map +static bool parse(std::map& result, std::string source) +{ + result.clear(); + + LLStringUtil::trim(source); + if (source.empty()) + return true; + + if (source.front() != '{' || source.back() != '}') + return false; + + source = source.substr(1, source.size() - 2); + + LLStringUtil::trim(source); + if (source.empty()) + return true; + + // Split the string "key:value" and add the pair to the map + auto split = [&](const std::string& pair) -> bool + { + size_t pos = pair.find(':'); + if (!pos || pos == std::string::npos) + return false; + std::string key = pair.substr(0, pos); + std::string value = pair.substr(pos + 1); + LLStringUtil::trim(key); + LLStringUtil::trim(value); + if (key.empty() || value.empty()) + return false; + result[key] = value; + return true; + }; + + U32 depth = 0; + size_t offset = 0; + while (true) + { + size_t pos = source.find_first_of(depth ? "{}" : ",{}", offset); + if (pos == std::string::npos) + { + return !depth && split(source); + } + if (source[pos] == ',') + { + if (!split(source.substr(0, pos))) + return false; + source = source.substr(pos + 1); + offset = 0; + } + else if (source[pos] == '{') + { + depth++; + offset = pos + 1; + } + else if (depth) // Assume '}' here + { + depth--; + offset = pos + 1; + } + else + { + return false; // Extra '}' found + } + } + + return true; +} + +void LLGameControl::Options::AxisOptions::loadFromString(std::string options) +{ + resetToDefaults(); + + if (options.empty()) + return; + + std::map pairs; + if (!parse(pairs, options)) + { + LL_WARNS("SDL3") << "Invalid axis options: '" << options << "'" << LL_ENDL; + } + + mMultiplier = 1; + std::string invert = pairs["invert"]; + if (!invert.empty()) + { + if (invert == "1") + { + mMultiplier = -1; + } + else + { + LL_WARNS("SDL3") << "Invalid invert value: '" << invert << "'" << LL_ENDL; + } + } + + std::string dead_zone = pairs["dead_zone"]; + if (!dead_zone.empty()) + { + U32 number; + if (LLStringUtil::convertToU32(dead_zone, number) && number <= MAX_AXIS_DEAD_ZONE + && std::to_string(number) == dead_zone) + { + mDeadZone = (U16)number; + } + else + { + LL_WARNS("SDL3") << "Invalid dead_zone value: '" << dead_zone << "'" << LL_ENDL; + } + } + + std::string offset = pairs["offset"]; + if (!offset.empty()) + { + S32 number; + if (!LLStringUtil::convertToS32(offset, number) || abs(number) > MAX_AXIS_OFFSET + || std::to_string(number) != offset) + { + LL_WARNS("SDL3") << "Invalid offset value: '" << offset << "'" << LL_ENDL; + } + else + { + mOffset = (S16)number; + } + } +} + +std::string LLGameControl::Options::saveToString(const std::string& name, bool force_empty) const +{ + return stringifyDeviceOptions(name, mAxisOptions, mAxisMap, mButtonMap, force_empty); +} + +bool LLGameControl::Options::loadFromString(std::string& name, std::string options) +{ + resetToDefaults(); + return LLGameControl::parseDeviceOptions(options, name, mAxisOptions, mAxisMap, mButtonMap); +} + +bool LLGameControl::Options::loadFromString(std::string options) +{ + resetToDefaults(); + std::string dummy_name; + return LLGameControl::parseDeviceOptions(options, dummy_name, mAxisOptions, mAxisMap, mButtonMap); +} + +LLGameControllerManager::LLGameControllerManager() +{ +} + +void LLGameControllerManager::resetDeviceOptionsToDefaults() +{ + for (LLGameControl::Device& device : mDevices) + { + device.resetOptionsToDefaults(); + } +} + +void LLGameControllerManager::applyRememberedDeviceOptions() +{ + for (LLGameControl::Device& device : mDevices) + { + device.loadOptionsFromString(getDeviceOptionsString(device.getGUID())); + } +} + +void LLGameControllerManager::rememberDeviceOptions() const +{ + for (const LLGameControl::Device& device : mDevices) + { + std::string options = device.saveOptionsToString(); + if (options.empty()) + { + g_deviceOptions.erase(device.getGUID()); + } + else + { + g_deviceOptions[device.getGUID()] = options; + } + } +} + +void LLGameControllerManager::setDeviceOptions(const std::string& guid, const LLGameControl::Options& options) +{ + for (LLGameControl::Device& device : mDevices) + { + if (device.getGUID() == guid) + { + device.mOptions = options; + recomputeDeviceAxesFromRaw(device); + + // remember the options + std::string options_str = device.saveOptionsToString(true); + auto itr = g_deviceOptions.find(guid); + if (itr == g_deviceOptions.end()) + { + g_deviceOptions.insert({guid, options_str}); + } + else + { + itr->second = options_str; + } + return; + } + } +} + +void LLGameControllerManager::addController(SDL_JoystickID id, const std::string& guid, const std::string& name) +{ + llassert(id >= 0); + + for (const LLGameControl::Device& device : mDevices) + { + if (device.getJoystickID() == id) + { + LL_WARNS("SDL3") << "device with id=" << id << " was already added" + << ", guid: '" << device.getGUID() << "'" + << ", name: '" << device.getName() << "'" + << LL_ENDL; + return; + } + } + + mDevices.emplace_back(id, guid, name).loadOptionsFromString(getDeviceOptionsString(guid)); +} + +void LLGameControllerManager::removeController(SDL_JoystickID id) +{ + LL_INFOS("SDL3") << "joystick id: " << id << LL_ENDL; + + mDevices.remove_if([id](LLGameControl::Device& device) + { + return device.getJoystickID() == id; + }); +} + +const LLGameControl::Device* LLGameControllerManager::getLastActiveDevice() const +{ + if (mDevices.empty()) + { + return nullptr; + } + + if (mlastActiveControllerID != 0) + { + auto it = std::find_if(mDevices.begin(), mDevices.end(), + [this](const LLGameControl::Device& device) + { + return device.getJoystickID() == mlastActiveControllerID; + }); + if (it != mDevices.end()) + { + return &(*it); + } + } + + return &mDevices.front(); +} + +// Splits a signed axis value into the +/- half-axis pair used by State::mAxes / +// State::mRawAxes: the positive magnitude lands in half_axes[base], the negative +// magnitude in half_axes[base + 1]. +static void storeHalfAxes(std::vector& half_axes, U8 base, S16 value) +{ + half_axes[base] = 0; + half_axes[base + 1] = 0; + if (value > 0) + { + half_axes[base] = value; + } + else + { + half_axes[base + 1] = (U16)abs(value); + } +} + +// Writes a single half-axis slot to a magnitude without disturbing its sibling. +// Used when two physical axes each own one half of the same canonical axis (the +// "Triggers left/right" merge) so their contributions do not clobber each other. +static void storeOneHalf(std::vector& half_axes, U8 slot, S16 value) +{ + half_axes[slot] = (U16)std::max(0, value); +} + +// Fans a single bidirectional axis value across the canonical trigger channels: +// the negative part drives the LEFT_TRIGGER channel, the positive part the +// RIGHT_TRIGGER channel. Triggers are positive-only, so each magnitude lands in +// its channel's positive half and the unused negative halves are cleared. +static void storeTriggerPairFanout(std::vector& half_axes, S16 value) +{ + constexpr U8 LT = LLGameControl::AXIS_LEFT_TRIGGER; + constexpr U8 RT = LLGameControl::AXIS_RIGHT_TRIGGER; + half_axes[RT * 2] = value > 0 ? (U16)value : 0; + half_axes[RT * 2 + 1] = 0; + half_axes[LT * 2] = value < 0 ? (U16)abs((S32)value) : 0; + half_axes[LT * 2 + 1] = 0; +} + +// Routes a (fixed, sign-corrected) axis value into the canonical half-axis slots of +// 'state' according to the physical axis' output code 'out': +// - a specific canonical axis: split by sign into that axis' two halves (1:1); +// - AXIS_OUTPUT_TRIGGER_PAIR: fan a bidirectional axis across the trigger channels +// (negative -> LEFT_TRIGGER, positive -> RIGHT_TRIGGER); +// - a stick canonical axis fed by a physical trigger: merge into one half of it +// (left trigger -> negative half, right trigger -> positive half). +// 'phys' is the physical axis index; 'phys_is_trigger' whether it is a trigger. +static void routeAxisValue(LLGameControl::State& state, U8 phys, bool phys_is_trigger, + U8 out, S16 value, S16 raw_value) +{ + constexpr U8 LT = LLGameControl::AXIS_LEFT_TRIGGER; + + if (out == LLGameControl::AXIS_OUTPUT_NONE) + { + return; // physical axis disabled: contributes nothing + } + + if (out == LLGameControl::AXIS_OUTPUT_TRIGGER_PAIR) + { + // Fan-out: a single bidirectional axis drives the whole trigger pair, so this + // physical axis owns both trigger channels and clears their unused halves. + storeTriggerPairFanout(state.mAxes, value); + storeTriggerPairFanout(state.mRawAxes, raw_value); + return; + } + + if (phys_is_trigger && out < LT) + { + // Merge: a physical trigger feeds one half of a bidirectional (stick) axis. + // Left trigger -> negative half, right trigger -> positive half. Write only + // our half, using the press magnitude so the two triggers do not clobber each + // other; inversion is not meaningful here (the half is fixed by which trigger). + U8 slot = (phys == LT) ? (U8)(out * 2 + 1) : (U8)(out * 2); + storeOneHalf(state.mAxes, slot, (S16)abs((S32)value)); + storeOneHalf(state.mRawAxes, slot, (S16)abs((S32)raw_value)); + return; + } + + // Normal 1:1 mapping: split the signed value into the canonical axis' halves. + storeHalfAxes(state.mAxes, (U8)(out * 2), value); + storeHalfAxes(state.mRawAxes, (U8)(out * 2), raw_value); +} + +void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 raw_value) +{ + device_it it = findDevice(id); + if (it == mDevices.end()) + { + LL_WARNS("SDL3") << "Unknown device: joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)axis + << " value=" << (S32)raw_value << LL_ENDL; + return; + } + + // 'axis' is the physical axis index for the remainder of this function; the + // hardware fix below is keyed on it so it describes the physical sensor, then + // the output code routes the result to canonical slots. + U8 phys = axis; + if (phys >= LLGameControl::NUM_AXES) + { + LL_WARNS("SDL3") << "Unknown axis: joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)(phys) + << " value=" << (S32)(raw_value) << LL_ENDL; + return; + } + + // The output code says where this physical axis goes: a single canonical axis, + // the trigger pair (fan-out), or None. + U8 out = it->mOptions.mapAxis(phys); + + // Note: raw_value is left as SDL reports it (no implicit sign flip here) -- + // any needed correction for SL's local right-handed reference frame (e.g. the + // RAW analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD) is instead + // expressed as a per-mode, per-action Invert flag (see getAxisInvert), applied + // once the value reaches its bound action in computeAgentActions()/getFlycamInputs(). + bool phys_is_trigger = phys >= LLGameControl::AXIS_LEFT_TRIGGER; + + S16 fixed_value = it->mOptions.fixAxisValue(phys, raw_value); + LL_DEBUGS("SDL3") << "joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)(phys) + << " raw_value=" << (S32)(raw_value) + << " fixed_value=" << (S32)(fixed_value) << LL_ENDL; + + // Preserve the pre-map values keyed by physical axis for the preferences UI. + // routeAxisValue() below re-keys the state axes by canonical output axis, which + // loses 'phys'; the Device-State tab's per-physical-axis rows need it. Stored + // unconditionally (even when 'out' is disabled) so a row still shows its reading. + it->mState.mPhysicalRawAxes[phys] = raw_value; + it->mState.mPhysicalFixedAxes[phys] = fixed_value; + + routeAxisValue(it->mState, phys, phys_is_trigger, out, fixed_value, raw_value); +} + +void LLGameControllerManager::recomputeDeviceAxesFromRaw(LLGameControl::Device& device) +{ + for (U8 phys = 0; phys < LLGameControl::NUM_AXES; ++phys) + { + S16 raw_value = device.mState.mPhysicalRawAxes[phys]; + U8 out = device.mOptions.mapAxis(phys); + bool phys_is_trigger = phys >= LLGameControl::AXIS_LEFT_TRIGGER; + S16 fixed_value = device.mOptions.fixAxisValue(phys, raw_value); + device.mState.mPhysicalFixedAxes[phys] = fixed_value; + routeAxisValue(device.mState, phys, phys_is_trigger, out, fixed_value, raw_value); + } +} + +void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool pressed) +{ + device_it it = findDevice(id); + if (it == mDevices.end()) + { + LL_WARNS("SDL3") << "Unknown device: joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button << LL_ENDL; + return; + } + + mlastActiveControllerID = id; + + // 'button' is the physical button index here; preserve its pressed state keyed by + // that index for the preferences Device-State tab (whose rows are physical buttons) + // before mapButton() below re-keys it to the canonical button for the input pipeline. + if (button < LLGameControl::NUM_BUTTONS) + { + if (pressed) + { + it->mState.mPhysicalButtons |= (0x01 << button); + } + else + { + it->mState.mPhysicalButtons &= ~(0x01 << button); + } + } + + // Map button using device-specific settings + // or leave the value unchanged + U8 mapped_button = it->mOptions.mapButton(button); + if (mapped_button != button) + { + LL_DEBUGS("SDL3") << "Button mapped: joystick=0x" << std::hex << id << std::dec + << " input button i=" << (S32)button + << " mapped button i=" << (S32)mapped_button << LL_ENDL; + button = mapped_button; + } + + if (button >= LLGameControl::NUM_BUTTONS) + { + LL_WARNS("SDL3") << "Unknown button: joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button << LL_ENDL; + return; + } + + if (it->mState.onButton(button, pressed)) + { + LL_DEBUGS("SDL3") << "joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button + << " pressed=" << pressed << LL_ENDL; + } +} + +void LLGameControllerManager::clearAllStates() +{ + for (auto& device : mDevices) + { + device.mState.clear(); + } + mExternalServerState.clear(); + mLastActiveFlags = 0; + mLastFlycamActionFlags = 0; +} + +void LLGameControllerManager::accumulateInternalState() +{ + g_innerState.storePrevious(); + // clear the old state + g_innerState.clear(); + + + // accumulate the controllers + for (const auto& device : mDevices) + { + g_innerState.mButtons |= device.mState.mButtons; + for(U8 i = 0; i < LLGameControl::Button::NUM_BUTTONS; i++) + { + U32 button = 1 << i; + if(g_innerState.mButtons & button && ~(g_innerState.mPrevButtons) & button) + { + g_buttonLevelTimer[i].reset(); + g_buttonLevelFrames[i] = 0; + } + else + { + g_buttonLevelFrames[i]++; + } + } + for (size_t i = 0; i < LLGameControl::NUM_MOVE_DIRS; ++i) + { + // Note: we don't bother to clamp the axes yet + // because at this stage we haven't yet accumulated the "inner" state. + if (device.mState.mAxes[i] > g_innerState.mAxes[i]) + { + g_innerState.mAxes[i] = (S32)device.mState.mAxes[i]; + if(g_innerState.mPrevAxes[i] < 1) + { + g_axisHeldTimer[i].reset(); + g_axisHeldFrames[i] = 0; + } + else + { + g_axisHeldFrames[i]++; + } + } + } + } +} + +F32 LLGameControl::getControllerHeldTime(ActionType actionType, U8 action) +{ + switch (actionType) + { + case ActionType::DOF: + return g_axisHeldTimer[action].getElapsedTimeF32(); + case ActionType::BUTTON: + return g_buttonLevelTimer[action].getElapsedTimeF32(); + default: + return 0.0; + } +} + +S32 LLGameControl::getControllerHeldFrames(ActionType actionType, U8 action) +{ + switch (actionType) + { + case ActionType::DOF: + return g_axisHeldFrames[action]; + case ActionType::BUTTON: + return g_buttonLevelFrames[action]; + default: + return 0; + } +} + +void LLGameControllerManager::computeFinalState() +{ + static const LLGameControlTranslator::ControllerMappings axis_mappings = { + // Axes + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_STRAFE_LEFT}, LLGameControl::MovementDirection::MOVE_DIR_STRAFE_LEFT}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_STRAFE_RIGHT}, LLGameControl::MovementDirection::MOVE_DIR_STRAFE_RIGHT}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_ADVANCE}, LLGameControl::MovementDirection::MOVE_DIR_ADVANCE}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_RETREAT}, LLGameControl::MovementDirection::MOVE_DIR_RETREAT}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_TURN_LEFT}, LLGameControl::MovementDirection::MOVE_DIR_TURN_LEFT}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_TURN_RIGHT}, LLGameControl::MovementDirection::MOVE_DIR_TURN_RIGHT}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_LOOK_UP}, LLGameControl::MovementDirection::MOVE_DIR_LOOK_UP}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_LOOK_DOWN}, LLGameControl::MovementDirection::MOVE_DIR_LOOK_DOWN}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_RISE_UP}, LLGameControl::MovementDirection::MOVE_DIR_RISE_UP}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_DROP_DOWN}, LLGameControl::MovementDirection::MOVE_DIR_DROP_DOWN}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_ROLL_LEFT}, LLGameControl::MovementDirection::MOVE_DIR_ROLL_LEFT}, + {{LLGameControl::ActionType::DOF, LLGameControl::MovementDirection::MOVE_DIR_ROLL_RIGHT}, LLGameControl::MovementDirection::MOVE_DIR_ROLL_RIGHT} + }; + + static const LLGameControlTranslator::ControllerMappings button_mappings = { + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_SOUTH}, (U32)1 << LLGameControl::Button::BUTTON_SOUTH}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_EAST}, (U32)1 << LLGameControl::Button::BUTTON_EAST}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_WEST}, (U32)1 << LLGameControl::Button::BUTTON_WEST}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_NORTH}, (U32)1 << LLGameControl::Button::BUTTON_NORTH}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_SELECT}, (U32)1 << LLGameControl::Button::BUTTON_SELECT}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_HOME}, (U32)1 << LLGameControl::Button::BUTTON_HOME}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_START}, (U32)1 << LLGameControl::Button::BUTTON_START}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_LEFT_STICK}, (U32)1 << LLGameControl::Button::BUTTON_LEFT_STICK}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_RIGHT_STICK}, (U32)1 << LLGameControl::Button::BUTTON_RIGHT_STICK}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_LEFT_SHOULDER}, (U32)1 << LLGameControl::Button::BUTTON_LEFT_SHOULDER}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_RIGHT_SHOULDER}, (U32)1 << LLGameControl::Button::BUTTON_RIGHT_SHOULDER}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_DPAD_UP}, (U32)1 << LLGameControl::Button::BUTTON_DPAD_UP}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_DPAD_DOWN}, (U32)1 << LLGameControl::Button::BUTTON_DPAD_DOWN}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_DPAD_LEFT}, (U32)1 << LLGameControl::Button::BUTTON_DPAD_LEFT}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_DPAD_RIGHT}, (U32)1 << LLGameControl::Button::BUTTON_DPAD_RIGHT}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_MISC1}, (U32)1 << LLGameControl::Button::BUTTON_MISC1}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_PADDLE1}, (U32)1 << LLGameControl::Button::BUTTON_PADDLE1}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_PADDLE2}, (U32)1 << LLGameControl::Button::BUTTON_PADDLE2}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_PADDLE3}, (U32)1 << LLGameControl::Button::BUTTON_PADDLE3}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_PADDLE4}, (U32)1 << LLGameControl::Button::BUTTON_PADDLE4}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_TOUCHPAD}, (U32)1 << LLGameControl::Button::BUTTON_TOUCHPAD}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_21}, (U32)1 << LLGameControl::Button::BUTTON_21}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_22}, (U32)1 << LLGameControl::Button::BUTTON_22}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_23}, (U32)1 << LLGameControl::Button::BUTTON_23}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_24}, (U32)1 << LLGameControl::Button::BUTTON_24}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_25}, (U32)1 << LLGameControl::Button::BUTTON_25}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_26}, (U32)1 << LLGameControl::Button::BUTTON_26}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_27}, (U32)1 << LLGameControl::Button::BUTTON_27}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_28}, (U32)1 << LLGameControl::Button::BUTTON_28}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_29}, (U32)1 << LLGameControl::Button::BUTTON_29}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_30}, (U32)1 << LLGameControl::Button::BUTTON_30}, + {{LLGameControl::ActionType::BUTTON, LLGameControl::Button::BUTTON_31}, (U32)1 << LLGameControl::Button::BUTTON_31} + }; + + // We assume accumulateInternalState() has already been called. This + // computes the CONTROLLER-ONLY state into g_controllerState -- no keyboard + // (mExternalServerState) contribution here -- so that a real controller's + // input can never flow through agent control flags and back into itself. + // The keyboard contribution is combined in afterward, exclusively by + // accumulateFinalState() below. + g_mappedState.clear(); + g_mappedState.mButtons = mActionTranslator.calculateTranslatedButtons(button_mappings, g_innerState); + + mActionTranslator.calculateTranslatedAxes(axis_mappings, g_innerState, g_mappedState.mAxes); + + g_controllerState.mButtons = g_mappedState.mButtons; + + size_t j = 0; + // clamp the accumulated axes + for(size_t i = 0; i < LLGameControl::NUM_MOVE_DIRS; i+=2) + { + // Accumulate in S32 to avoid overflow + S32 axis_pos = g_mappedState.mAxes[i]; + S32 axis_neg = g_mappedState.mAxes[i+1]; + // Note: g_mappedState uses NUM_MOVE_DIRS split half-axes (indexed by 'i'), + // while g_controllerState uses NUM_AXES combined signed axes (indexed by 'j'). + g_controllerState.mAxes[j] = (S16)std::clamp(axis_pos - axis_neg, -32768, 32767); + ++j; + } + + accumulateFinalState(); +} + +void LLGameControllerManager::accumulateFinalState() +{ + // This is the only place controller state (g_controllerState, real hardware) + // and keyboard state (mExternalServerState, from AGENT_CONTROL_* bits -- see + // setExternalInput()) are combined: into g_finalState, the single + // ServerState actually packed into the outgoing GameControlData message. + // Buttons OR together; axes sum (clamped to the wire's signed range) so + // simultaneous keyboard + controller input on the same axis combines + // rather than either one masking the other. + U32 old_buttons = g_finalState.mButtons; + U32 new_buttons = g_controllerState.mButtons | mExternalServerState.mButtons; + g_finalState.mPrevButtons = old_buttons; + g_finalState.mButtons = new_buttons; + if (old_buttons != new_buttons) + { + scheduleImmediateResend(); // packet needs to go out ASAP + } + + for (size_t j = 0; j < LLGameControl::NUM_AXES; ++j) + { + S32 combined = (S32)g_controllerState.mAxes[j] + (S32)mExternalServerState.mAxes[j]; + S16 axis = (S16)std::clamp(combined, -32768, 32767); + if (g_finalState.mAxes[j] != axis) + { + // When axis changes we explicitly update the corresponding prevAxis + // prior to storing axis. The only other place where prevAxis + // is updated is updateResendPeriod() which is explicitly called after + // a packet is sent. The result is: unchanged axes are included in + // first resend but not later ones. + g_finalState.mPrevAxes[j] = g_finalState.mAxes[j]; + g_finalState.mAxes[j] = axis; + scheduleImmediateResend(); // packet needs to go out ASAP + } + } + + computeSemanticState(g_finalState); +} + +LLGameControl::ActionNameType LLGameControllerManager::getActionNameType(const std::string& action) const +{ + auto it = mActions.find(action); + return it == mActions.end() ? LLGameControl::ACTION_NAME_UNKNOWN : it->second; +} + +namespace +{ + // Resolve a symbolic input name as stored in ModeMappings ("AXIS_LEFTX", + // "BUTTON_SOUTH", "AXIS_NONE", ...) to a canonical InputChannel. This is the + // inverse of InputChannel::getRemoteName(); axis channels are returned with + // sign 0 (bidirectional) because ModeMappings do not store a sign. Anything + // unrecognized (including the "none" sentinels) yields an isNone() channel. + LLGameControl::InputChannel channelFromInputName(const std::string& name) + { + static const std::map s_axis_names = { + { "AXIS_LEFTX", LLGameControl::AXIS_LEFTX }, + { "AXIS_LEFTY", LLGameControl::AXIS_LEFTY }, + { "AXIS_RIGHTX", LLGameControl::AXIS_RIGHTX }, + { "AXIS_RIGHTY", LLGameControl::AXIS_RIGHTY }, + { "AXIS_LEFT_TRIGGER", LLGameControl::AXIS_LEFT_TRIGGER }, + { "AXIS_RIGHT_TRIGGER", LLGameControl::AXIS_RIGHT_TRIGGER }, + }; + static const std::map s_button_names = { + { "BUTTON_SOUTH", LLGameControl::BUTTON_SOUTH }, + { "BUTTON_EAST", LLGameControl::BUTTON_EAST }, + { "BUTTON_WEST", LLGameControl::BUTTON_WEST }, + { "BUTTON_NORTH", LLGameControl::BUTTON_NORTH }, + { "BUTTON_SELECT", LLGameControl::BUTTON_SELECT }, + { "BUTTON_HOME", LLGameControl::BUTTON_HOME }, + { "BUTTON_START", LLGameControl::BUTTON_START }, + { "BUTTON_LEFT_STICK", LLGameControl::BUTTON_LEFT_STICK }, + { "BUTTON_RIGHT_STICK", LLGameControl::BUTTON_RIGHT_STICK }, + { "BUTTON_LEFT_SHOULDER", LLGameControl::BUTTON_LEFT_SHOULDER }, + { "BUTTON_RIGHT_SHOULDER", LLGameControl::BUTTON_RIGHT_SHOULDER }, + { "BUTTON_DPAD_UP", LLGameControl::BUTTON_DPAD_UP }, + { "BUTTON_DPAD_DOWN", LLGameControl::BUTTON_DPAD_DOWN }, + { "BUTTON_DPAD_LEFT", LLGameControl::BUTTON_DPAD_LEFT }, + { "BUTTON_DPAD_RIGHT", LLGameControl::BUTTON_DPAD_RIGHT }, + { "BUTTON_MISC1", LLGameControl::BUTTON_MISC1 }, + { "BUTTON_PADDLE1", LLGameControl::BUTTON_PADDLE1 }, + { "BUTTON_PADDLE2", LLGameControl::BUTTON_PADDLE2 }, + { "BUTTON_PADDLE3", LLGameControl::BUTTON_PADDLE3 }, + { "BUTTON_PADDLE4", LLGameControl::BUTTON_PADDLE4 }, + { "BUTTON_TOUCHPAD", LLGameControl::BUTTON_TOUCHPAD }, + }; + + auto ait = s_axis_names.find(name); + if (ait != s_axis_names.end()) + { + return LLGameControl::InputChannel(LLGameControl::InputChannel::TYPE_AXIS, ait->second, 0); + } + auto bit = s_button_names.find(name); + if (bit != s_button_names.end()) + { + return LLGameControl::InputChannel(LLGameControl::InputChannel::TYPE_BUTTON, bit->second); + } + // Numeric fallback for the higher buttons (e.g. "BUTTON_21"). + if (LLStringUtil::startsWith(name, "BUTTON_")) + { + S32 index = atoi(name.substr(7).c_str()); + if (index > 0 && index < LLGameControl::NUM_BUTTONS) + { + return LLGameControl::InputChannel(LLGameControl::InputChannel::TYPE_BUTTON, (U8)index); + } + } + return LLGameControl::InputChannel(); // isNone() + } + + // Step-0 bridge (approach A): UI action label -> engine effect for the + // Avatar/Mouselook/Captive modes. Analog axis labels expand to a positive-half and + // negative-half AGENT_CONTROL bit; when an axis is bound to the trigger pair the + // per-axis binding half (see AxisActionBinding) selects which of these two bits a + // given trigger drives. Button labels expand to a single AGENT_CONTROL bit via + // avatarButtonBridge(), except the one-shot commands in avatarMiscButtonBridge() + // (below), which are non-flag actions carried via AgentActions::mMiscActionBits + // and processed by LLAgent::applyExternalActions. + // TODO: implement "Interact" action. + struct AxisActionEffect { U32 posFlag; U32 negFlag; }; + + const std::map& avatarAxisBridge() + { + // Note: g_innerState half-axes are pre-negated so LEFT/FORWARD/UP land in + // the positive half (axis*2); see LLGameControllerManager::onAxis(). + static const std::map bridge = { + { "Strafe left/right", { AGENT_CONTROL_LEFT_POS, AGENT_CONTROL_LEFT_NEG } }, + { "Advance forward/back", { AGENT_CONTROL_AT_POS, AGENT_CONTROL_AT_NEG } }, + { "Turn left/right", { AGENT_CONTROL_YAW_POS, AGENT_CONTROL_YAW_NEG } }, + { "Look up/down", { AGENT_CONTROL_PITCH_POS, AGENT_CONTROL_PITCH_NEG } }, + { "Fly up/down", { AGENT_CONTROL_UP_POS, AGENT_CONTROL_UP_NEG } }, + }; + return bridge; + } + + const std::map& avatarButtonBridge() + { + static const std::map bridge = { + { "Jump", AGENT_CONTROL_UP_POS }, + { "Crouch", AGENT_CONTROL_UP_NEG }, + { "Fly up", AGENT_CONTROL_UP_POS }, + { "Fly down", AGENT_CONTROL_UP_NEG }, + { "Advance forward", AGENT_CONTROL_AT_POS }, + { "Advance back", AGENT_CONTROL_AT_NEG }, + { "Strafe left", AGENT_CONTROL_LEFT_POS }, + { "Strafe right", AGENT_CONTROL_LEFT_NEG }, + { "Turn left", AGENT_CONTROL_YAW_POS }, + { "Turn right", AGENT_CONTROL_YAW_NEG }, + { "Look up", AGENT_CONTROL_PITCH_POS }, + { "Look down", AGENT_CONTROL_PITCH_NEG }, + // HACK bits consumed by LLAgent::applyExternalActions for toggles. + //{ "Toggle fly", AGENT_CONTROL_FLY }, // HACK + //{ "Toggle flycam", AGENT_CONTROL_NUDGE_AT_NEG }, // HACK + /* + // Real protocol bits: LLAgent::applyExternalActions gates these on + // avatar state (only sits down when standing on the ground, only stands + // up when sitting) so holding the button while the action doesn't apply + // is inert. + { "Sit down", AGENT_CONTROL_SIT_DOWN }, + { "Stand up", AGENT_CONTROL_STAND_UP }, + */ + }; + return bridge; + } + + // Avatar/Mouselook/Captive/Cursor-mode button label -> one-shot AvatarMiscAction bit. + // These don't correspond to an AGENT_CONTROL_* flag, so they're kept in a bridge + // separate from avatarButtonBridge() and edge-triggered (not-pressed -> + // pressed) by computeAgentActions() into AgentActions::mMiscActionBits, + // rather than OR'd every frame the button is held like a movement flag. + const std::map& avatarMiscButtonBridge() + { + static const std::map bridge = { + { "Toggle fly", LLGameControl::AVATAR_ACTION_TOGGLE_FLY }, + { "Toggle sit", LLGameControl::AVATAR_ACTION_TOGGLE_SIT }, + { "Toggle speak", LLGameControl::AVATAR_ACTION_TOGGLE_SPEAK }, + { "Toggle flycam", LLGameControl::AVATAR_ACTION_TOGGLE_FLYCAM }, + { "Toggle mouselook", LLGameControl::AVATAR_ACTION_TOGGLE_MOUSELOOK }, + { "Toggle mouse cursor", LLGameControl::AVATAR_ACTION_TOGGLE_MOUSE_CURSOR }, + { "ESC", LLGameControl::AVATAR_ACTION_ESCAPE }, + }; + return bridge; + } + + // Avatar/Mouselook/Captive-mode button label -> AvatarMouseButton bit. Unlike + // avatarMiscButtonBridge()'s one-shot commands, a simulated mouse button should + // behave like the real thing (held down for as long as the bound button is), + // so these are level-triggered every frame into AgentActions::mMouseButtonBits + // -- mirroring avatarButtonBridge() rather than avatarMiscButtonBridge(). + const std::map& avatarMouseButtonBridge() + { + static const std::map bridge = { + { "Mouse click left", LLGameControl::AVATAR_MOUSE_BUTTON_LEFT }, + { "Mouse click right", LLGameControl::AVATAR_MOUSE_BUTTON_RIGHT }, + }; + return bridge; + } + + // Avatar/Mouselook/Captive-mode button label -> full-deflection contribution to + // the same zoom accumulator as the "Zoom +/-" axis (see computeAgentActions()). + // Mirrors mouseCursorButtonBridge()'s "only override if stronger" pattern rather + // than avatarButtonBridge()'s OR-into-mControlFlags, since zoom has no + // AGENT_CONTROL_* bit of its own. + const std::map& avatarZoomButtonBridge() + { + static const std::map bridge = { + { "Zoom +", 1.f }, + { "Zoom -", -1.f }, + }; + return bridge; + } + + // Cursor mode's digital (button) equivalent of "Mouse left/right"/"Mouse up/down": + // a discrete alternative/addition to the analog stick for driving the on-screen + // cursor, e.g. so the D-Pad can be rebound to nudge the cursor instead of moving + // the avatar. Not part of Cursor mode's default button map (see cursor_buttons in + // buildDefaultModeMappings()) -- purely opt-in via rebinding. A pressed button + // contributes full deflection into computeAgentActions()'s mouse_dx/mouse_dy, + // same "only override if stronger" pattern as Turn/Look's button contribution. + struct MouseCursorButtonEffect { bool isX; F32 sign; }; + const std::map& mouseCursorButtonBridge() + { + static const std::map bridge = { + { "Cursor up", { false, 1.f } }, + { "Cursor down", { false, -1.f } }, + { "Cursor left", { true, -1.f } }, + { "Cursor right", { true, 1.f } }, + }; + return bridge; + } + + // GameControlData's ModeAxes packs the 5 avatar-movement axes in this + // fixed order (LLGameControl::SemanticAxis), regardless of which physical inputs + // are mapped. This mirrors avatarAxisBridge()'s label set -- keep the two in sync + // by hand, same as the other avatar*Bridge()s. + const std::map& avatarSemanticAxisSlots() + { + static const std::map bridge = { + { "Strafe left/right", LLGameControl::SEMANTIC_AXIS_STRAFE }, + { "Advance forward/back", LLGameControl::SEMANTIC_AXIS_ADVANCE }, + { "Turn left/right", LLGameControl::SEMANTIC_AXIS_TURN }, + { "Look up/down", LLGameControl::SEMANTIC_AXIS_LOOK }, + { "Fly up/down", LLGameControl::SEMANTIC_AXIS_RISE }, + }; + return bridge; + } + + // Digital (button) equivalents of the same 5 axes -- mirrors avatarButtonBridge()'s + // movement-action label set, adding the semantic slot + sign each one drives. A + // pressed button contributes full deflection (+/-32767) to its slot, same as + // computeAgentActions() does for Turn/Look -- *and* also sets its own bit in + // ModeButtons (see computeSemanticState()), unlike an analog stick tilt of the + // same axis, which only ever shows up in ModeAxes. + struct SemanticButtonAxisEffect { LLGameControl::SemanticAxis slot; F32 sign; }; + const std::map& avatarSemanticButtonAxisSlots() + { + static const std::map bridge = { + { "Strafe left", { LLGameControl::SEMANTIC_AXIS_STRAFE, 1.f } }, + { "Strafe right", { LLGameControl::SEMANTIC_AXIS_STRAFE, -1.f } }, + { "Advance forward", { LLGameControl::SEMANTIC_AXIS_ADVANCE, 1.f } }, + { "Advance back", { LLGameControl::SEMANTIC_AXIS_ADVANCE,-1.f } }, + { "Turn left", { LLGameControl::SEMANTIC_AXIS_TURN, 1.f } }, + { "Turn right", { LLGameControl::SEMANTIC_AXIS_TURN, -1.f } }, + { "Look up", { LLGameControl::SEMANTIC_AXIS_LOOK, 1.f } }, + { "Look down", { LLGameControl::SEMANTIC_AXIS_LOOK, -1.f } }, + { "Jump", { LLGameControl::SEMANTIC_AXIS_RISE, 1.f } }, + { "Crouch", { LLGameControl::SEMANTIC_AXIS_RISE, -1.f } }, + { "Fly up", { LLGameControl::SEMANTIC_AXIS_RISE, 1.f } }, + { "Fly down", { LLGameControl::SEMANTIC_AXIS_RISE, -1.f } }, + }; + return bridge; + } + + // Cursor mode's ModeAxes slots 0-1 (CURSOR_DX/CURSOR_DY): reuses SemanticAxis's + // STRAFE/ADVANCE slots purely as storage, but they carry "Mouse left/right"/"Mouse + // up/down" (on-screen cursor movement). Slots 2-5 carry cursor position, filled + // separately in computeSemanticState() from setMouseCursorPosition(). + const std::map& cursorSemanticAxisSlots() + { + static const std::map bridge = { + { "Mouse left/right", LLGameControl::SEMANTIC_AXIS_STRAFE }, + { "Mouse up/down", LLGameControl::SEMANTIC_AXIS_ADVANCE }, + }; + return bridge; + } + + // Is 'label' one of Cursor mode's mouse-cursor-emulation actions -- the only + // Cursor-mode actions allowed to steal a physical input away from FlyCam's own + // mapping when FlyCam is concurrently engaged (see + // LLGameControllerManager::rebuildActionLookup()). Reuses the existing bridge + // tables that already enumerate these action labels rather than duplicating + // the literal strings. + bool isMouseCursorAxisAction(const std::string& label) + { + return cursorSemanticAxisSlots().count(label) > 0; // "Mouse left/right"/"Mouse up/down" + } + bool isMouseCursorButtonAction(const std::string& label) + { + // "Mouse click left/right" (avatarMouseButtonBridge) and the digital + // "Cursor up/down/left/right" equivalent (mouseCursorButtonBridge). + return avatarMouseButtonBridge().count(label) > 0 || mouseCursorButtonBridge().count(label) > 0; + } + + // Defensive-repair helpers for stale/renamed axis-action keys in saved settings + // (e.g. a pre-merge "Rise up"). Defined below, after the flycam bridge. + // isKnownAnalogAxisAction - is 'label' a real axis action for this mode? + // defaultAxisActionForInput - which action owns 'input' in the built-in defaults? + bool isKnownAnalogAxisAction(LLGameControl::AgentControlMode mode, const std::string& label); + std::string defaultAxisActionForInput(LLGameControl::AgentControlMode mode, const std::string& input); +} // namespace + +void LLGameControllerManager::applyAxisMappings(LLGameControl::AgentControlMode mode, const LLSD& axes) +{ + const std::string& mode_name = modeToString(mode); + + // Invert ModeMappings[mode][Axes] (action label -> input name) into + // canonical input index -> action binding. Last binding wins on collision. + for (auto it = axes.beginMap(); it != axes.endMap(); ++it) + { + const std::string& action = it->first; + std::string input = it->second.asString(); + + // Defensively handle stale/renamed action keys (e.g. a pre-merge "Rise up" + // left in saved settings). If the key isn't a valid axis action for this mode, + // reassign its input to the action that owns that input by default -- unless + // that default action is already mapped, in which case drop the stale key + // (treat as None). This only sanitizes the in-memory lookup; the stored + // settings are left untouched (they'll be fixed if/when the user edits them). + std::string bound_action = action; + if (!isKnownAnalogAxisAction(mode, action)) + { + std::string default_action = defaultAxisActionForInput(mode, input); + if (default_action.empty() || axes.has(default_action)) + { + continue; // no free default owner for this input: treat as None + } + bound_action = default_action; + } + + bool invert = LLGameControl::getAxisInvert(mode_name, bound_action); + + if (input == INPUT_AXIS_TRIGGERS) + { + // The trigger pair is one bidirectional axis: the left trigger feeds the + // action's negative side, the right trigger its positive side. (SL's + // positive sense is left/forward/up and, for yaw, counter-clockwise per + // the right-hand rule, so e.g. the right trigger turns left by default; + // invert the trigger axes in Device Options to reverse that.) + mAxisActionBindings[LLGameControl::AXIS_LEFT_TRIGGER] = { bound_action, HALF_NEGATIVE, invert }; + mAxisActionBindings[LLGameControl::AXIS_RIGHT_TRIGGER] = { bound_action, HALF_POSITIVE, invert }; + continue; + } + LLGameControl::InputChannel channel = channelFromInputName(input); + if (channel.isAxis() && channel.mIndex < LLGameControl::NUM_AXES) + { + mAxisActionBindings[channel.mIndex] = { bound_action, HALF_FULL, invert }; + } + } +} + +void LLGameControllerManager::applyButtonMappings(const LLSD& buttons) +{ + for (auto it = buttons.beginMap(); it != buttons.endMap(); ++it) + { + LLGameControl::InputChannel channel = channelFromInputName(it->second.asString()); + if (channel.isButton() && channel.mIndex < LLGameControl::NUM_BUTTONS) + { + mButtonActionLabels[channel.mIndex] = it->first; + } + } +} + +void LLGameControllerManager::rebuildActionLookup(bool force) +{ + LLGameControl::AgentControlMode mode = g_agentControlMode; + + // Cursor mode with FlyCam concurrently engaged underneath (see + // LLAgent::updateGameControlMode()/setFlycamEngaged()) is a merge of two modes' + // mappings rather than a plain per-mode lookup -- see below. + bool cursor_over_flycam = (mode == LLGameControl::CONTROL_MODE_CURSOR) && g_flycamEngaged; + + if (!force && mode == mLookupMode && cursor_over_flycam == mLookupCursorOverFlycam + && !mAxisActionBindings.empty()) + { + return; // already current + } + mLookupMode = mode; + mLookupCursorOverFlycam = cursor_over_flycam; + mAxisActionBindings.assign(LLGameControl::NUM_AXES, AxisActionBinding()); + mButtonActionLabels.assign(LLGameControl::NUM_BUTTONS, std::string()); + + if (cursor_over_flycam) + { + // Base layer: FlyCam mode's own configured axes/buttons (Truck/Dolly/Pan/ + // Tilt/Boom/Roll/Zoom/Reset/...), so whatever physical inputs FlyCam already + // owns keep driving the camera exactly as they do in plain FlyCam mode. + applyAxisMappings(LLGameControl::CONTROL_MODE_FLYCAM, + LLGameControl::getModeMapping(GC_MODE_FLYCAM, GC_AXES)); + applyButtonMappings(LLGameControl::getModeMapping(GC_MODE_FLYCAM, GC_BUTTONS)); + + // Overlay: only Cursor mode's own mouse-cursor-emulation actions (the on- + // screen cursor movement axes, simulated clicks, and the digital cursor-move + // buttons), so those -- and only those -- win any physical-input collision + // with the FlyCam base above (e.g. the default left stick: FlyCam's + // Truck/Dolly underneath, Cursor's Mouse left/right / up/down on top). Every + // other Cursor-mode action (Turn/Look/Fly/Jump/Strafe/...) is intentionally + // dropped here: it must not steal an input away from FlyCam's own mapping. + LLSD cursor_axes = LLGameControl::getModeMapping(GC_MODE_CURSOR, GC_AXES); + LLSD mouse_axes; + for (auto it = cursor_axes.beginMap(); it != cursor_axes.endMap(); ++it) + { + if (isMouseCursorAxisAction(it->first)) + { + mouse_axes[it->first] = it->second; + } + } + applyAxisMappings(LLGameControl::CONTROL_MODE_CURSOR, mouse_axes); + + LLSD cursor_buttons = LLGameControl::getModeMapping(GC_MODE_CURSOR, GC_BUTTONS); + LLSD mouse_buttons; + for (auto it = cursor_buttons.beginMap(); it != cursor_buttons.endMap(); ++it) + { + if (isMouseCursorButtonAction(it->first)) + { + mouse_buttons[it->first] = it->second; + } + } + applyButtonMappings(mouse_buttons); + return; + } + + const std::string& mode_name = modeToString(mode); + if (mode_name.empty()) + { + return; // CONTROL_MODE_NONE has no mappings + } + applyAxisMappings(mode, LLGameControl::getModeMapping(mode_name, GC_AXES)); + applyButtonMappings(LLGameControl::getModeMapping(mode_name, GC_BUTTONS)); +} + +LLGameControl::AgentActions LLGameControllerManager::computeAgentActions() +{ + // Ensure the lookup matches the active mode (cheap no-op when up to date). + rebuildActionLookup(); + + // Threshold matching LLGameControlTranslator's ON/OFF zone for analog->digital. + constexpr U16 AXIS_THRESHOLD = 32768 / 8; + + LLGameControl::AgentActions result; + + // step through the axis mappings and calculate the combined contribution to various + // action directions + const auto& axis_bridge = avatarAxisBridge(); + S32 movement_magnitude = 0; + S32 yaw_value = 0; + S32 pitch_value = 0; + S32 mouse_dx = 0; + S32 mouse_dy = 0; + S32 zoom_value = 0; + for (U8 axis = 0; axis < LLGameControl::NUM_AXES; ++axis) + { + const AxisActionBinding& binding = mAxisActionBindings[axis]; + if (binding.label.empty()) + { + continue; + } + + // "Mouse left/right"/"Mouse up/down" (Cursor mode only) drive the on-screen + // cursor rather than an AGENT_CONTROL_* flag, so they're captured here + // separately from avatarAxisBridge() below. + bool is_mouse_x = binding.label == "Mouse left/right"; + bool is_mouse_y = binding.label == "Mouse up/down"; + if (is_mouse_x || is_mouse_y) + { + S32 deflection = (S32)g_innerState.mAxes[axis * 2] - (S32)g_innerState.mAxes[axis * 2 + 1]; + S32 value = binding.half == HALF_NEGATIVE ? -deflection : deflection; + if (binding.invert) + { + value = -value; + } + S32& mouse_axis_value = is_mouse_x ? mouse_dx : mouse_dy; + if (std::abs(value) > std::abs(mouse_axis_value)) + { + mouse_axis_value = value; + } + continue; + } + + // "Zoom +/-" (Avatar/Mouselook/Captive only) drives the camera zoom rate + // rather than an AGENT_CONTROL_* bit, so it's captured here separately from + // avatarAxisBridge() below, same reasoning as the mouse-cursor axes above. + if (binding.label == "Zoom +/-") + { + S32 deflection = (S32)g_innerState.mAxes[axis * 2] - (S32)g_innerState.mAxes[axis * 2 + 1]; + S32 value = binding.half == HALF_NEGATIVE ? -deflection : deflection; + if (binding.invert) + { + value = -value; + } + if (std::abs(value) > std::abs(zoom_value)) + { + zoom_value = value; + } + continue; + } + + auto it = axis_bridge.find(binding.label); + if (it == axis_bridge.end()) + { + continue; + } + + // Axes: work from each canonical axis' signed deflection (positive half at + // axis*2 minus negative half at axis*2+1). Using the deflection rather than a + // single half makes the Device Options per-axis Invert option work for triggers + // too: inverting there moves the magnitude into the other half and flips the + // sign. A trigger half (HALF_NEGATIVE = left, HALF_POSITIVE = right) applies + // its side's polarity; a full axis (HALF_FULL) uses the deflection directly. + // binding.invert (the per-mode, per-action Invert flag) is then applied on top. + S32 deflection = (S32)g_innerState.mAxes[axis * 2] - (S32)g_innerState.mAxes[axis * 2 + 1]; + + S32 value = binding.half == HALF_NEGATIVE ? -deflection : deflection; + if (binding.invert) + { + value = -value; + } + if (value > AXIS_THRESHOLD) + { + result.mControlFlags |= it->second.posFlag; + } + if (value < -(S32)AXIS_THRESHOLD) + { + result.mControlFlags |= it->second.negFlag; + } + if (binding.label == "Strafe left/right" || binding.label == "Advance forward/back") + { + // Largest deflection seen on a ground-movement axis (Strafe/Advance) this frame, + // drives the analog "is running" hysteresis. + movement_magnitude = std::max(movement_magnitude, std::abs(value)); + } + else if (binding.label == "Turn left/right" && std::abs(value) > std::abs(yaw_value)) + { + yaw_value = value; + } + else if (binding.label == "Look up/down" && std::abs(value) > std::abs(pitch_value)) + { + pitch_value = value; + } + } + + // Analog "is running" hysteresis: engage once a movement axis is pushed past + // 90% of full deflection, release once every movement axis drops back below + // 70%. The dead zone between the two thresholds avoids flicker right at the + // boundary. + constexpr F32 RUN_ENGAGE_FRACTION = 0.9f; + constexpr F32 RUN_RELEASE_FRACTION = 0.7f; + F32 movement_fraction = (F32)movement_magnitude / 32767.f; + if (movement_fraction > RUN_ENGAGE_FRACTION) + { + mIsAnalogRunning = true; + } + else if (movement_fraction < RUN_RELEASE_FRACTION) + { + mIsAnalogRunning = false; + } + + // Each pressed button contributes its bound label's action. + // Most labels in avatarButtonBridge() are level-triggered (held == asserted), + // matching how the movement bits work, and OR their AGENT_CONTROL_* bit + // into mControlFlags every frame they're held. + // + // Simulated mouse buttons in avatarMouseButtonBridge() are likewise + // level-triggered, OR'd into mMouseButtonBits every frame they're held, + // so a held bound button holds the mouse button down (e.g. for click-drag) + // LLAgent::applyExternalActions diffs mMouseButtonBits against its own + // previous-frame value to find the press/release edges. + // + // One-shot *commands* (avatarMiscButtonBridge(), plus "Sit down"/"Stand up") + // should instead fire once per physical press -- for those, only the not-pressed + // -> pressed edge counts. Edge state is tracked per physical button via + // g_innerState.mPrevButtons (maintained every frame by accumulateInternalState()'s + // storePrevious(), which runs before this), not on the resulting bit, since + // the same held button can flip which of these labels it's bound to across a + // single frame boundary (sitting flips the active mode Avatar -> Captive); + // from the new bit's own history that would otherwise look like a fresh press. + + const auto& button_bridge = avatarButtonBridge(); + const auto& mouse_button_bridge = avatarMouseButtonBridge(); + const auto& misc_button_bridge = avatarMiscButtonBridge(); + const auto& mouse_cursor_button_bridge = mouseCursorButtonBridge(); + const auto& zoom_button_bridge = avatarZoomButtonBridge(); + U32 pressed_edges = g_innerState.mButtons & ~g_innerState.mPrevButtons; + for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) + { + if (!(g_innerState.mButtons & (1U << btn))) + { + continue; + } + const std::string& label = mButtonActionLabels[btn]; + if (label.empty()) + { + continue; + } + if ((label == "Sit down" || label == "Stand up") && !(pressed_edges & (1U << btn))) + { + continue; + } + auto it = button_bridge.find(label); + if (it != button_bridge.end()) + { + // this action corresponds to AGENT_CONTROL_* and sets a bit in mControlFlags + result.mControlFlags |= it->second; + + // but we also want to compute contribution modulation so + // button input can be reasonably combined with axis input + if (label == "Turn left" && 32767 > std::abs(yaw_value)) + { + yaw_value = 32767; + } + else if (label == "Turn right" && 32767 > std::abs(yaw_value)) + { + yaw_value = -32767; + } + else if (label == "Look up" && 32767 > std::abs(pitch_value)) + { + pitch_value = 32767; + } + else if (label == "Look down" && 32767 > std::abs(pitch_value)) + { + pitch_value = -32767; + } + continue; + } + auto bit = mouse_button_bridge.find(label); + if (bit != mouse_button_bridge.end()) + { + // level-triggered: held for as long as the bound button is + result.mMouseButtonBits |= bit->second; + continue; + } + auto cursor_it = mouse_cursor_button_bridge.find(label); + if (cursor_it != mouse_cursor_button_bridge.end()) + { + S32 full = (S32)(cursor_it->second.sign * 32767.f); + S32& mouse_axis_value = cursor_it->second.isX ? mouse_dx : mouse_dy; + if (std::abs(full) > std::abs(mouse_axis_value)) + { + mouse_axis_value = full; + } + continue; + } + auto zoom_it = zoom_button_bridge.find(label); + if (zoom_it != zoom_button_bridge.end()) + { + S32 full = (S32)(zoom_it->second * 32767.f); + if (std::abs(full) > std::abs(zoom_value)) + { + zoom_value = full; + } + continue; + } + auto mit = misc_button_bridge.find(label); + if (mit != misc_button_bridge.end() && (pressed_edges & (1U << btn))) + { + result.mMiscActionBits |= mit->second; + } + } + + result.mIsRunning = mIsAnalogRunning; + + result.mYawAmplitude = std::clamp((F32)yaw_value / 32767.f, -1.f, 1.f); + result.mPitchAmplitude = std::clamp((F32)pitch_value / 32767.f, -1.f, 1.f); + result.mMouseCursorDX = std::clamp((F32)mouse_dx / 32767.f, -1.f, 1.f); + result.mMouseCursorDY = std::clamp((F32)mouse_dy / 32767.f, -1.f, 1.f); + result.mZoomAmplitude = std::clamp((F32)zoom_value / 32767.f, -1.f, 1.f); + + return result; +} + +namespace +{ + // Flycam channel packing order is defined by LLGameControl::FlycamChannel + // (shared with LLAgent::updateFlycam()). + struct FlycamAxisEffect { U8 channel; F32 polarity; }; + + // FlyCam-mode axis label -> flycam channel + polarity applied to the axis' + // signed value (positive half minus negative half). "Fly up/down" bound to the + // trigger pair yields the tied-trigger RISE behavior: the right trigger (positive + // half) rises, the left trigger (negative half) drops. + const std::map& flycamAxisBridge() + { + static const std::map bridge = { + { "Truck left/right", { LLGameControl::FLYCAM_TRUCK, 1.f } }, + { "Dolly forward/back", { LLGameControl::FLYCAM_DOLLY, 1.f } }, + { "Tilt up/down", { LLGameControl::FLYCAM_TILT, 1.f } }, + { "Pan left/right", { LLGameControl::FLYCAM_PAN, 1.f } }, + { "Boom up/down", { LLGameControl::FLYCAM_BOOM, 1.f } }, + { "Roll CCW/CW", { LLGameControl::FLYCAM_ROLL, 1.f } }, + { "Zoom in/out", { LLGameControl::FLYCAM_ZOOM, 1.f } }, + }; + return bridge; + } + + // FlyCam-mode button label -> flycam channel + full-deflection contribution. + // A gamepad has no free axis for roll (all six are used for translate/look), + // so roll is driven by the shoulder buttons by default; the dpad likewise + // provides digital dolly/pan. + const std::map& flycamButtonBridge() + { + static const std::map bridge = { + { "Truck left", { LLGameControl::FLYCAM_TRUCK, 1.f } }, + { "Truck right", { LLGameControl::FLYCAM_TRUCK, -1.f } }, + { "Dolly forward", { LLGameControl::FLYCAM_DOLLY, 1.f } }, + { "Dolly back", { LLGameControl::FLYCAM_DOLLY, -1.f } }, + { "Pan left", { LLGameControl::FLYCAM_PAN, 1.f } }, + { "Pan right", { LLGameControl::FLYCAM_PAN, -1.f } }, + { "Tilt up", { LLGameControl::FLYCAM_TILT, 1.f } }, + { "Tilt down", { LLGameControl::FLYCAM_TILT, -1.f } }, + { "Boom up", { LLGameControl::FLYCAM_BOOM, 1.f } }, + { "Boom down", { LLGameControl::FLYCAM_BOOM, -1.f } }, + { "Roll CCW", { LLGameControl::FLYCAM_ROLL, -1.f } }, + { "Roll CW", { LLGameControl::FLYCAM_ROLL, 1.f } }, + { "Zoom in", { LLGameControl::FLYCAM_ZOOM, 1.f } }, + { "Zoom out", { LLGameControl::FLYCAM_ZOOM, -1.f } }, + }; + return bridge; + } + + // FlyCam-mode button label -> one-shot FlycamMiscAction bit. These are + // discrete commands rather than per-frame DOF contributions, so they're + // kept in a bridge separate from flycamButtonBridge() and edge-triggered + // (not-pressed -> pressed) by getFlycamInputs() into its misc_actions_out + // bitmask, mirroring avatarMiscButtonBridge()/mMiscActionBits above. + const std::map& flycamMiscButtonBridge() + { + static const std::map bridge = { + { "Unroll", LLGameControl::FLYCAM_ACTION_UNROLL }, + { "ESC", LLGameControl::FLYCAM_ACTION_ESCAPE }, + }; + return bridge; + } + + // FlyCam-mode button label -> level-triggered FlycamModifier bit. Unlike + // flycamMiscButtonBridge()'s one-shot commands, these are asserted every + // frame the bound button is held down, mirroring the plain (non-edge) + // button-held check flycamButtonBridge() itself uses. + const std::map& flycamModifierButtonBridge() + { + static const std::map bridge = { + { "Orbit", LLGameControl::FLYCAM_MODIFIER_ORBIT }, + }; + return bridge; + } + + // Per-mode ModeButtons slot table: default-mapped action label -> semantic slot + // (the canonical Button index 'mode's DEFAULT mapping assigns that action), e.g. + // "Crouch" -> 1 (BUTTON_EAST) for Avatar mode. This is derived solely from the + // DEFAULT mapping (buildDefaultModeMappings()), so it stays fixed no matter how + // the user remaps buttons in Preferences: whichever physical button "Crouch" is + // currently bound to, pressing it must still set the same ModeButtons bit (see + // message_template.msg's GameControlData doc -- "a ModeButtons bit index is + // always identical to the canonical Button index to which it is mapped by + // default, no matter how it is actually mapped"). Actions the default mapping + // leaves unbound, and CONTROL_MODE_NONE (which has no ModeButtons concept at + // all), are simply absent from the map. + const std::map& getDefaultButtonSemanticSlots(LLGameControl::AgentControlMode mode) + { + static std::map> cache; + auto found = cache.find(mode); + if (found != cache.end()) + { + return found->second; + } + + std::map table; + const std::string& mode_name = modeToString(mode); + if (!mode_name.empty()) + { + LLSD default_buttons = buildDefaultModeMappings()[mode_name][GC_BUTTONS]; + for (auto it = default_buttons.beginMap(); it != default_buttons.endMap(); ++it) + { + LLGameControl::InputChannel channel = channelFromInputName(it->second.asString()); + if (channel.isButton() && channel.mIndex < LLGameControl::NUM_BUTTONS) + { + table[it->first] = channel.mIndex; + } + } + } + + return cache.emplace(mode, std::move(table)).first->second; + } + + // (Declared above, before rebuildActionLookup.) The axis bridges are the complete + // set of valid axis actions per mode, so bridge membership is the authoritative + // "is this a real axis action" test. + bool isKnownAnalogAxisAction(LLGameControl::AgentControlMode mode, const std::string& label) + { + if (mode == LLGameControl::CONTROL_MODE_FLYCAM) + { + return flycamAxisBridge().count(label) > 0; + } + if (mode == LLGameControl::CONTROL_MODE_CURSOR) + { + // Shares the avatar axis actions (Turn/Look/Fly, plus Strafe/Advance if the + // user re-binds them) and adds the two cursor-movement axes. + return avatarAxisBridge().count(label) > 0 + || label == "Mouse left/right" || label == "Mouse up/down"; + } + // Avatar, Mouselook, and Captive share the avatar axis actions, + // and "Zoom +/-" (camera zoom rate; see computeAgentActions()). + return avatarAxisBridge().count(label) > 0 || label == "Zoom +/-"; + } + + std::string defaultAxisActionForInput(LLGameControl::AgentControlMode mode, const std::string& input) + { + const std::string& mode_name = modeToString(mode); + if (mode_name.empty()) + { + return LLStringUtil::null; + } + LLSD axes = buildDefaultModeMappings()[mode_name][GC_AXES]; + for (auto it = axes.beginMap(); it != axes.endMap(); ++it) + { + if (it->second.asString() == input) + { + return it->first; + } + } + return LLStringUtil::null; + } +} + +// Fills in state.mActionMode/mSemanticAxes/mSemanticButtons (the AgentData.ActionMode/ +// ModeAxes/ModeButtons blocks of GameControlData) from the current +// controller + keyboard state and the active mode's *live* action mapping +// (mAxisActionBindings/mButtonActionLabels), mirroring computeAgentActions()'s +// per-axis/per-button loops but emitting signed magnitudes / renumbered indices +// instead of AGENT_CONTROL_*/FlycamChannel bits, and packed in whichever mode-specific +// slot order applies (SemanticAxis for Avatar/Mouselook/Captive/Mouse, FlycamChannel for +// FlyCam; see LLGameControl::NUM_SEMANTIC_SLOTS/numSemanticAxesForMode()). +void LLGameControllerManager::computeSemanticState(LLGameControl::ServerState& state) +{ + rebuildActionLookup(); + + // AgentData.ActionMode: resend immediately on change, same as axes/buttons below. + U8 old_mode = state.mActionMode; + U8 new_mode = (U8)g_agentControlMode; + state.mPrevActionMode = old_mode; + state.mActionMode = new_mode; + if (old_mode != new_mode) + { + scheduleImmediateResend(); + } + + S32 semantic_values[LLGameControl::NUM_SEMANTIC_SLOTS] = { 0 }; + U32 new_semantic_buttons = 0; + + if (g_agentControlMode == LLGameControl::CONTROL_MODE_AVATAR + || g_agentControlMode == LLGameControl::CONTROL_MODE_MOUSELOOK + || g_agentControlMode == LLGameControl::CONTROL_MODE_CAPTIVE + || g_agentControlMode == LLGameControl::CONTROL_MODE_CURSOR) + { + // In Cursor mode the STRAFE/ADVANCE wire slots carry "Mouse left/right"/"Mouse + // up/down" (cursor movement) instead of Strafe/Advance -- see + // cursorSemanticAxisSlots(). Slots 2-5 (Turn/Look in every other mode) instead + // carry cursor position, filled in below. + bool is_cursor_mode = (g_agentControlMode == LLGameControl::CONTROL_MODE_CURSOR); + + const auto& axis_slots = is_cursor_mode ? cursorSemanticAxisSlots() : avatarSemanticAxisSlots(); + for (U8 axis = 0; axis < LLGameControl::NUM_AXES; ++axis) + { + const AxisActionBinding& binding = mAxisActionBindings[axis]; + if (binding.label.empty()) + { + continue; + } + auto it = axis_slots.find(binding.label); + if (it == axis_slots.end()) + { + continue; + } + + // Same signed-deflection calculation as computeAgentActions(): positive + // half minus negative half, trigger-half selection, then per-action invert. + S32 deflection = (S32)g_innerState.mAxes[axis * 2] - (S32)g_innerState.mAxes[axis * 2 + 1]; + S32 value = binding.half == HALF_NEGATIVE ? -deflection : deflection; + if (binding.invert) + { + value = -value; + } + U8 slot = it->second; + if (std::abs(value) > std::abs(semantic_values[slot])) + { + semantic_values[slot] = value; + } + } + + if (!is_cursor_mode) + { + // Movement buttons (real controller only -- keyboard's contribution to these + // same actions is folded in below via mExternalActionFlags. + const auto& button_axis_slots = avatarSemanticButtonAxisSlots(); + for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) + { + if (!(g_innerState.mButtons & (1U << btn))) + { + continue; + } + const std::string& label = mButtonActionLabels[btn]; + if (label.empty()) + { + continue; + } + auto it = button_axis_slots.find(label); + if (it == button_axis_slots.end()) + { + continue; + } + S32 full = (S32)(it->second.sign * 32767.f); + if (std::abs(full) > std::abs(semantic_values[it->second.slot])) + { + semantic_values[it->second.slot] = full; + } + } + + // Keyboard-only contribution: setExternalInput()'s AGENT_CONTROL_* bits map + // directly onto the TURN/LOOK/RISE/STRAFE/ADVANCE semantic slots (YAW->TURN, + // PITCH->LOOK, UP->RISE, LEFT->STRAFE, AT->ADVANCE). Summed on top of the + // controller-only value above, mirroring how accumulateFinalState() sums + // keyboard + controller for the raw axes, so a keyboard-only user still + // produces meaningful ModeAxes. + static const struct { U32 posFlag; U32 negFlag; LLGameControl::SemanticAxis slot; } KEY_AXIS_BITS[] = { + { AGENT_CONTROL_YAW_POS, AGENT_CONTROL_YAW_NEG, LLGameControl::SEMANTIC_AXIS_TURN }, + { AGENT_CONTROL_PITCH_POS, AGENT_CONTROL_PITCH_NEG, LLGameControl::SEMANTIC_AXIS_LOOK }, + { AGENT_CONTROL_UP_POS, AGENT_CONTROL_UP_NEG, LLGameControl::SEMANTIC_AXIS_RISE }, + { AGENT_CONTROL_LEFT_POS, AGENT_CONTROL_LEFT_NEG, LLGameControl::SEMANTIC_AXIS_STRAFE }, + { AGENT_CONTROL_AT_POS, AGENT_CONTROL_AT_NEG, LLGameControl::SEMANTIC_AXIS_ADVANCE }, + }; + const S32 key_magnitude = mExternalIsRunning ? 32767 : 32767 / 2; + for (const auto& bit : KEY_AXIS_BITS) + { + S32 kb_value = 0; + if (mExternalActionFlags & bit.posFlag) + { + kb_value += key_magnitude; + } + if (mExternalActionFlags & bit.negFlag) + { + kb_value -= key_magnitude; + } + semantic_values[bit.slot] += kb_value; + } + } + else + { + // CURSOR_PX/PY/NX/NY (slots 2-5): the actual on-screen cursor position + // within the rect available to a gamepad-driven cursor, fed in externally + // via setMouseCursorPosition() since this module has no notion of window + // geometry -- unlike every other semantic axis, these don't come from any + // bound controller/keyboard input at all. + S32 px = std::clamp(mMouseCursorPixelX, 0, mMouseCursorRectWidth); + S32 py = std::clamp(mMouseCursorPixelY, 0, mMouseCursorRectHeight); + semantic_values[2] = px; + semantic_values[3] = py; + // NX/NY: [0.0, 1.0] normalized position mapped onto the S16 wire range, + // rounded to nearest and clamped to 32767 (S16_MAX -- 1.0 would otherwise + // map to the unrepresentable 32768). + semantic_values[4] = mMouseCursorRectWidth > 0 + ? std::clamp((px * 32767 + mMouseCursorRectWidth / 2) / mMouseCursorRectWidth, 0, 32767) + : 0; + semantic_values[5] = mMouseCursorRectHeight > 0 + ? std::clamp((py * 32767 + mMouseCursorRectHeight / 2) / mMouseCursorRectHeight, 0, 32767) + : 0; + } + } + else if (g_agentControlMode == LLGameControl::CONTROL_MODE_FLYCAM) + { + // No keyboard-driven path to FlyCam's channels exists in this codebase (FlyCam + // motion is only ever produced by a real controller, via getFlycamInputs()), + // so this is controller-only -- same scope FlyCam has always had. + const auto& axis_slots = flycamAxisBridge(); + for (U8 axis = 0; axis < LLGameControl::NUM_AXES; ++axis) + { + const AxisActionBinding& binding = mAxisActionBindings[axis]; + if (binding.label.empty()) + { + continue; + } + auto it = axis_slots.find(binding.label); + if (it == axis_slots.end()) + { + continue; + } + S32 deflection = (S32)g_innerState.mAxes[axis * 2] - (S32)g_innerState.mAxes[axis * 2 + 1]; + S32 value = binding.half == HALF_NEGATIVE ? -deflection : deflection; + if (binding.invert) + { + value = -value; + } + S32 signed_value = (S32)(it->second.polarity * (F32)value); + U8 slot = it->second.channel; + if (std::abs(signed_value) > std::abs(semantic_values[slot])) + { + semantic_values[slot] = signed_value; + } + } + + const auto& button_axis_slots = flycamButtonBridge(); + for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) + { + if (!(g_innerState.mButtons & (1U << btn))) + { + continue; + } + const std::string& label = mButtonActionLabels[btn]; + if (label.empty()) + { + continue; + } + auto it = button_axis_slots.find(label); + if (it == button_axis_slots.end()) + { + continue; + } + S32 full = (S32)(it->second.polarity * 32767.f); + U8 slot = it->second.channel; + if (std::abs(full) > std::abs(semantic_values[slot])) + { + semantic_values[slot] = full; + } + } + } + // else CONTROL_MODE_NONE: no movement concept applies; semantic_values stays zero. + + // ModeButtons: every currently-pressed button -- real controller OR a + // keyboard-simulated press folded in via setExternalInput() (mExternalServerState, + // e.g. a literal "simulate this GameControl button" keybind) -- sets the bit for + // whichever action is CURRENTLY bound to it (mButtonActionLabels), looked up + // against the mode's default mapping to find that action's stable slot -- NOT the + // bit matching the button's own (possibly remapped) canonical index. This includes + // buttons whose current action is a movement action (e.g. D-Pad Strafe/Advance): + // those already fold into mSemanticAxes above too, so a movement button (or a + // keyboard key mapped to the same action) shows up in both ModeAxes and + // ModeButtons, whereas tilting the equivalent analog stick only ever shows up in + // ModeAxes. + U32 combined_buttons = g_innerState.mButtons | mExternalServerState.mButtons; + const std::map& default_slots = getDefaultButtonSemanticSlots(g_agentControlMode); + for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) + { + if (!(combined_buttons & (1U << btn))) + { + continue; + } + const std::string& label = mButtonActionLabels[btn]; + if (label.empty()) + { + continue; + } + auto it = default_slots.find(label); + if (it != default_slots.end()) + { + new_semantic_buttons |= (1U << it->second); + } + } + + U32 old_semantic_buttons = state.mSemanticButtons; + state.mPrevSemanticButtons = old_semantic_buttons; + state.mSemanticButtons = new_semantic_buttons; + if (old_semantic_buttons != new_semantic_buttons) + { + scheduleImmediateResend(); + } + + for (U8 slot = 0; slot < LLGameControl::NUM_SEMANTIC_SLOTS; ++slot) + { + S16 value = (S16)std::clamp(semantic_values[slot], -32768, 32767); + if (state.mSemanticAxes[slot] != value) + { + // Same first-resend-includes-the-change, later-resends-don't pattern as + // mAxes above; updateResendPeriod() catches mPrevSemanticAxes up on + // second+ resend. + state.mPrevSemanticAxes[slot] = state.mSemanticAxes[slot]; + state.mSemanticAxes[slot] = value; + scheduleImmediateResend(); + } + } +} + +void LLGameControllerManager::getFlycamInputs(std::vector& inputs, U32& misc_actions, U32& modifiers) +{ + misc_actions = 0; + modifiers = 0; + + // When FlyCam-mode conversion is disabled, produce no motion. This path + // (LLAgent::updateFlycam) is not gated by willControlFlycam(), so gate it + // here on the per-mode flag. + if (!LLGameControl::isModeEnabled(GC_MODE_FLYCAM)) + { + inputs.assign(LLGameControl::FLYCAM_NUM_CHANNELS, 0.f); + return; + } + + // Ensure the runtime lookup matches the active (FlyCam) mode. + rebuildActionLookup(); + + // Accumulate each flycam channel from the bound FlyCam-mode axes, reading the + // canonical device state. Values are normalized to [-1, 1] and packed in the + // fixed order consumed by LLAgent::updateFlycam(). + std::vector dof(LLGameControl::FLYCAM_NUM_CHANNELS, 0.f); + const auto& bridge = flycamAxisBridge(); + for (U8 axis = 0; axis < LLGameControl::NUM_AXES; ++axis) + { + const AxisActionBinding& binding = mAxisActionBindings[axis]; + if (binding.label.empty()) + { + continue; + } + auto it = bridge.find(binding.label); + if (it == bridge.end()) + { + continue; + } + // g_innerState half-axes: positive magnitude at axis*2, negative at axis*2+1. + // Use the signed deflection so the Device Options per-axis Invert option + // works for triggers too (inverting there swaps which half holds the + // magnitude, flipping the sign). A right-trigger half (HALF_NEGATIVE) + // negates; left (HALF_POSITIVE) and full axes use the deflection as-is. + // binding.invert (the per-mode, per-action Invert flag) is applied on top. + F32 pos = (F32)g_innerState.mAxes[axis * 2] / 32767.f; + F32 neg = (F32)g_innerState.mAxes[axis * 2 + 1] / 32767.f; + F32 deflection = pos - neg; + F32 signed_value = binding.half == HALF_NEGATIVE ? -deflection : deflection; + if (binding.invert) + { + signed_value = -signed_value; + } + dof[it->second.channel] += it->second.polarity * signed_value; + } + + // TODO: avoid repetitive button_bridge.find() by building + // the index translation once and then just iterating over the indicies. + + // Button-driven flycam motion: each pressed button contributes full + // deflection to its channel, every frame it's held (flycamButtonBridge()). + // One-shot commands (flycamMiscButtonBridge()) fire only on the + // not-pressed -> pressed edge instead, mirroring how computeAgentActions() + // splits avatarButtonBridge()/avatarMiscButtonBridge() above. Modifiers + // (flycamModifierButtonBridge()) are level-triggered like the channel + // buttons, just reported in their own bitmask instead of a DOF channel. + const auto& button_bridge = flycamButtonBridge(); + const auto& misc_button_bridge = flycamMiscButtonBridge(); + const auto& modifier_button_bridge = flycamModifierButtonBridge(); + U32 pressed_edges = g_innerState.mButtons & ~g_innerState.mPrevButtons; + for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) + { + if (!(g_innerState.mButtons & (1U << btn))) + { + continue; + } + const std::string& label = mButtonActionLabels[btn]; + if (label.empty()) + { + continue; + } + auto it = button_bridge.find(label); + if (it != button_bridge.end()) + { + dof[it->second.channel] += it->second.polarity; + continue; + } + auto mod_it = modifier_button_bridge.find(label); + if (mod_it != modifier_button_bridge.end()) + { + modifiers |= mod_it->second; + continue; + } + auto mit = misc_button_bridge.find(label); + if (mit != misc_button_bridge.end() && (pressed_edges & (1U << btn))) + { + misc_actions |= mit->second; + } + } + + for (F32& v : dof) + { + v = std::clamp(v, -1.f, 1.f); + } + inputs = dof; +} + +void LLGameControllerManager::setExternalInput(U32 action_flags, U32 buttons, bool is_running) +{ + // Kept raw for computeSemanticState()'s keyboard fold-in (see mExternalActionFlags). + mExternalActionFlags = action_flags; + mExternalIsRunning = is_running; // avatar is running via external input + + // Translate the AGENT_CONTROL_* bits that correspond directly to a + // movement/turn/look/fly axis into the matching canonical wire axis of + // mExternalServerState -- the same final ServerState format + // sendGameControlData() packs -- so keyboard-driven avatar movement + // (control_flags) is mirrored into outgoing GameControlData (and + // therefore visible to LSL scripts) even when no physical game controller + // is present. 'action_flags' is expected to be gAgent::getControlFlags() + // read BEFORE any game-controller-driven movement (see + // LLGameControllerManager::computeAgentActions()/LLAgent:: + // applyExternalActions()) is folded into it this frame, so this never + // re-derives GameControl data from the controller's own output; the + // controller-only contribution (g_controllerState) is computed entirely + // separately by computeFinalState() and the two are combined only in + // accumulateFinalState(), right before a packet is sent. Other + // AGENT_CONTROL_* bits (Stop, fast-movement modifiers, sit, etc.) have no + // direct GameControl axis/button equivalent and are left untranslated. + static const struct { U32 posFlag; U32 negFlag; U8 axis; } AXIS_BITS[] = { + { AGENT_CONTROL_LEFT_POS, AGENT_CONTROL_LEFT_NEG, LLGameControl::AXIS_LEFTX }, + { AGENT_CONTROL_AT_POS, AGENT_CONTROL_AT_NEG, LLGameControl::AXIS_LEFTY }, + { AGENT_CONTROL_YAW_POS, AGENT_CONTROL_YAW_NEG, LLGameControl::AXIS_RIGHTX }, + { AGENT_CONTROL_PITCH_POS, AGENT_CONTROL_PITCH_NEG, LLGameControl::AXIS_RIGHTY }, + { AGENT_CONTROL_UP_POS, AGENT_CONTROL_UP_NEG, LLGameControl::AXIS_LEFT_TRIGGER }, + }; + + // Approximate the walk/run stick-tilt threshold (see RUN_ENGAGE_FRACTION / + // RUN_RELEASE_FRACTION in computeAgentActions()) as a flat 50%: a keyboard + // "walk" looks like a half-deflected axis, a keyboard "run" looks like a + // fully-deflected one. + const S16 magnitude = is_running ? std::numeric_limits::max() + : std::numeric_limits::max() / 2; + + mExternalServerState.clear(); + for (const auto& bit : AXIS_BITS) + { + S32 value = 0; + if (action_flags & bit.posFlag) + { + value += magnitude; + } + if (action_flags & bit.negFlag) + { + value -= magnitude; + } + mExternalServerState.mAxes[bit.axis] = (S16)std::clamp(value, -32768, 32767); + } + + // Also translate those same bits into a "pressed" state for whichever + // GameControl button (if any) is currently bound -- via the active mode's + // Button mappings -- to the action that produces that bit (e.g. "Jump" + // bound to BUTTON_SOUTH). This mirrors computeAgentActions()'s button + // loop in reverse: there, a pressed physical button yields a control + // flag; here, an active control flag yields a "pressed" button so + // outgoing GameControlData/LSL-visible button state matches whichever + // button the user has mapped to that keyboard action. Labels bound to a + // one-shot AvatarMiscAction (avatarMiscButtonBridge()) have no persistent + // flag to test and are absent from avatarButtonBridge(), so they're + // skipped automatically here. + rebuildActionLookup(); + const auto& button_bridge = avatarButtonBridge(); + for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) + { + const std::string& label = mButtonActionLabels[btn]; + if (label.empty()) + { + continue; + } + auto it = button_bridge.find(label); + if (it == button_bridge.end()) + { + continue; + } + if (action_flags & it->second) + { + mExternalServerState.mButtons |= (0x01U << btn); + } + } + + mExternalServerState.mButtons |= buttons; +} + +void LLGameControllerManager::setMouseCursorPosition(S32 pixel_x, S32 pixel_y, S32 rect_width, S32 rect_height) +{ + mMouseCursorPixelX = pixel_x; + mMouseCursorPixelY = pixel_y; + mMouseCursorRectWidth = rect_width; + mMouseCursorRectHeight = rect_height; +} + +void LLGameControllerManager::clear() +{ + mDevices.clear(); +} + +void onJoystickDeviceAdded(const SDL_Event& event) +{ + std::string guid(std::to_string(SDL_GetJoystickGUIDForID(event.jdevice.which))); + SDL_JoystickType type(SDL_GetJoystickTypeForID(event.jdevice.which)); + std::string name(ll_safe_string(SDL_GetJoystickNameForID(event.jdevice.which))); + + LL_INFOS("SDL3") << "joystick {id:" << event.jdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << "}" << LL_ENDL; + + if (SDL_Joystick* joystick = SDL_OpenJoystick(event.jdevice.which)) + { + LL_INFOS("SDL3") << "joystick " << joystick << LL_ENDL; + SDL_CloseJoystick(joystick); + } + else + { + LL_WARNS("SDL3") << "Can't open joystick: " << SDL_GetError() << LL_ENDL; + } +} + +void onJoystickDeviceRemoved(const SDL_Event& event) +{ + LL_INFOS("SDL3") << "joystick id: " << event.jdevice.which << LL_ENDL; +} + +void onControllerDeviceAdded(const SDL_Event& event) +{ + std::string guid(std::to_string(SDL_GetGamepadGUIDForID(event.gdevice.which))); + SDL_GamepadType type(SDL_GetGamepadTypeForID(event.gdevice.which)); + std::string name(ll_safe_string(SDL_GetGamepadNameForID(event.gdevice.which))); + + LL_INFOS("SDL3") << "controller {id:" << event.gdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << "}" << LL_ENDL; + + SDL_Gamepad* controller = SDL_OpenGamepad(event.gdevice.which); + if (!controller) + { + LL_WARNS("SDL3") << "Can't open game controller: " << SDL_GetError() << LL_ENDL; + return; + } + + g_manager.addController(event.gdevice.which, guid, name); + + // this event could happen while the preferences UI is open + // in which case we need to force it to update + s_updateUI(); +} + +void onControllerDeviceRemoved(const SDL_Event& event) +{ + LL_INFOS("SDL3") << "joystick id=" << event.gdevice.which << LL_ENDL; + + SDL_JoystickID id = event.gdevice.which; + g_manager.removeController(id); + + // this event could happen while the preferences UI is open + // in which case we need to force it to update + s_updateUI(); +} + +void onControllerButton(const SDL_Event& event) +{ + g_manager.onButton(event.gbutton.which, event.gbutton.button, event.gbutton.down); +} + +void onControllerAxis(const SDL_Event& event) +{ + LL_DEBUGS("SDL3") << "joystick=0x" << std::hex << event.gaxis.which << std::dec + << " axis=" << (S32)(event.gaxis.axis) + << " value=" << (S32)(event.gaxis.value) << LL_ENDL; + g_manager.onAxis(event.gaxis.which, event.gaxis.axis, event.gaxis.value); +} + +// static +bool LLGameControl::actionFromString(const std::string& string, ActionType& actionType, U8& action) +{ + actionType = ActionType::NONE; + if(LLStringUtil::startsWith(string, "axis_")) { + actionType = ActionType::DOF; + if(string == "axis_left") + { + action = MOVE_DIR_STRAFE_LEFT; + } + else if(string == "axis_right") + { + action = MOVE_DIR_STRAFE_RIGHT; + } + else if(string == "axis_forward") + { + action = MOVE_DIR_ADVANCE; + } + else if(string == "axis_backward") + { + action = MOVE_DIR_RETREAT; + } + else if(string == "axis_turn_left") + { + action = MOVE_DIR_TURN_LEFT; + } + else if(string == "axis_turn_right") + { + action = MOVE_DIR_TURN_RIGHT; + } + else if(string == "axis_look_up") + { + action = MOVE_DIR_LOOK_UP; + } + else if(string == "axis_look_down") + { + action = MOVE_DIR_LOOK_DOWN; + } + else if(string == "axis_up") + { + action = MOVE_DIR_RISE_UP; + } + else if(string == "axis_down") + { + action = MOVE_DIR_DROP_DOWN; + } + else if(string == "axis_roll_left") + { + action = MOVE_DIR_ROLL_LEFT; + } + else if(string == "axis_roll_right") + { + action = MOVE_DIR_ROLL_RIGHT; + } + else + { + actionType = ActionType::NONE; + return false; + } + return true; + } + else if(LLStringUtil::startsWith(string, "button_")) { + actionType = ActionType::BUTTON; + if(string == "button_south") + { + action = BUTTON_SOUTH; + } + else if(string == "button_east") + { + action = BUTTON_EAST; + } + else if(string == "button_west") + { + action = BUTTON_WEST; + } + else if(string == "button_north") + { + action = BUTTON_NORTH; + } + else if(string == "button_back") + { + action = BUTTON_SELECT; + } + else if(string == "button_start") + { + action = BUTTON_START; + } + else if(string == "button_guide") + { + action = BUTTON_HOME; + } + else if(string == "button_leftstick") + { + action = BUTTON_LEFT_STICK; + } + else if(string == "button_rightstick") + { + action = BUTTON_RIGHT_STICK; + } + else if(string == "button_leftshoulder") + { + action = BUTTON_LEFT_SHOULDER; + } + else if(string == "button_rightshoulder") + { + action = BUTTON_RIGHT_SHOULDER; + } + else if(string == "button_dpad_up") + { + action = BUTTON_DPAD_UP; + } + else if(string == "button_dpad_down") + { + action = BUTTON_DPAD_DOWN; + } + else if(string == "button_dpad_left") + { + action = BUTTON_DPAD_LEFT; + } + else if(string == "button_dpad_right") + { + action = BUTTON_DPAD_RIGHT; + } + else if(string == "button_misc1") + { + action = BUTTON_MISC1; + } + else if(string == "button_paddle1") + { + action = BUTTON_PADDLE1; + } + else if(string == "button_paddle2") + { + action = BUTTON_PADDLE2; + } + else if(string == "button_paddle3") + { + action = BUTTON_PADDLE3; + } + else if(string == "button_paddle4") + { + action = BUTTON_PADDLE4; + } + else if(string == "button_touchpad") + { + action = BUTTON_TOUCHPAD; + } + else + { + actionType = ActionType::NONE; + return false; + } + return true; + } + return false; +} + +std::string LLGameControl::stringFromAction(const ActionType actionType, const U8 action) +{ + switch (actionType) + { + case ActionType::DOF: + { + switch(action) + { + case MovementDirection::MOVE_DIR_STRAFE_LEFT: return "axis_left"; + case MovementDirection::MOVE_DIR_STRAFE_RIGHT: return "axis_right"; + case MovementDirection::MOVE_DIR_ADVANCE: return "axis_forward"; + case MovementDirection::MOVE_DIR_RETREAT: return "axis_backward"; + case MovementDirection::MOVE_DIR_TURN_LEFT: return "axis_turn_left"; + case MovementDirection::MOVE_DIR_TURN_RIGHT: return "axis_turn_right"; + case MovementDirection::MOVE_DIR_LOOK_UP: return "axis_look_up"; + case MovementDirection::MOVE_DIR_LOOK_DOWN: return "axis_look_down"; + case MovementDirection::MOVE_DIR_RISE_UP: return "axis_up"; + case MovementDirection::MOVE_DIR_DROP_DOWN: return "axis_down"; + case MovementDirection::MOVE_DIR_ROLL_LEFT: return "axis_roll_left"; + case MovementDirection::MOVE_DIR_ROLL_RIGHT: return "axis_roll_right"; + } + } + case ActionType::BUTTON: + { + switch(action) + { + case BUTTON_SOUTH: return "button_south"; + case BUTTON_EAST: return "button_east"; + case BUTTON_WEST: return "button_west"; + case BUTTON_NORTH: return "button_north"; + case BUTTON_SELECT: return "button_back"; + case BUTTON_START: return "button_start"; + case BUTTON_HOME: return "button_guide"; + case BUTTON_LEFT_STICK: return "button_leftstick"; + case BUTTON_RIGHT_STICK: return "button_rightstick"; + case BUTTON_LEFT_SHOULDER: return "button_leftshoulder"; + case BUTTON_RIGHT_SHOULDER: return "button_rightshoulder"; + case BUTTON_DPAD_UP: return "button_dpad_up"; + case BUTTON_DPAD_DOWN: return "button_dpad_down"; + case BUTTON_DPAD_LEFT: return "button_dpad_left"; + case BUTTON_DPAD_RIGHT: return "button_dpad_right"; + case BUTTON_MISC1: return "button_misc1"; + case BUTTON_PADDLE1: return "button_paddle1"; + case BUTTON_PADDLE2: return "button_paddle2"; + case BUTTON_PADDLE3: return "button_paddle3"; + case BUTTON_PADDLE4: return "button_paddle4"; + case BUTTON_TOUCHPAD: return "button_touchpad"; + } + } + default: + return ""; + } + return ""; +} + +std::string LLGameControl::controllerInputStringFromAction(const ActionType actionType, const U8 action) +{ + //TODO map to recent controller + std::string aa = stringFromAction(actionType, action); + if(actionType < 2) { + LL_WARNS() << "MAP KEYBIND " << (S32)actionType << ":" << (S32)action << " = " << aa << LL_ENDL; + } + return aa; +} + +// static +bool LLGameControl::isInitialized() +{ + return g_gameControl != nullptr; +} + +// static +void LLGameControl::init(const std::string& gamecontrollerdb_path, + LoadSettingsFn loadSettings, + SaveSettingsFn saveSettings, + std::function updateUI) +{ + if (g_gameControl) + return; + + llassert(loadSettings); + llassert(saveSettings); + llassert(updateUI); + + bool result = SDL_InitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_SENSOR); + if (!result) + { + // This error is critical, we stop working with SDL and return + LL_WARNS("SDL3") << "Error initializing GameController subsystems : " << SDL_GetError() << LL_ENDL; + return; + } + + // The inability to read this file is not critical, we can continue working + if (!LLFile::isfile(gamecontrollerdb_path.c_str())) + { + LL_WARNS("SDL3") << "Device mapping db file not found: " << gamecontrollerdb_path << LL_ENDL; + } + else + { + int count = SDL_AddGamepadMappingsFromFile(gamecontrollerdb_path.c_str()); + if (count < 0) + { + LL_WARNS("SDL3") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL; + } + else + { + LL_INFOS("SDL3") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL; + } + } + + g_gameControl = LLGameControl::getInstance(); + + s_loadSettings = loadSettings; + s_saveSettings = saveSettings; + s_updateUI = updateUI; + + loadFromSettings(); +} + +// static +void LLGameControl::terminate() +{ + g_manager.clear(); +} + +// static +const std::list& LLGameControl::getDevices() +{ + return g_manager.mDevices; +} + +//static +const std::map& LLGameControl::getDeviceOptions() +{ + return g_deviceOptions; +} + +//static +void LLGameControl::computeFinalState() +{ + g_manager.accumulateInternalState(); + // Note: LLGameControllerManager::computeFinalState() calls scheduleImmediateResend() + // (pulling g_nextSend forward) as a side-effect whenever outgoing state changed. + g_manager.computeFinalState(); +} + +//static +// returns 'true' if GameControlData message needs to go out, +// which will be the case for new data or resend. Call this right +// before deciding to put a GameControlData packet on the wire +// or not. +bool LLGameControl::computeFinalStateAndCheckForChanges() +{ + computeFinalState(); + + // g_nextSend is the absolute deadline (see scheduleImmediateResend() and + // updateResendPeriod(), the only two places that set it): once it's in the + // past, the next GameControlData is due. + return g_sendToServer && (g_nextSend < (U64)totalTime()); +} + +// static +void LLGameControl::clearAllStates() +{ + g_manager.clearAllStates(); +} + +// static +void LLGameControl::processEvents(bool app_has_focus) +{ + if (!gSDLMainHandled) + { + // This logic is used by non-linux platforms which only use SDL for GameController input + SDL_Event event; + while (g_gameControl && SDL_PollEvent(&event)) + { + handleEvent(event, app_has_focus); + } + } + + if (!app_has_focus) + { + g_manager.clearAllStates(); + } +} + +void LLGameControl::handleEvent(const SDL_Event& event, bool app_has_focus) +{ + switch (event.type) + { + case SDL_EVENT_JOYSTICK_ADDED: + onJoystickDeviceAdded(event); + break; + case SDL_EVENT_JOYSTICK_REMOVED: + onJoystickDeviceRemoved(event); + break; + case SDL_EVENT_GAMEPAD_ADDED: + onControllerDeviceAdded(event); + break; + case SDL_EVENT_GAMEPAD_REMOVED: + onControllerDeviceRemoved(event); + break; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + /* FALLTHROUGH */ + case SDL_EVENT_GAMEPAD_BUTTON_UP: + if (app_has_focus) + { + onControllerButton(event); + } + break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + if (app_has_focus) + { + onControllerAxis(event); + } + break; + default: + break; + } +} + +// static +const LLGameControl::ServerState& LLGameControl::getServerState() +{ + return g_finalState; +} + +const LLGameControl::State& LLGameControl::getState() +{ + return g_innerState; +} + +// static +LLGameControl::InputChannel LLGameControl::getActiveInputChannel() +{ + InputChannel input; + + ServerState state = g_finalState; + if (state.mButtons > 0) + { + // check buttons + input.mType = LLGameControl::InputChannel::TYPE_BUTTON; + for (U8 i = 0; i < 32; ++i) + { + if ((0x1u << i) & state.mButtons) + { + input.mIndex = i; + break; + } + } + } + else + { + // scan axes + constexpr S16 threshold = std::numeric_limits::max() / 2; + for (U8 i = 0; i < 6; ++i) + { + if (abs(state.mAxes[i]) > threshold) + { + input.mType = LLGameControl::InputChannel::TYPE_AXIS; + // input.mIndex ultimately translates to a LLGameControl::KeyboardAxis + // which distinguishes between negative and positive directions + // so we must translate to axis index "i" according to the sign + // of the axis value. + input.mIndex = i; + input.mSign = state.mAxes[i] > 0 ? 1 : -1; + break; + } + } + } + + return input; +} + +// static +void LLGameControl::getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out, U32& modifiers_out) +{ + return g_manager.getFlycamInputs(inputs_out, misc_actions_out, modifiers_out); +} + +// static +void LLGameControl::setSendToServer(bool enable) +{ + g_sendToServer = enable; + // SendDataToServer lives inside the GameControl map; it is persisted along with + // the rest of that map (mode mappings, device config) when settings are saved. + ensureGameControlSettings(); + g_gameControlSettings[GC_SENDTOSERVER] = g_sendToServer; +} + +// static +bool LLGameControl::sendToServer() +{ + return g_sendToServer; +} + +// static +void LLGameControl::setServerFrameRate(F32 fps) +{ + if (fps <= 0.f) + { + g_serverFrameRate = DEFAULT_SERVER_FRAME_RATE; + return; + } + g_serverFrameRate = llmin(DEFAULT_SERVER_FRAME_RATE, llmax(fps, MIN_SERVER_FRAME_RATE)); +} + +// static +void LLGameControl::setAgentControlMode(AgentControlMode mode) +{ + // AgentControlMode is purely runtime state, auto-derived from avatar state + // every frame (see LLAgent::updateGameControlMode). It is intentionally not + // persisted: the panel's "action_mode" selector only chooses which mode's + // mappings are shown for editing, not the live mode. + g_agentControlMode = mode; +} + +// static +void LLGameControl::setFlycamEngaged(bool engaged) +{ + g_flycamEngaged = engaged; +} + +// static +LLGameControl::AgentControlMode LLGameControl::getAgentControlMode() +{ + return g_agentControlMode; +} + +// static +bool LLGameControl::isEnabled() +{ + // "Enabled" means at least one controller is connected. + // willControlAvatar()/willControlFlycam() build on this. + return !g_manager.mDevices.empty(); +} + +// static +bool LLGameControl::willControlAvatar() +{ + return isEnabled() + && (g_agentControlMode == CONTROL_MODE_AVATAR + || g_agentControlMode == CONTROL_MODE_MOUSELOOK + || g_agentControlMode == CONTROL_MODE_CAPTIVE + || g_agentControlMode == CONTROL_MODE_CURSOR) + && isModeEnabled(modeToString(g_agentControlMode)); +} + +// static +bool LLGameControl::willControlFlycam() +{ + return isEnabled() + && g_agentControlMode == CONTROL_MODE_FLYCAM + && isModeEnabled(GC_MODE_FLYCAM); +} + +// static +std::string LLGameControl::getModeName(AgentControlMode mode) +{ + return modeToString(mode); +} + +// static +LLSD LLGameControl::getDefaultModeMappings() +{ + return buildDefaultModeMappings(); +} + +// static +LLSD LLGameControl::getDefaultGameControlSettings() +{ + return buildDefaultGameControlSettings(); +} + +// static +const LLSD& LLGameControl::getGameControlSettings() +{ + ensureGameControlSettings(); + return g_gameControlSettings; +} + +// static +void LLGameControl::setGameControlSettings(const LLSD& settings) +{ + g_gameControlSettings = settings; + ensureGameControlSettings(); + // Mappings may have changed: refresh the runtime action lookup immediately. + g_manager.rebuildActionLookup(true); +} + +// static +LLSD LLGameControl::getModeMapping(const std::string& mode, const std::string& kind) +{ + ensureGameControlSettings(); + LLSD mapping = g_gameControlSettings[GC_MODEMAPPINGS][mode][kind]; + if (!mapping.isMap()) + { + // Fall back to the built-in defaults for this mode/kind. + mapping = buildDefaultModeMappings()[mode][kind]; + } + return mapping; +} + +// static +void LLGameControl::setModeMapping(const std::string& mode, const std::string& kind, + const LLSD& mapping) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][mode][kind] = mapping; + // Take effect immediately (e.g. panel "reset to defaults"), without waiting + // for OK: refresh the runtime action lookup for the active mode. + g_manager.rebuildActionLookup(true); +} + +// static +void LLGameControl::updateModeMapping(const std::string& mode, const std::string& kind, + const std::string& action, const std::string& input) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][mode][kind][action] = input; + // Take effect immediately (live panel edit), without waiting for OK: refresh + // the runtime action lookup for the active mode. + g_manager.rebuildActionLookup(true); +} + +// static +bool LLGameControl::getAxisInvert(const std::string& mode, const std::string& action) +{ + LLSD inverts = getModeMapping(mode, GC_AXES_INVERT); + return inverts.has(action) && inverts[action].asBoolean(); +} + +// static +void LLGameControl::setAxisInvert(const std::string& mode, const std::string& action, bool invert) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][mode][GC_AXES_INVERT][action] = invert; + // Take effect immediately (live panel edit), without waiting for OK: refresh + // the runtime action lookup for the active mode. + g_manager.rebuildActionLookup(true); +} + +// static +bool LLGameControl::isModeEnabled(const std::string& mode) +{ + ensureGameControlSettings(); + const LLSD& mode_map = g_gameControlSettings[GC_MODEMAPPINGS][mode]; + // Default to enabled when the flag is absent (e.g. settings saved before this + // flag existed), so pre-existing configs keep working. + if (mode_map.isMap() && mode_map.has(GC_ENABLED)) + { + return mode_map[GC_ENABLED].asBoolean(); + } + return true; +} + +// static +void LLGameControl::setModeEnabled(const std::string& mode, bool enabled) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][mode][GC_ENABLED] = enabled; + // The runtime gates (willControlAvatar/willControlFlycam/getFlycamInputs) read + // this flag live each frame, so no action-lookup rebuild is needed here. +} + +// static +bool LLGameControl::isFlycamRollAllowed() +{ + ensureGameControlSettings(); + const LLSD& mode_map = g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM]; + // Default to disallowed when the flag is absent (e.g. settings saved before + // this flag existed, or LLFlycam::mAllowRoll's own default). + return mode_map.isMap() && mode_map[GC_ALLOW_ROLL].asBoolean(); +} + +// static +void LLGameControl::setFlycamRollAllowed(bool allowed) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM][GC_ALLOW_ROLL] = allowed; + // The runtime reads this flag live each frame (LLAgentCamera::updateFlycam()), + // so no action-lookup rebuild is needed here. +} + +// static +F32 LLGameControl::getFlycamSpeedFactor() +{ + ensureGameControlSettings(); + const LLSD& mode_map = g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM]; + // Default to 1.0 (unscaled, i.e. LLFlycam::mSpeedFactor's own default) when + // the value is absent, e.g. settings saved before this flag existed. + if (mode_map.isMap() && mode_map.has(GC_SPEED_FACTOR)) + { + return (F32)mode_map[GC_SPEED_FACTOR].asReal(); + } + return 1.0f; +} + +// static +void LLGameControl::setFlycamSpeedFactor(F32 speed_factor) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM][GC_SPEED_FACTOR] = speed_factor; + // The runtime reads this value live each frame (LLAgentCamera::updateFlycam()), + // so no action-lookup rebuild is needed here. +} + +// static +bool LLGameControl::isFlycamCrosshairEnabled() +{ + ensureGameControlSettings(); + const LLSD& mode_map = g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM]; + // Default to disabled when the flag is absent (e.g. settings saved before + // this flag existed). + return mode_map.isMap() && mode_map[GC_SHOW_CROSSHAIR].asBoolean(); +} + +// static +void LLGameControl::setFlycamCrosshairEnabled(bool enabled) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM][GC_SHOW_CROSSHAIR] = enabled; + // The runtime reads this flag live each frame (LLViewerWindow::draw()), + // so no action-lookup rebuild is needed here. +} + +// static +std::string LLGameControl::getDeviceConfig(const std::string& guid) +{ + ensureGameControlSettings(); + const LLSD& devices = g_gameControlSettings[GC_DEVICES]; + return devices.has(guid) ? devices[guid][GC_CONFIG].asString() : LLStringUtil::null; +} + +// static +void LLGameControl::setDeviceConfig(const std::string& guid, const std::string& config) +{ + ensureDeviceEntry(guid); + g_gameControlSettings[GC_DEVICES][guid][GC_CONFIG] = config; +} + + +// static +LLGameControl::ActionNameType LLGameControl::getActionNameType(const std::string& action) +{ + return g_manager.getActionNameType(action); +} + +// static +// +// Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel +// If the axis name lacks the +/- postfix it assumes '+' postfix. +LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& name) +{ + LLGameControl::InputChannel channel; + + // 'name' has two acceptable formats: AXIS_[sign] or BUTTON_ + if (LLStringUtil::startsWith(name, "AXIS_")) + { + channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS; + // Decimal postfix is only one character + channel.mIndex = atoi(name.substr(5, 1).c_str()); + // AXIS_n can have an optional +/- at index 6 + // Assume positive axis when sign not provided + channel.mSign = name.back() == '-' ? -1 : 1; + } + else if (LLStringUtil::startsWith(name, "BUTTON_")) + { + channel.mType = LLGameControl::InputChannel::Type::TYPE_BUTTON; + // Decimal postfix is only one or two characters + channel.mIndex = atoi(name.substr(7).c_str()); + } + + return channel; +} + +// static +U8 LLGameControl::axisOutputFromName(const std::string& name) +{ + if (name == INPUT_AXIS_TRIGGERS) + { + return AXIS_OUTPUT_TRIGGER_PAIR; + } + InputChannel channel = channelFromInputName(name); + if (channel.isAxis() && channel.mIndex < NUM_AXES) + { + return channel.mIndex; + } + return AXIS_OUTPUT_NONE; // "AXIS_NONE" or anything unrecognized +} + +// static +std::string LLGameControl::axisOutputName(U8 code) +{ + if (code == AXIS_OUTPUT_TRIGGER_PAIR) + { + return INPUT_AXIS_TRIGGERS; + } + if (code < NUM_AXES) + { + return InputChannel(InputChannel::TYPE_AXIS, code).getRemoteName(); + } + return "AXIS_NONE"; +} + +// static +LLGameControl::AgentActions LLGameControl::computeAgentActions() +{ + return g_manager.computeAgentActions(); +} + +// static +void LLGameControl::setExternalInput(U32 action_flags, U32 buttons, bool is_running) +{ + g_manager.setExternalInput(action_flags, buttons, is_running); +} + +// static +void LLGameControl::setMouseCursorPosition(S32 pixel_x, S32 pixel_y, S32 rect_width, S32 rect_height) +{ + g_manager.setMouseCursorPosition(pixel_x, pixel_y, rect_width, rect_height); +} + +//static +void LLGameControl::updateResendPeriod() +{ + // we expect this method to be called right after data is sent + g_finalState.mPrevAxes = g_finalState.mAxes; + g_finalState.mPrevSemanticAxes = g_finalState.mSemanticAxes; + // resend starts at FIRST_RESEND_PERIOD and expands by RESEND_EXPANSION_RATE on each resend + if (g_nextResendPeriod == 0) + { + g_nextResendPeriod = llmax(FIRST_RESEND_PERIOD, getMinSendPeriod()); + } + else + { + g_nextResendPeriod *= RESEND_EXPANSION_RATE; + } + g_nextSend = (U64)totalTime() + std::max(g_nextResendPeriod, getMinSendPeriod()); +} + +//static +U8 LLGameControl::getNextPacketNum() +{ + return g_packetNum++; +} + +//static +U8 LLGameControl::numSemanticAxesForMode(AgentControlMode mode) +{ + switch (mode) + { + case CONTROL_MODE_AVATAR: + case CONTROL_MODE_MOUSELOOK: + case CONTROL_MODE_CAPTIVE: + return NUM_SEMANTIC_AXES; + case CONTROL_MODE_CURSOR: + return NUM_CURSOR_SEMANTIC_AXES; + case CONTROL_MODE_FLYCAM: + return FLYCAM_NUM_CHANNELS; + case CONTROL_MODE_NONE: + default: + return 0; + } +} + +// static +U8 LLGameControl::numSemanticButtonsForMode(AgentControlMode mode) +{ + if (mode == CONTROL_MODE_NONE) + { + return 0; + } + U8 count = 0; + for (const auto& label_and_slot : getDefaultButtonSemanticSlots(mode)) + { + U8 slot = label_and_slot.second; + if ((U8)(slot + 1) > count) + { + count = (U8)(slot + 1); + } + } + return count; +} + +// static +std::string LLGameControl::semanticAxisName(AgentControlMode mode, U8 slot) +{ + static const char* const AVATAR_FAMILY_NAMES[NUM_SEMANTIC_AXES] = + { "AXIS_STRAFE", "AXIS_ADVANCE", "AXIS_TURN", "AXIS_LOOK", "AXIS_RISE" }; + static const char* const CURSOR_NAMES[NUM_CURSOR_SEMANTIC_AXES] = + { "CURSOR_DX", "CURSOR_DY", "CURSOR_PX", "CURSOR_PY", "CURSOR_NX", "CURSOR_NY" }; + static const char* const FLYCAM_NAMES[FLYCAM_NUM_CHANNELS] = + { "FLYCAM_AXIS_TRUCK", "FLYCAM_AXIS_DOLLY", "FLYCAM_AXIS_PAN", "FLYCAM_AXIS_TILT", + "FLYCAM_AXIS_BOOM", "FLYCAM_AXIS_ROLL", "FLYCAM_AXIS_ZOOM" }; + + switch (mode) + { + case CONTROL_MODE_AVATAR: + case CONTROL_MODE_MOUSELOOK: + case CONTROL_MODE_CAPTIVE: + if (slot < NUM_SEMANTIC_AXES) + { + return AVATAR_FAMILY_NAMES[slot]; + } + break; + case CONTROL_MODE_CURSOR: + if (slot < NUM_CURSOR_SEMANTIC_AXES) + { + return CURSOR_NAMES[slot]; + } + break; + case CONTROL_MODE_FLYCAM: + if (slot < FLYCAM_NUM_CHANNELS) + { + return FLYCAM_NAMES[slot]; + } + break; + case CONTROL_MODE_NONE: + default: + break; + } + return LLStringUtil::null; +} + +// static +std::string LLGameControl::semanticButtonName(AgentControlMode mode, U8 slot) +{ + if (mode == CONTROL_MODE_NONE || slot >= numSemanticButtonsForMode(mode)) + { + return LLStringUtil::null; + } + + // Find the action 'mode's default mapping assigned this semantic slot to + // (getDefaultButtonSemanticSlots() maps each default-bound action label to its + // stable slot), e.g. "Toggle sit" for Avatar slot 2 (BUTTON_WEST). Slots the + // default mapping leaves unassigned fall back to a generic name. + for (const auto& label_and_slot : getDefaultButtonSemanticSlots(mode)) + { + if (label_and_slot.second == slot) + { + return label_and_slot.first; + } + } + return llformat("MODE_BUTTON_%d", (S32)slot); +} + +// static +bool LLGameControl::parseDeviceOptions(const std::string& options, std::string& name, + std::vector& axis_options, + std::vector& axis_map, std::vector& button_map) +{ + if (options.empty()) + return false; + + name.clear(); + axis_options.resize(NUM_AXES); + axis_map.resize(NUM_AXES); + button_map.resize(NUM_BUTTONS); + + for (size_t i = 0; i < NUM_AXES; ++i) + { + axis_options[i].resetToDefaults(); + axis_map[i] = (U8)i; + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + button_map[i] = (U8)i; + } + + std::map pairs; + if (!parse(pairs, options)) + { + LL_WARNS("SDL3") << "Invalid options: '" << options << "'" << LL_ENDL; + return false; + } + + std::map axis_string_options; + if (!parse(axis_string_options, pairs["axis_options"])) + { + LL_WARNS("SDL3") << "Invalid axis_options: '" << pairs["axis_options"] << "'" << LL_ENDL; + return false; + } + + std::map axis_string_map; + if (!parse(axis_string_map, pairs["axis_map"])) + { + LL_WARNS("SDL3") << "Invalid axis_map: '" << pairs["axis_map"] << "'" << LL_ENDL; + return false; + } + + std::map button_string_map; + if (!parse(button_string_map, pairs["button_map"])) + { + LL_WARNS("SDL3") << "Invalid button_map: '" << pairs["button_map"] << "'" << LL_ENDL; + return false; + } + + name = pairs["name"]; + + for (size_t i = 0; i < NUM_AXES; ++i) + { + std::string key = std::to_string(i); + + std::string one_axis_options = axis_string_options[key]; + if (!one_axis_options.empty()) + { + axis_options[i].loadFromString(one_axis_options); + } + + std::string value = axis_string_map[key]; + if (!value.empty()) + { + U32 number; + // Output codes cover the canonical axes plus the None and trigger-pair + // sentinels (see LLGameControl::AXIS_OUTPUT_*). + if (!LLStringUtil::convertToU32(value, number) || number >= NUM_AXIS_OUTPUTS + || std::to_string(number) != value) + { + LL_WARNS("SDL3") << "Invalid axis mapping: " << i << "->" << value << LL_ENDL; + } + else + { + axis_map[i] = (U8)number; + } + } + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + std::string value = button_string_map[std::to_string(i)]; + if (!value.empty()) + { + U32 number; + if (!LLStringUtil::convertToU32(value, number) || number >= NUM_BUTTONS + || std::to_string(number) != value) + { + LL_WARNS("SDL3") << "Invalid button mapping: " << i << "->" << value << LL_ENDL; + } + else + { + button_map[i] = (U8)number; + } + } + } + + return true; +} + +// static +std::string LLGameControl::stringifyDeviceOptions(const std::string& name, + const std::vector& axis_options, + const std::vector& axis_map, const std::vector& button_map, + bool force_empty) +{ + std::list options; + + auto opts2str = [](size_t i, const Options::AxisOptions& options) -> std::string + { + std::string string = options.saveToString(); + return string.empty() ? string : llformat("%u:%s", i, string.c_str()); + }; + + std::string axis_options_string = LLStringUtil::join, Options::AxisOptions>(axis_options, opts2str); + if (!axis_options_string.empty()) + { + options.push_back("axis_options:{" + axis_options_string + "}"); + } + + auto map2str = [](size_t index, const U8& value) -> std::string + { + return value == index ? LLStringUtil::null : llformat("%u:%u", index, value); + }; + + std::string axis_map_string = LLStringUtil::join, U8>(axis_map, map2str); + if (!axis_map_string.empty()) + { + options.push_back("axis_map:{" + axis_map_string + "}"); + } + + std::string button_map_string = LLStringUtil::join, U8>(button_map, map2str); + if (!button_map_string.empty()) + { + options.push_back("button_map:{" + button_map_string + "}"); + } + + if (!force_empty && options.empty()) + return LLStringUtil::null; + + // Remove control characters [',', '{', '}'] from name + std::string safe_name; + safe_name.reserve(name.size()); + for (char c : name) + { + if (c != ',' && c != '{' && c != '}') + { + safe_name.push_back(c); + } + } + options.push_front(llformat("name:%s", safe_name.c_str())); + + std::string result = LLStringUtil::join(options); + + return "{" + result + "}"; +} + +// static +void LLGameControl::initByDefault() +{ + g_sendToServer = false; + g_agentControlMode = CONTROL_MODE_AVATAR; + g_gameControlSettings = buildDefaultGameControlSettings(); + g_manager.resetDeviceOptionsToDefaults(); + g_deviceOptions.clear(); +} + +// static +const std::vector& LLGameControl::getSettingKeys() +{ + static const std::vector keys = { + SETTING_GAMECONTROL + }; + return keys; +} + +// static +LLSD LLGameControl::getSettingsAsLLSD() +{ + ensureGameControlSettings(); + + // Fold each device's serialized options into GameControl/Devices//Config. + g_manager.rememberDeviceOptions(); // refresh g_deviceOptions from current device state + for (const auto& [guid, options] : g_deviceOptions) + { + if (!options.empty()) + { + g_gameControlSettings[GC_DEVICES][guid][GC_CONFIG] = options; + } + } + + g_gameControlSettings[GC_SENDTOSERVER] = g_sendToServer; + + LLSD result = LLSD::emptyMap(); + result[SETTING_GAMECONTROL] = g_gameControlSettings; + return result; +} + +// static +void LLGameControl::applySettingsFromLLSD(const LLSD& settings) +{ + if (settings.has(SETTING_GAMECONTROL)) g_gameControlSettings = settings[SETTING_GAMECONTROL]; + ensureGameControlSettings(); + + // SendDataToServer is nested inside the GameControl map. + if (g_gameControlSettings.has(GC_SENDTOSERVER)) + g_sendToServer = g_gameControlSettings[GC_SENDTOSERVER].asBoolean(); + + // Rebuild the device-options cache from GameControl/Devices//Config and + // apply it to the connected devices. + g_deviceOptions.clear(); + const LLSD& devices = g_gameControlSettings[GC_DEVICES]; + for (auto it = devices.beginMap(); it != devices.endMap(); ++it) + { + if (it->first == GC_DEFAULT_DEVICE) + continue; // the "Default" template is not a real device + std::string config = it->second[GC_CONFIG].asString(); + if (!config.empty()) + { + g_deviceOptions.emplace(it->first, config); + } + } + g_manager.applyRememberedDeviceOptions(); + + // ModeMappings may have changed (e.g. live edits in the preferences panel); + // force the runtime action lookup to rebuild for the active mode. + g_manager.rebuildActionLookup(true); +} + +// static +void LLGameControl::loadFromSettings() +{ + applySettingsFromLLSD(s_loadSettings(getSettingKeys())); +} + +// static +void LLGameControl::saveToSettings() +{ + s_saveSettings(getSettingsAsLLSD()); +} + +// static +void LLGameControl::setDeviceOptions(const std::string& guid, const Options& options) +{ + g_manager.setDeviceOptions(guid, options); +} + +static bool mapLocalStringToTypeAndIndex(const std::string control, LLGameControl::InputChannel::Type& cType, U8& cIndex) +{ + // HACK: needs proper method to map strings to Input Types and Indexes + for(U8 i = 0;i < 8;i++) + { + if(control == ("AXIS_" + std::to_string((U32)i))) + { + cIndex = i; + cType = LLGameControl::InputChannel::Type::TYPE_AXIS; + return true; + } + } + + for(U8 i = 0;i < 32;i++) + { + if(control == ("BUTTON_" + std::to_string((U32)i))) + { + cIndex = i; + cType = LLGameControl::InputChannel::Type::TYPE_BUTTON; + return true; + } + } + // /HACK + return false; +} + +// virtual, from LLGameControllerBindingToStringHandler +std::string LLGameControl::getBindingAsString(const std::string& control) const +{ + if(!hasHandlingDevice()) { + return std::string(); + } + + InputChannel::Type cType = InputChannel::Type::TYPE_NONE; + U8 cIndex = 255; + if(!mapLocalStringToTypeAndIndex(control, cType, cIndex)) + { + return std::string(); + } + + if(cType == InputChannel::Type::TYPE_NONE) + { + return std::string(); + } + + const LLGameControl::Device* device = g_manager.getLastActiveDevice(); + + if(!device) + { + return std::string(); + } + + SDL_Gamepad* gp = SDL_GetGamepadFromID(device->getJoystickID()); + + if(cType == InputChannel::Type::TYPE_AXIS) + { + U8 axis = device->getOptions().unmapAxis(cIndex); + return std::to_string((SDL_GamepadAxis)axis); + } + else + { + U8 button = device->getOptions().unmapButton(cIndex); + if(button < 4) + { + return std::to_string(SDL_GetGamepadButtonLabel(gp, (SDL_GamepadButton)button)); + } + return std::to_string((SDL_GamepadButton)button); + + } +} + +// virtual, from LLGameControllerBindingToStringHandler +bool LLGameControl::hasHandlingDevice() const +{ + return !g_manager.mDevices.empty(); +} diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h new file mode 100644 index 00000000000..e34d3b1badc --- /dev/null +++ b/indra/llwindow/llgamecontrol.h @@ -0,0 +1,707 @@ +/** + * @file llgamecontrol.h + * @brief GameController detection and management + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include + + +#include "llerror.h" +#include "llsingleton.h" +#include "stdtypes.h" +#include "SDL3/SDL_events.h" + +// For reference, here are the RAW indices of the various input channels +// of a standard XBox controller. Button (N) is numbered in parentheses, +// whereas axisN has N+ and N- labels. +// +// leftpaddle rightpaddle +// _______ _______ +// / 4+ '-. .-' 5+ \. +// leftshoulder _(9)_________'-.____ ____.-'_________(10) rightshoulder +// / _________ \_________/ \. +// / / 1- \ (3) \. +// | | | (4) (5) (6) N | +// | |0- (7) 0+| _________ (2)W E(1) | +// | | | / 3- \ S | +// | | 1+ | | | (0) | +// | \_________/ |2- (8) 2+| | +// | leftstick (11) | | | +// | (13) (14) | 3+ | | +// | (12) \_________/ | +// | d-pad rightstick | +// | ____________________ | +// | / \ | +// | / \ | +// | / \ | +// \__________/ \__________/ +// +// Note: the analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD +// whereas those directions are actually POSITIVE in SL's local right-handed +// reference frame. Rather than hard-coding a sign flip in the raw-axis +// extraction (LLGameControllerManager::onAxis()), this is handled by the +// per-mode, per-action Invert flag (see getAxisInvert/setAxisInvert): the +// built-in default mappings set Invert on whichever actions need it to feel +// correct by default (e.g. "Strafe left/right", "Truck left/right"). + +// Interface to get controller binding from assigned command +class LLGameControllerBindingToStringHandler +{ +public: + virtual std::string getBindingAsString(const std::string& control) const = 0; + virtual bool hasHandlingDevice() const = 0; +}; + +// LLGameControl is a singleton with pure static public interface +class LLGameControl : public LLSingleton, public LLGameControllerBindingToStringHandler +{ + LLSINGLETON_EMPTY_CTOR(LLGameControl); + virtual ~LLGameControl(); + LOG_CLASS(LLGameControl); + +public: + enum AgentControlMode + { + CONTROL_MODE_AVATAR, + CONTROL_MODE_MOUSELOOK, // Avatar camera is in mouselook + CONTROL_MODE_FLYCAM, + CONTROL_MODE_CAPTIVE, // Avatar is sitting, or controls have been taken + CONTROL_MODE_CURSOR, // Left stick drives the on-screen cursor instead of avatar movement + CONTROL_MODE_NONE + }; + + enum ActionType: U8 + { + DOF, + BUTTON, + NONE = 255, + }; + + enum MovementDirection: U8 + { + MOVE_DIR_STRAFE_LEFT, + MOVE_DIR_STRAFE_RIGHT, + MOVE_DIR_ADVANCE, + MOVE_DIR_RETREAT, + MOVE_DIR_TURN_LEFT, + MOVE_DIR_TURN_RIGHT, + MOVE_DIR_LOOK_UP, + MOVE_DIR_LOOK_DOWN, + MOVE_DIR_RISE_UP, + MOVE_DIR_DROP_DOWN, + MOVE_DIR_ROLL_LEFT, + MOVE_DIR_ROLL_RIGHT, + NUM_MOVE_DIRS, + }; + + enum ActionNameType + { + ACTION_NAME_UNKNOWN, + ACTION_NAME_ANALOG, // avatar movement, e.g. "push" + ACTION_NAME_BINARY, // maps to button, e.g. "stop" + ACTION_NAME_FLYCAM // E.g., "zoom" + }; + + enum KeyboardAxis : U8 + { + AXIS_LEFTX, + AXIS_LEFTY, + AXIS_RIGHTX, + AXIS_RIGHTY, + AXIS_LEFT_TRIGGER, + AXIS_RIGHT_TRIGGER, + NUM_AXES + }; + + // Fixed order of the GameControlData message's ModeAxes block for Avatar/ + // Mouselook/Captive: the avatar-movement intent an axis (or button) is currently + // bound to, independent of which physical canonical axis/button that happens to be + // for the active mapping. + enum SemanticAxis : U8 + { + SEMANTIC_AXIS_STRAFE, // STRAFE_LEFT_RIGHT + SEMANTIC_AXIS_ADVANCE,// ADVANCE_FORWARD_BACK ("Advance forward/back") + SEMANTIC_AXIS_TURN, // TURN_LEFT_RIGHT + SEMANTIC_AXIS_LOOK, // LOOK_UP_DOWN + SEMANTIC_AXIS_RISE, // RISE_UP_DOWN ("Fly up/down") + NUM_SEMANTIC_AXES + }; + + // CONTROL_MODE_CURSOR's ModeAxes block does NOT reuse SemanticAxis's Turn/Look/Rise + // slots, despite the fact turning/looking still work in Cursor mode (via the ordinary + // Turn/Look axis bridge in computeAgentActions(), independent of this wire mirror), + // they're just no longer reported here. + // 0 CURSOR_DX // input driving mouse horizontal movement + // 1 CURSOR_DY // input driving mouse vertical movement + // 2 CURSOR_PX // mouse horizontal position in pixels from upper-left corner + // 3 CURSOR_PY // mouse vertical position in pixels from upper-left corner + // 4 CURSOR_NX // mouse normalized horizontal position [0,1] from left-edge + // 5 CURSOR_NY // mouse normalized vertical position [0,1] from upper-edge + static constexpr U8 NUM_CURSOR_SEMANTIC_AXES = 6; + + enum Button : U8 + { + BUTTON_SOUTH, + BUTTON_EAST, + BUTTON_WEST, + BUTTON_NORTH, + BUTTON_SELECT, // BUTTON_BACK in SDL + BUTTON_HOME, // BUTTON_GUIDE in SDL + BUTTON_START, + BUTTON_LEFT_STICK, + BUTTON_RIGHT_STICK, + BUTTON_LEFT_SHOULDER, + BUTTON_RIGHT_SHOULDER, // 10 + BUTTON_DPAD_UP, + BUTTON_DPAD_DOWN, + BUTTON_DPAD_LEFT, + BUTTON_DPAD_RIGHT, + BUTTON_MISC1, + BUTTON_PADDLE1, + BUTTON_PADDLE2, + BUTTON_PADDLE3, + BUTTON_PADDLE4, + BUTTON_TOUCHPAD, // 20 + BUTTON_21, + BUTTON_22, + BUTTON_23, + BUTTON_24, + BUTTON_25, + BUTTON_26, + BUTTON_27, + BUTTON_28, + BUTTON_29, + BUTTON_30, + BUTTON_31, + NUM_BUTTONS + }; + + // Order in which flycam channel values are packed by getFlycamInputs() and + // consumed by LLAgent::updateFlycam(). + enum FlycamChannel : U8 + { + FLYCAM_TRUCK = 0, // strafe left/right + FLYCAM_DOLLY, // advance forward/back + FLYCAM_PAN, // yaw left/right + FLYCAM_TILT, // pitch up/down + FLYCAM_BOOM, // rise up/down + FLYCAM_ROLL, + FLYCAM_ZOOM, + FLYCAM_NUM_CHANNELS + }; + + // GameControlData's ModeAxes/ModeButtons blocks use whichever mode-specific slot + // order matches the sender's ActionMode: Avatar/Mouselook/Captive use SemanticAxis + // (NUM_SEMANTIC_AXES slots, in that order); Cursor uses its own unrelated + // NUM_CURSOR_SEMANTIC_AXES-slot layout (CURSOR_DX/DY/PX/PY/NX/NY, see + // NUM_CURSOR_SEMANTIC_AXES); FlyCam uses FlycamChannel (FLYCAM_NUM_CHANNELS slots, + // in that order); CONTROL_MODE_NONE uses none (always zero/empty). ServerState + // reserves enough slots for the largest of these so any mode's data fits without + // resizing per-frame -- see numSemanticAxesForMode(). + static constexpr U8 NUM_SEMANTIC_SLOTS = + (U8)FLYCAM_NUM_CHANNELS > (U8)NUM_SEMANTIC_AXES ? (U8)FLYCAM_NUM_CHANNELS : (U8)NUM_SEMANTIC_AXES; + + // Number of axes that are ever packed for 'mode' (e.g. the number of known + // mappable axes for that mode). + // into GameControlData's ModeAxes block for 'mode' (see message_template.msg's + // GameControlData doc): + // NUM_SEMANTIC_AXES for Avatar/Mouselook/Captive, + // NUM_CURSOR_SEMANTIC_AXES for Cursor (CURSOR_DX/DY/PX/PY/NX/NY), + // FLYCAM_NUM_CHANNELS for FlyCam, + // 0 for CONTROL_MODE_NONE. + static U8 numSemanticAxesForMode(AgentControlMode mode); + + // Number of leading bits of ServerState::mSemanticButtons that are ever set for + // 'mode' (e.g. the number of known mappable actions for that mode). + static U8 numSemanticButtonsForMode(AgentControlMode mode); + + // Display name for ServerState::mSemanticAxes[slot] under 'mode', matching the + // per-mode axis tables documented in message_template.msg's GameControlData doc + // (e.g. "AXIS_STRAFE", "FLYCAM_AXIS_TRUCK", "CURSOR_DX"). Empty if slot >= + // numSemanticAxesForMode(mode). + static std::string semanticAxisName(AgentControlMode mode, U8 slot); + + // Display name for ServerState::mSemanticButtons bit 'slot' under 'mode': the + // default-mapped action's label if 'mode's default button mapping assigns that + // slot's canonical button to a non-movement action (e.g. "Toggle sit"), else a + // generic "MODE_BUTTON_" fallback -- mirroring how canonical buttons fall back + // to BUTTON_ (InputChannel::getRemoteName()) when unnamed. Empty if slot >= + // numSemanticButtonsForMode(mode). + static std::string semanticButtonName(AgentControlMode mode, U8 slot); + + // Axis-map output codes stored in Options::mAxisMap (one per physical axis): + // 0 .. NUM_AXES-1 maps 1:1 to that canonical axis + // AXIS_OUTPUT_NONE physical axis is unmapped / disabled + // AXIS_OUTPUT_TRIGGER_PAIR physical axis fans out to the canonical trigger + // pair: its negative half drives the LEFT_TRIGGER + // channel, its positive half the RIGHT_TRIGGER channel. + // The trigger pair is a virtual bidirectional axis (left = negative side, + // right = positive side) shared with the Actions-tab "Triggers left/right" input. + static constexpr U8 AXIS_OUTPUT_NONE = NUM_AXES; + static constexpr U8 AXIS_OUTPUT_TRIGGER_PAIR = NUM_AXES + 1; + static constexpr U8 NUM_AXIS_OUTPUTS = NUM_AXES + 2; + + static const U16 MAX_AXIS_DEAD_ZONE = 16384; + static const U16 MAX_AXIS_OFFSET = 16384; + + // Default dead zone applied to a freshly-constructed / reset axis + // is nonzero because most controllers have a bit of hysteresis or offset. + static constexpr U16 DEFAULT_AXIS_DEAD_ZONE = 2048; + + class InputChannel + { + public: + enum Type + { + TYPE_NONE, + TYPE_AXIS, + TYPE_BUTTON + }; + + InputChannel() {} + InputChannel(Type type, U8 index) : mType(type), mIndex(index) {} + InputChannel(Type type, U8 index, S32 sign) : mType(type), mSign(sign), mIndex(index) {} + + // these methods for readability + bool isNone() const { return mType == TYPE_NONE; } + bool isAxis() const { return mType == TYPE_AXIS; } + bool isButton() const { return mType == TYPE_BUTTON; } + + bool isEqual(const InputChannel& other) + { + return mType == other.mType && mSign == other.mSign && mIndex == other.mIndex; + } + + std::string getLocalName() const; // AXIS_0, AXIS_1, BUTTON_0, NONE etc. + std::string getSignedLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, NONE etc. + std::string getRemoteName() const; // AXIS_LEFTX, BUTTON_SOUTH, etc + + Type mType { TYPE_NONE }; + S32 mSign { 0 }; + U8 mIndex { 255 }; + }; + + // Options is a data structure for storing device-specific settings + class Options + { + public: + struct AxisOptions + { + S32 mMultiplier = 1; + U16 mDeadZone { DEFAULT_AXIS_DEAD_ZONE }; + S16 mOffset { 0 }; + + void resetToDefaults(); + + S16 computeModifiedValue(S16 raw_value) const; + + std::string saveToString() const; + void loadFromString(std::string options); + }; + + Options(); + + void resetToDefaults(); + + U8 mapAxis(U8 axis) const; + U8 mapButton(U8 button) const; + + U8 unmapAxis(U8 axis) const; + U8 unmapButton(U8 button) const; + + S16 fixAxisValue(U8 axis, S16 value) const; + + std::string saveToString(const std::string& name, bool force_empty = false) const; + bool loadFromString(std::string& name, std::string options); + bool loadFromString(std::string options); + + const std::vector& getAxisOptions() const { return mAxisOptions; } + std::vector& getAxisOptions() { return mAxisOptions; } + const std::vector& getAxisMap() const { return mAxisMap; } + std::vector& getAxisMap() { return mAxisMap; } + const std::vector& getButtonMap() const { return mButtonMap; } + std::vector& getButtonMap() { return mButtonMap; } + + private: + std::vector mAxisOptions; + std::vector mAxisMap; + std::vector mButtonMap; + }; + + // State is a minimal class for storing axes and buttons values + class State + { + public: + State(); + void clear(); + void storePrevious(); + bool onButton(U8 button, bool pressed); + std::vector mAxes; // [ 0 , 32767 ] post-fix (dead zone/offset/invert applied), split into +/- half-axes + std::vector mRawAxes; // [ 0 , 32767 ] pre-fix values, same +/- half-axis layout as mAxes + + // Pre-map (physical-axis-indexed) copies of the per-axis values, kept only for + // the preferences Device-State tab. onAxis() re-keys mAxes/mRawAxes by canonical + // output axis (mapAxis) for the input pipeline, discarding the physical index; the + // tab's rows are physical-axis-indexed (their Invert/Offset/Dead-Zone options are), + // so it reads these to show Raw/Adjusted on the matching row. Signed [ -32768, + // 32767 ], one entry per physical axis (NUM_AXES), not split into half-axes. + std::vector mPhysicalRawAxes; // pre-fix, indexed by physical axis + std::vector mPhysicalFixedAxes; // post-fix, indexed by physical axis + + U32 mButtons; + + // Pre-map button state, keyed by physical button index (same reason as the + // mPhysical*Axes above): onButton() re-keys mButtons by canonical button + // (mapButton) for the input pipeline, and the Device-State tab's rows are + // physical buttons, so it reads this to show each row's own pressed state. + U32 mPhysicalButtons; + + std::vector mPrevAxes; + U32 mPrevButtons; + }; + + class ServerState + { + public: + ServerState(); + void clear(); + std::vector mAxes; // [ -32768, 32767 ] + std::vector mPrevAxes; // value in last outgoing packet + U32 mButtons; + U32 mPrevButtons; + + // Semantic re-encoding of the above (see NUM_SEMANTIC_SLOTS): + // mSemanticAxes is indexed by SemanticAxis or FlycamChannel, whichever matches + // mActionMode (unused trailing slots are zero); mSemanticButtons is a bitmask + // of the active mode's default-mapped action slots (see + // numSemanticButtonsForMode()/semanticButtonName()), each bit reflecting + // whether that action is currently active regardless of which physical button + // it's presently bound to. mActionMode mirrors LLGameControl::AgentControlMode. + std::vector mSemanticAxes; + std::vector mPrevSemanticAxes; + U32 mSemanticButtons; + U32 mPrevSemanticButtons; + U8 mActionMode; + U8 mPrevActionMode; + }; + + // Device is a data structure for describing any detected controller + class Device + { + const int mJoystickID { -1 }; + const std::string mGUID; + const std::string mName; + Options mOptions; + State mState; + + public: + Device(int joystickID, const std::string& guid, const std::string& name); + int getJoystickID() const { return mJoystickID; } + std::string getGUID() const { return mGUID; } + std::string getName() const { return mName; } + const Options& getOptions() const { return mOptions; } + const State& getState() const { return mState; } + + void resetOptionsToDefaults() { mOptions.resetToDefaults(); } + std::string saveOptionsToString(bool force_empty = false) const { return mOptions.saveToString(mName, force_empty); } + void loadOptionsFromString(const std::string& options) { mOptions.loadFromString(options); } + + friend class LLGameControllerManager; + }; + + static bool actionFromString(const std::string& string, ActionType& actionType, U8& action); + static std::string stringFromAction(const ActionType actionType, U8 action); + static std::string controllerInputStringFromAction(const ActionType actionType, U8 action); + + static F32 getControllerHeldTime(ActionType actionType, U8 action); + static S32 getControllerHeldFrames(ActionType actionType, U8 action); + + static bool isInitialized(); + + // Bulk settings I/O is delegated to the host so this library does not + // need to know about LLControlGroup at compile time. + // loadSettings(keys) -- returns an LLSD map of the requested keys. + // Keys absent from the host's store are + // omitted from the returned map. + // saveSettings(key_values) -- writes every entry of the LLSD map. + using LoadSettingsFn = std::function&)>; + using SaveSettingsFn = std::function; + + static void init(const std::string& gamecontrollerdb_path, + LoadSettingsFn loadSettings, + SaveSettingsFn saveSettings, + std::function updateUI); + static void terminate(); + + static const std::list& getDevices(); + static const std::map& getDeviceOptions(); + + // returns 'true' if GameControlData message needs to go out, + // which will be the case for new data or resend (throttled to never fire + // faster than the server's expected max frame rate). Call this right + // before deciding to put a GameControlData packet on the wire + // or not. + static bool computeFinalStateAndCheckForChanges(); + static void computeFinalState(); + + static void clearAllStates(); + + static void processEvents(bool app_has_focus = true); + static void handleEvent(const SDL_Event& event, bool app_has_focus); + static const ServerState& getServerState(); + static const State& getState(); + static InputChannel getActiveInputChannel(); + + // Packs the FlyCam-mode DOF channels (order per FlycamChannel) into + // 'inputs_out'. FlycamMiscAction commands (e.g. FLYCAM_ACTION_UNROLL) are + // discrete one-shot commands rather than per-frame DOF contributions, so + // they are reported separately via 'misc_actions_out': the bitmask of + // FlycamMiscAction values whose bound button transitioned from + // not-pressed to pressed this frame. FlycamModifier values (e.g. + // FLYCAM_MODIFIER_ORBIT) are level-triggered instead: 'modifiers_out' is + // the bitmask of FlycamModifier values whose bound button is held down + // this frame. + static void getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out, U32& modifiers_out); + + // these methods for accepting input from keyboard + static void setSendToServer(bool enable); + static void setAgentControlMode(AgentControlMode mode); + + // Whether FlyCam is concurrently engaged underneath the reported + // AgentControlMode. Normally that's implied by mode == CONTROL_MODE_FLYCAM, + // but Cursor mode takes precedence over FlyCam for reporting purposes (see + // LLAgent::updateGameControlMode()), so this is how the input-mapping lookup + // still finds out FlyCam is active while mode reports CONTROL_MODE_CURSOR -- + // in that case, axes/buttons not claimed by Cursor mode's own mouse-cursor + // actions fall back to FlyCam mode's own configured mappings instead of + // Avatar's Turn/Look/Fly. Call every frame alongside setAgentControlMode(). + static void setFlycamEngaged(bool engaged); + + // Called whenever a fresh SimStats report gives us the simulator's current + // frame rate, so the GameControlData outgoing throttle (see + // computeFinalStateAndCheckForChanges() above) can track it instead of + // assuming a fixed expected rate. 'fps' <= 0 is ignored (falls back to the + // default expected rate). + static void setServerFrameRate(F32 fps); + + static bool sendToServer(); + static AgentControlMode getAgentControlMode(); + + // Runtime gating for the local-control path. "Enabled" now means simply + // "a controller is connected" -- the feature no longer has a separate on/off + // toggle (see the "GameControl always enabled" change). willControlAvatar() + // and willControlFlycam() additionally gate on the active AgentControlMode. + static bool isEnabled(); // a controller is connected + static bool willControlAvatar();// enabled AND mode is Avatar/Mouselook/Captive + static bool willControlFlycam();// enabled AND mode is FlyCam + static ActionNameType getActionNameType(const std::string& action); + + // Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel + // If the axis name lacks the +/- postfix it assumes '+' postfix. + static LLGameControl::InputChannel getChannelByName(const std::string& name); + + // Translate between an axis-map output code and its symbolic name, as used by the + // Devices-tab output selector: "AXIS_LEFTX".."AXIS_RIGHT_TRIGGER" for the canonical + // axes, "AXIS_TRIGGERS" for the fan-out pair, and "AXIS_NONE"/unrecognized for None. + static U8 axisOutputFromName(const std::string& name); + static std::string axisOutputName(U8 code); + + // Edge-triggered ("pressed this frame") one-shot commands bound to a + // button that don't correspond to an AGENT_CONTROL_* bit (e.g. they + // toggle viewer-side state rather than a movement flag). Each mode has + // its own bitmask of these. + enum AvatarMiscAction : U32 + { + AVATAR_ACTION_TOGGLE_FLY = 1u << 0, + AVATAR_ACTION_TOGGLE_SIT = 1u << 1, + AVATAR_ACTION_TOGGLE_SPEAK = 1u << 2, + AVATAR_ACTION_TOGGLE_FLYCAM = 1u << 3, + AVATAR_ACTION_TOGGLE_MOUSELOOK = 1u << 4, + AVATAR_ACTION_TOGGLE_MOUSE_CURSOR = 1u << 5, + // Mouselook/Cursor-mode only (see buildDefaultModeMappings()): drops + // straight back to plain Avatar mode, clearing whichever of Mouselook/ + // Cursor (and FlyCam, via FLYCAM_ACTION_ESCAPE below) got it there. + AVATAR_ACTION_ESCAPE = 1u << 6, + }; + + enum FlycamMiscAction : U32 + { + FLYCAM_ACTION_UNROLL = 1u << 0, + // FlyCam-mode counterpart of AVATAR_ACTION_ESCAPE above. + FLYCAM_ACTION_ESCAPE = 1u << 1, + }; + + // Level-triggered (held == asserted) FlyCam-mode modifiers, reported via + // getFlycamInputs()'s 'modifiers_out'. Unlike FlycamMiscAction these are + // not one-shot commands: they change the meaning of other flycam channels + // for as long as they're held (see LLAgentCamera::updateFlycam()). + enum FlycamModifier : U32 + { + FLYCAM_MODIFIER_ORBIT = 1u << 0, + }; + + // Simulated mouse button actions (press-n-hold) + enum AvatarMouseButton : U32 + { + AVATAR_MOUSE_BUTTON_LEFT = 1u << 0, + AVATAR_MOUSE_BUTTON_RIGHT = 1u << 1, + }; + + // Result of translating controller inputs into agent actions: + struct AgentActions + { + U32 mMiscActionBits { 0 }; // toggles + U32 mMouseButtonBits { 0 }; // press-n-holds + U32 mControlFlags { 0 }; // translation into AGENT_CONRTOL bits + bool mIsRunning { false }; + + // Signed magnitudes ([-1, 1]) of the inputs that set YAW/PITCH bits + // in mControlFlags'. Used to modulate turn speeds. + F32 mYawAmplitude { 0.f }; + F32 mPitchAmplitude { 0.f }; + + // Signed magnitudes ([-1, 1]) of "Mouse left/right"/"Mouse up/down" + // (CONTROL_MODE_CURSOR only) -- drive the on-screen cursor rather than an + // AGENT_CONTROL_* bit; consumed by LLAgent::applyExternalActions(). + F32 mMouseCursorDX { 0.f }; + F32 mMouseCursorDY { 0.f }; + + // Signed magnitude ([-1, 1]) of "Zoom +/-" (Avatar/Mouselook/Captive only), + // combined with the full-deflection contribution of "Zoom +"/"Zoom -" -- + // drives the camera zoom rate rather than an AGENT_CONTROL_* bit; consumed + // by LLAgent::applyExternalActions() via LLAgentCamera::setOrbitInKey/ + // setOrbitOutKey(). Positive zooms in (camera moves closer). + F32 mZoomAmplitude { 0.f }; + }; + + // Keyboard presses produce action_flags which can be translated into State + // and game_control devices produce State which can be translated into action_flags. + // These methods help exchange such translations. + static AgentActions computeAgentActions(); + + // is_running mirrors gAgent::getRunning() (the same walk/run state + // computeAgentActions()/RUN_ENGAGE_FRACTION derive from an analog controller + // axis): translated movement axes are set to full deflection when running, + // half deflection when walking -- approximating the walk/run threshold as a + // 50% stick tilt, per LLGameControllerManager::computeAgentActions(). + static void setExternalInput(U32 action_flags, U32 buttons_from_keys, bool is_running); + + // Feeds the actual on-screen cursor position into CONTROL_MODE_CURSOR's + // CURSOR_PX/CURSOR_PY/CURSOR_NX/CURSOR_NY semantic axes (see NUM_CURSOR_SEMANTIC_AXES). + // Only consumed while the mode is CONTROL_MODE_CURSOR; harmless to call unconditionally + // otherwise. + static void setMouseCursorPosition(S32 pixel_x, S32 pixel_y, S32 rect_width, S32 rect_height); + + // call this after putting a GameControlData packet on the wire + static void updateResendPeriod(); + + // Returns the AgentData.Packet sequence number for the next outgoing + // GameControlData message, then increments it (U8 wraps after 255). + // Call exactly once per message, while packing it. + static U8 getNextPacketNum(); + + static bool parseDeviceOptions(const std::string& options, std::string& name, + std::vector& axis_options, + std::vector& axis_map, std::vector& button_map); + static std::string stringifyDeviceOptions(const std::string& name, + const std::vector& axis_options, + const std::vector& axis_map, const std::vector& button_map, + bool force_empty = false); + + static void initByDefault(); + static void loadFromSettings(); + static void saveToSettings(); + + // Settings serialization helpers used by loadFromSettings/saveToSettings + // and exposed for callers that need to snapshot or restore module state + // (e.g. preference panel cancel-restore). + // getSettingKeys() -- the host setting names this module reads/writes. + // getSettingsAsLLSD() -- snapshot of current in-memory state, keyed by the same names. + // applySettingsFromLLSD(settings) -- apply 'settings' to in-memory state. + // Keys not present in 'settings' are left unchanged. + static const std::vector& getSettingKeys(); + static LLSD getSettingsAsLLSD(); + static void applySettingsFromLLSD(const LLSD& settings); + + // GameControl settings, stored under the single "GameControl" setting key: + // ModeMappings//Axes|Buttons -- GLOBAL action -> canonical-input maps + // Devices//Config -- per-device serialized hardware options + // 'mode' is "Avatar"/"Mouselook"/"FlyCam"/"Captive"/"Cursor"; 'kind' is "Axes"/"Buttons". + static LLSD getDefaultModeMappings(); // { : { Axes, Buttons } } + static LLSD getDefaultGameControlSettings(); // full default GameControl map + static const LLSD& getGameControlSettings(); + static void setGameControlSettings(const LLSD& settings); + + // Global per-mode action mapping accessors. getModeMapping falls back to the + // built-in defaults when an entry is missing. + static LLSD getModeMapping(const std::string& mode, const std::string& kind); + static void setModeMapping(const std::string& mode, const std::string& kind, + const LLSD& mapping); + static void updateModeMapping(const std::string& mode, const std::string& kind, + const std::string& action, const std::string& input); + + // Per-mode, per-axis-action Invert flag (axes only; buttons have no polarity). + // When set, the action's mapped axis value is negated before being applied. + // Defaults to false when absent, except for the built-in defaults' invert-by- + // default axis actions (see buildDefaultModeMappings()). + static bool getAxisInvert(const std::string& mode, const std::string& action); + static void setAxisInvert(const std::string& mode, const std::string& action, bool invert); + + // Per-mode enable flag: when false, game-control input is not converted to the + // mode's actions and its mappings are treated as locked. Defaults to true when + // the flag is absent. 'mode' is "Avatar"/"Mouselook"/"FlyCam"/"Captive"/"Cursor". + static bool isModeEnabled(const std::string& mode); + static void setModeEnabled(const std::string& mode, bool enabled); + + // FlyCam-only flag: whether Roll input is allowed to rotate the flycam about + // its forward axis (see LLFlycam::mAllowRoll). Defaults to false when absent. + static bool isFlycamRollAllowed(); + static void setFlycamRollAllowed(bool allowed); + + // FlyCam-only camera move-speed multiplier, applied to LLFlycam's Pitch/Yaw/ + // Roll rate coefficients (see LLFlycam::mSpeedFactor). Range [0.5, 2.0]; + // defaults to 1.0 when absent. + static F32 getFlycamSpeedFactor(); + static void setFlycamSpeedFactor(F32 speed_factor); + + // FlyCam-only flag: whether a centered crosshair is drawn over the 3D view + // while flycam is engaged (see LLViewerWindow::draw() / + // LLAgentCamera::isUsingFlycam()). Defaults to false when absent. + static bool isFlycamCrosshairEnabled(); + static void setFlycamCrosshairEnabled(bool enabled); + + static std::string getDeviceConfig(const std::string& guid); + static void setDeviceConfig(const std::string& guid, const std::string& config); + + // "Avatar"/"Mouselook"/"FlyCam"/"Captive"/"Cursor" for the given mode (empty for CONTROL_MODE_NONE). + static std::string getModeName(AgentControlMode mode); + + static void setDeviceOptions(const std::string& guid, const Options& options); + + // inherited from LLGameControllerBindingToStringHandler + virtual std::string getBindingAsString(const std::string& control) const override; + virtual bool hasHandlingDevice() const override; +}; diff --git a/indra/llwindow/llgamecontroltranslator.cpp b/indra/llwindow/llgamecontroltranslator.cpp new file mode 100644 index 00000000000..1b30e4f5781 --- /dev/null +++ b/indra/llwindow/llgamecontroltranslator.cpp @@ -0,0 +1,347 @@ +/** + * @file llgamecontroltranslator.cpp + * @brief LLGameControlTranslator class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * App-wide preferences. Note that these are not per-user, + * because we need to load many preferences before we have + * a login name. + */ + +#include "llgamecontroltranslator.h" +#include "llgamecontrol.h" +#include "llsd.h" + + +using ActionToMaskMap = LLGameControlTranslator::ActionToMaskMap; + +LLGameControlTranslator::LLGameControlTranslator() +{ +} + + + +const S32 LLGameControlTranslator::calculateTranslatedButtons( + const ControllerMappings& mappings, + const LLGameControl::State state +) +{ + // HACK: supply hard-coded threshold for ON/OFF zones + constexpr S32 AXIS_THRESHOLD = 32768 / 8; + + S32 result = 0; + + + + size_t axesSize = state.mAxes.size(); + for(const ControllerMapping& mapping : mappings) + { + const ControllerActionTypeIndex& from = mapping.first; + const U32 to = mapping.second; + + U8 btn = 0; + + if(from.first == LLGameControl::ActionType::BUTTON) + { + btn = (state.mButtons & (1 << from.second)) > 0; + } + else if(from.first == LLGameControl::ActionType::DOF) + { + if(from.second < axesSize) { + btn = state.mAxes[from.second] > AXIS_THRESHOLD; + } + } + + result |= to * btn; + } + return result; +} + +void LLGameControlTranslator::calculateTranslatedAxes( + const ControllerMappings& mappings, + const LLGameControl::State& state, + std::vector& outDOF +) +{ + std::fill(outDOF.begin(), outDOF.end(), 0); + + size_t outDOFSize = outDOF.size(); + size_t dofSize = state.mAxes.size(); + for(const ControllerMapping& mapping : mappings) + { + const ControllerActionTypeIndex& from = mapping.first; + const U32 to = mapping.second; + + if(to >= outDOFSize) + { + continue; + } + + S16 dof = 0; + + if(from.first == LLGameControl::ActionType::BUTTON) + { + dof = (state.mButtons & (1 << from.second)) > 0; + } + else if(from.first == LLGameControl::ActionType::DOF) + { + if(from.second < dofSize) { + dof = state.mAxes[from.second]; + } + } + + outDOF[to] = dof; + } +} + +void LLGameControlTranslator::setAvailableActionMasks(ActionToMaskMap& action_to_mask) +{ + mActionToMask = std::move(action_to_mask); +} + +LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const std::string& action) const +{ + LLGameControl::InputChannel channel; + auto mask_itr = mActionToMask.find(action); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + auto channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + channel = channel_itr->second; + } + } + else + { + // It is expected that sometimes 'action' lacks the postfix '+' or '-'. + // When it is missing we append '+' and try again. + std::string action_plus = action; + action_plus.append("+"); + mask_itr = mActionToMask.find(action_plus); + if (mask_itr != mActionToMask.end()) + { + U32 mask = mask_itr->second; + auto channel_itr = mMaskToChannel.find(mask); + if (channel_itr != mMaskToChannel.end()) + { + channel = channel_itr->second; + } + } + } + return channel; +} + +void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& named_channels) +{ + mMaskToChannel.clear(); + mMappedFlags = 0; + mPrevActiveFlags = 0; + mCachedState.clear(); + + for (const auto& [name, channel] : named_channels) + { + updateMap(name, channel); + } +} + +void LLGameControlTranslator::updateMap(const std::string& action, const LLGameControl::InputChannel& channel) +{ + // First, get the action name type + LLGameControl::ActionNameType actionNameType = LLGameControl::getActionNameType(action); + if (actionNameType == LLGameControl::ACTION_NAME_UNKNOWN || + actionNameType == LLGameControl::ACTION_NAME_FLYCAM) + { + LL_WARNS("GameControl") << "unmappable action='" << action << "' (type=" << actionNameType << ")" << LL_ENDL; + return; + } + + // Second, get the expected associated channel type (except of TYPE_NONE) + LLGameControl::InputChannel::Type expectedChannelType = + actionNameType == LLGameControl::ACTION_NAME_BINARY ? + LLGameControl::InputChannel::TYPE_BUTTON : + LLGameControl::InputChannel::TYPE_AXIS; + if (!channel.isNone() && (channel.mType != expectedChannelType)) + { + LL_WARNS("GameControl") << "unmappable channel (type=" << channel.mType << ")" + << " for action='" << action << "' (type=" << actionNameType << ")" << LL_ENDL; + return; + } + + if (actionNameType == LLGameControl::ACTION_NAME_ANALOG) // E.g., "push" + { + // Special (double) processing for analog action names + // sequentially adding '+' and '-' to the given action + updateMapInternal(action + "+", channel); + + // For the channel type TYPE_NONE we can map the same channel + // In fact, the mapping will be removed from the mapping list + if (channel.isNone()) + { + updateMapInternal(action + "-", channel); + } + else + { + // For the channel type except of TYPE_NONE we construct the other channel + // with the same type and index but with the opposite sign + LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign); + + // TIED TRIGGER HACK: this works for XBox and similar controllers, + // and those are pretty much the only supported devices right now + // however TODO: figure out how to do this better. + // + // AXIS_LEFT_TRIGGER and AXIS_RIGHT_TRIGGER are separate axes and most devices + // only allow them to read positive, not negative. When used for motion control + // they are typically paired together. We assume as much here when computing + // the other_channel. + if (channel.mIndex == LLGameControl::AXIS_LEFT_TRIGGER) + { + other_channel.mIndex = LLGameControl::AXIS_RIGHT_TRIGGER; + other_channel.mSign = 1; + } + else if (channel.mIndex == LLGameControl::AXIS_RIGHT_TRIGGER) + { + other_channel.mIndex = LLGameControl::AXIS_LEFT_TRIGGER; + other_channel.mSign = 1; + } + updateMapInternal(action + "-", other_channel); + } + } + else + { + // Simple (single) processing for other action name types + updateMapInternal(action, channel); + } + + // recompute mMappedFlags + mMappedFlags = 0; + for (const auto& [mask, channel] : mMaskToChannel) + { + mMappedFlags |= mask; + } + mPrevActiveFlags = 0; + mCachedState.clear(); +} + +// Given external action_flags (i.e. raw avatar input) +// compute the corresponding LLGameControl::State that would have produced those flags. +// Note: "action flags" are similar to, but not quite the same as, "control flags". +// "Action flags" are the raw input of avatar movement intent, whereas "control flags" +// are the consequential set of instructions that are sent to the server for moving +// the avatar character. +const LLGameControl::State& LLGameControlTranslator::computeStateFromFlags(U32 action_flags) +{ + // translate action_flag bits to equivalent game controller state + // according to data in mMaskToChannel + + // only bother to update mCachedState if active_flags have changed + U32 active_flags = action_flags & mMappedFlags; + if (active_flags != mPrevActiveFlags) + { + mCachedState.clear(); + for (const auto& [mask, channel] : mMaskToChannel) + { + if (mask == (mask & action_flags)) + { + if (channel.isAxis()) + { + if (channel.mSign < 0) + { + mCachedState.mAxes[channel.mIndex] = std::numeric_limits::min(); + } + else + { + mCachedState.mAxes[channel.mIndex] = std::numeric_limits::max(); + } + } + else if (channel.isButton()) + { + mCachedState.mButtons |= 0x01U << channel.mIndex; + } + } + } + mPrevActiveFlags = active_flags; + } + return mCachedState; +} + +// Given LLGameControl::State (i.e. from a real controller) +// compute corresponding action flags (e.g. for moving the avatar around) +U32 LLGameControlTranslator::computeFlagsFromState(const std::vector& axes, U32 buttons) +{ + // HACK: supply hard-coded threshold for ON/OFF zones + constexpr S32 AXIS_THRESHOLD = 32768 / 8; + U32 action_flags = 0; + for (const auto& [mask, channel] : mMaskToChannel) + { + if (channel.isAxis()) + { + if (channel.mSign < 0) + { + if (axes[channel.mIndex] < -AXIS_THRESHOLD) + { + action_flags |= mask; + } + } + else if (axes[channel.mIndex] > AXIS_THRESHOLD) + { + action_flags |= mask; + } + } + else if (channel.isButton()) + { + U32 bit_set = buttons & (0x01U << channel.mIndex); + if (bit_set) + { + action_flags |= mask; + } + } + } + return action_flags; +} + +void LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) +{ + auto action_it = mActionToMask.find(name); + llassert(action_it != mActionToMask.end()); + U32 mask = action_it->second; + auto channel_it = mMaskToChannel.find(mask); + if (channel_it == mMaskToChannel.end()) + { + // create new mapping + mMaskToChannel[mask] = channel; + } + else if (channel.isNone()) + { + // remove old mapping + mMaskToChannel.erase(channel_it); + } + else + { + // update old mapping + channel_it->second = channel; + } +} + diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h new file mode 100644 index 00000000000..8647fbca1db --- /dev/null +++ b/indra/llwindow/llgamecontroltranslator.h @@ -0,0 +1,98 @@ +/** + * @file llgamecontroltranslator.h + * @brief LLGameControlTranslator class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include + +#include "stdtypes.h" +#include "llgamecontrol.h" + + +// GameControl data is sent to the server to expose game controller input to LSL scripts, +// however not everyone will have a game controller device. To allow keyboard users to provide +// GameControl data we allow the User to configure equivalences between avatar actions +// (i.e. "push forward", "strafe left", etc) and keyboard buttons to GameControl axes +// and buttons. +// +// The LLGameControlTranslator stores the equivalences and translates avatar action_flags +// and keyboard state into GameContrl data, and in some cases the other direction: from +// LLGameControl::State into avatar action_flags. +// + +class LLGameControlTranslator +{ +public: + + using ControllerActionTypeIndex = std::pair; + using ControllerMapping = std::pair; + using ControllerMappings = std::vector; + + using ActionToMaskMap = std::map< std::string, U32 >; // < action : mask > + using MaskToChannelMap = std::map< U32, LLGameControl::InputChannel >; // < mask : channel > + using NamedChannel = std::pair < std::string , LLGameControl::InputChannel >; + using NamedChannels = std::vector< NamedChannel >; + + LLGameControlTranslator(); + const S32 calculateTranslatedButtons(const ControllerMappings& mapping, const LLGameControl::State state); + void calculateTranslatedAxes(const ControllerMappings& mapping, const LLGameControl::State& state, std::vector& outDOF); + void setAvailableActionMasks(ActionToMaskMap& action_to_mask); + LLGameControl::InputChannel getChannelByAction(const std::string& action) const; + void setMappings(NamedChannels& named_channels); + void updateMap(const std::string& action, const LLGameControl::InputChannel& channel); + // Note: to remove a mapping you can call updateMap() with a TYPE_NONE channel + + // Given external action_flags (i.e. raw avatar input) + // compute the corresponding LLGameControl::State that would have produced those flags. + // Note: "action_flags" are similar to, but not quite the same as, "control_flags". + const LLGameControl::State& computeStateFromFlags(U32 action_flags); + + // Given LLGameControl::State (i.e. from a real controller) + // compute corresponding action flags (e.g. for moving the avatar around) + U32 computeFlagsFromState(const std::vector& axes, U32 buttons); + + U32 getMappedFlags() const { return mMappedFlags; } + +private: + void updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); + +private: + // mActionToMask is an invarient map between the possible actions + // and the action bit masks. Only actions therein can have their + // bit masks mapped to channels. + ActionToMaskMap mActionToMask; // invariant map after init + + // mMaskToChannel is a dynamic map between action bit masks + // and GameControl channels. + MaskToChannelMap mMaskToChannel; // dynamic map, per preference changes + + // mCachedState is an optimization: + // it is only recomputed when external action_flags change + LLGameControl::State mCachedState; + + U32 mMappedFlags { 0 }; + U32 mPrevActiveFlags { 0 }; +}; diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index df69b1390cf..ba6a97b012b 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -278,6 +278,32 @@ bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, MASK translated_mask) } +bool LLKeyboard::handleKeyDown(const NATIVE_KEY_TYPE key, const MASK mask) +{ + MASK translated_mask = updateModifiers(mask); + KEY translated_key = 0; + bool handled = false; + if(translateKey(key, &translated_key)) + { + handled = handleTranslatedKeyDown(translated_key, translated_mask); + } + return handled; +} + + +bool LLKeyboard::handleKeyUp(const NATIVE_KEY_TYPE key, const MASK mask) +{ + MASK translated_mask = updateModifiers(mask); + KEY translated_key = 0; + bool handled = false; + if(translateKey(key, &translated_key)) + { + handled = handleTranslatedKeyUp(translated_key, translated_mask); + } + return handled; +} + + void LLKeyboard::toggleInsertMode() { if (LL_KIM_INSERT == mInsertMode) diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index a092787cef9..36f7c7f18fc 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -40,7 +40,47 @@ enum EKeystate KEYSTATE_UP }; -typedef std::function LLKeyFunc; + +struct LLKeyPressState +{ + LLKeyPressState(EKeystate state, F32 value) + : + mState(state), + mValue(value), + mElapsedTime(0.0), + mElapsedFrames(0), + mRepeat(false), + mAnalog(false) + { + mUp = mState == KEYSTATE_UP; + mLevel = mState == KEYSTATE_LEVEL; + mDown = mState == KEYSTATE_DOWN; + } + LLKeyPressState(bool up, bool level, bool down, F32 value) + : + mUp(up), + mLevel(level), + mDown(down), + mValue(value), + mElapsedTime(0.0), + mElapsedFrames(0), + mRepeat(false), + mAnalog(false) + { + mState = mDown ? KEYSTATE_DOWN : ( mLevel ? KEYSTATE_LEVEL : KEYSTATE_UP ); + } + EKeystate mState; + F32 mValue; + F32 mElapsedTime; + S32 mElapsedFrames; + bool mRepeat; + bool mUp; + bool mLevel; + bool mDown; + bool mAnalog; +}; + +typedef std::function LLKeyFunc; typedef std::string (LLKeyStringTranslatorFunc)(std::string_view); enum EKeyboardInsertMode @@ -69,7 +109,8 @@ class LLKeyboard F32 getCurKeyElapsedTime() { return getKeyDown(mCurScanKey) ? getKeyElapsedTime( mCurScanKey ) : 0.f; } - F32 getCurKeyElapsedFrameCount() { return getKeyDown(mCurScanKey) ? (F32)getKeyElapsedFrameCount( mCurScanKey ) : 0.f; } + S32 getCurKeyElapsedFrameCount() { return getKeyDown(mCurScanKey) ? getKeyElapsedFrameCount( mCurScanKey ) : 0; } + KEY getCurScanKey() { return mCurScanKey; } bool getKeyDown(const KEY key) { return mKeyLevel[key]; } bool getKeyRepeated(const KEY key) { return mKeyRepeated[key]; } @@ -78,13 +119,10 @@ class LLKeyboard bool handleTranslatedKeyUp(KEY translated_key, MASK translated_mask); // Translated into "Linden" keycodes bool handleTranslatedKeyDown(KEY translated_key, MASK translated_mask); // Translated into "Linden" keycodes - virtual bool handleKeyUp(const NATIVE_KEY_TYPE key, MASK mask) = 0; - virtual bool handleKeyDown(const NATIVE_KEY_TYPE key, MASK mask) = 0; + virtual bool handleKeyUp(const NATIVE_KEY_TYPE key, MASK mask); + virtual bool handleKeyDown(const NATIVE_KEY_TYPE key, MASK mask); -#if LL_DARWIN && !LL_SDL_WINDOW - // We only actually use this for macOS. - virtual void handleModifier(MASK mask) = 0; -#endif // LL_DARWIN + virtual void handleModifier(MASK mask) { } // Asynchronously poll the control, alt, and shift keys and set the // appropriate internal key masks. @@ -114,6 +152,7 @@ class LLKeyboard protected: void addKeyName(KEY key, const std::string& name); + virtual MASK updateModifiers(const MASK mask) { return mask; } protected: std::map mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs diff --git a/indra/llwindow/llkeyboardmacosx.h b/indra/llwindow/llkeyboardmacosx.h index c824e7b7135..569af97eda4 100644 --- a/indra/llwindow/llkeyboardmacosx.h +++ b/indra/llwindow/llkeyboardmacosx.h @@ -52,7 +52,7 @@ class LLKeyboardMacOSX : public LLKeyboard void handleModifier(MASK mask) override; protected: - MASK updateModifiers(const MASK mask); + MASK updateModifiers(const MASK mask) override; void setModifierKeyLevel( KEY key, bool new_state ); bool translateNumpadKey( const U16 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index 2ebe6475610..9415b122d03 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -43,7 +43,7 @@ class LLKeyboardSDL : public LLKeyboard void scanKeyboard() override; protected: - MASK updateModifiers(const MASK mask); + MASK updateModifiers(const MASK mask) override; void setModifierKeyLevel( KEY key, bool new_state ); bool translateNumpadKey( const LLKeyboard::NATIVE_KEY_TYPE os_key, KEY *translated_key ); LLKeyboard::NATIVE_KEY_TYPE inverseTranslateNumpadKey(const KEY translated_key); diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 4ef0b493a57..62f0527fd6d 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -182,7 +182,7 @@ void LLKeyboardWin32::resetMaskKeys() //} -MASK LLKeyboardWin32::updateModifiers() +MASK LLKeyboardWin32::updateModifiers(const U32 mask) { //RN: this seems redundant, as we should have already received the appropriate // messages for the modifier keys @@ -191,8 +191,7 @@ MASK LLKeyboardWin32::updateModifiers() // (keydown encoded in high order bit of short) mKeyLevel[KEY_CAPSLOCK] = (GetKeyState(VK_CAPITAL) & 0x0001) != 0; // Low order bit carries the toggle state. // Get mask for keyboard events - MASK mask = currentMask(false); - return mask; + return currentMask(false); } @@ -203,7 +202,7 @@ bool LLKeyboardWin32::handleKeyDown(const LLKeyboard::NATIVE_KEY_TYPE key, MASK U32 translated_mask; bool handled = false; - translated_mask = updateModifiers(); + translated_mask = updateModifiers(mask); if (translateExtendedKey(key, mask, &translated_key)) { @@ -220,7 +219,7 @@ bool LLKeyboardWin32::handleKeyUp(const LLKeyboard::NATIVE_KEY_TYPE key, MASK ma U32 translated_mask; bool handled = false; - translated_mask = updateModifiers(); + translated_mask = updateModifiers(mask); if (translateExtendedKey(key, mask, &translated_key)) { @@ -259,6 +258,7 @@ void LLKeyboardWin32::scanKeyboard() mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]); } } + mCurScanKey = KEY_NONE; // Reset edges for next frame for (key = 0; key < KEY_COUNT; key++) @@ -321,4 +321,4 @@ U16 LLKeyboardWin32::inverseTranslateExtendedKey(const KEY translated_key) return inverseTranslateKey(converted_key); } -#endif +#endif // LL_WINDOWS diff --git a/indra/llwindow/llkeyboardwin32.h b/indra/llwindow/llkeyboardwin32.h index fa223a29048..c4ed11a18f3 100644 --- a/indra/llwindow/llkeyboardwin32.h +++ b/indra/llwindow/llkeyboardwin32.h @@ -49,7 +49,7 @@ class LLKeyboardWin32 : public LLKeyboard U16 inverseTranslateExtendedKey(const KEY translated_key); protected: - MASK updateModifiers(); + MASK updateModifiers(const MASK mask) override; //void setModifierKeyLevel( KEY key, bool new_state ); private: std::map mTranslateNumpadMap; diff --git a/indra/llwindow/llsdl.cpp b/indra/llwindow/llsdl.cpp index bc381203864..c562c32f886 100644 --- a/indra/llwindow/llsdl.cpp +++ b/indra/llwindow/llsdl.cpp @@ -93,7 +93,6 @@ void init_sdl(const std::string& app_name) #endif #if LL_SDL_WINDOW - // For linux we SDL_INIT_VIDEO and _AUDIO std::initializer_list > hintList = { {SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,"0"}, @@ -111,19 +110,7 @@ void init_sdl(const std::string& app_name) std::initializer_list> initList= { {SDL_INIT_VIDEO,"SDL_INIT_VIDEO", true}, - {SDL_INIT_JOYSTICK,"SDL_INIT_JOYSTICK", true}, - {SDL_INIT_GAMEPAD,"SDL_INIT_GAMEPAD", true}, }; -#else - // For non-linux platforms we still SDL_INIT_VIDEO because it is a pre-requisite - // for SDL_INIT_GAMECONTROLLER. - std::initializer_list> initList= - { - {SDL_INIT_VIDEO,"SDL_INIT_VIDEO", false}, - }; -#endif // LL_LINUX - // We SDL_INIT_GAMECONTROLLER later in the startup process to make it - // more likely we'll catch initial SDL_CONTROLLERDEVICEADDED events. for (auto subSystem : initList) { @@ -138,6 +125,9 @@ void init_sdl(const std::string& app_name) } } } +#endif // LL_SDL_WINDOW + // We SDL_INIT_GAMECONTROLLER later in the startup process to make it + // more likely we'll catch initial SDL_CONTROLLERDEVICEADDED events. } void quit_sdl() @@ -145,5 +135,14 @@ void quit_sdl() #if LL_WINDOWS && defined(LL_SDL_WINDOW) SDL_UnregisterApp(); #endif - SDL_Quit(); + + // When SDL_MAIN_USE_CALLBACKS is in effect (gSDLMainHandled=true), the + // SDL framework owns SDL_Init/SDL_Quit around the SDL_App* callbacks. + // Calling SDL_Quit() here would run before SDL's own SDL_Quit at process + // exit — SDL3 makes this idempotent, but it's still the wrong owner. + // Only quit ourselves when we initialised SDL ourselves. + if (!gSDLMainHandled) + { + SDL_Quit(); + } } diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index b6177abfc7c..e68422f0e85 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -39,7 +39,7 @@ #include "llerror.h" #include "llkeyboard.h" -#if LL_SDL_WINDOW && !defined(LL_MESA_HEADLESS) +#if !defined(LL_MESA_HEADLESS) #include "llsdl.h" #endif #include "llwindowcallbacks.h" @@ -431,7 +431,7 @@ LLWindow* LLWindowManager::createWindow( { LLWindow* new_window; -#if LL_SDL_WINDOW && !defined(LL_MESA_HEADLESS) +#if !defined(LL_MESA_HEADLESS) init_sdl(name); #endif @@ -484,7 +484,7 @@ bool LLWindowManager::destroyWindow(LLWindow* window) window->close(); sWindowList.erase(window); -#if LL_SDL_WINDOW && !defined(LL_MESA_HEADLESS) +#if !defined(LL_MESA_HEADLESS) quit_sdl(); #endif diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h index 327323b018d..f8c940bb1a6 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -70,16 +70,16 @@ class LLWindow : public LLInstanceTracker virtual void show() = 0; virtual void hide() = 0; virtual void close() = 0; - virtual bool getVisible() = 0; - virtual bool getMinimized() = 0; - virtual bool getMaximized() = 0; + virtual bool getVisible() const = 0; + virtual bool getMinimized() const = 0; + virtual bool getMaximized() const = 0; virtual bool maximize() = 0; virtual void minimize() = 0; virtual void restore() = 0; - bool getFullscreen() { return mFullscreen; }; - virtual bool getPosition(LLCoordScreen *position) = 0; - virtual bool getSize(LLCoordScreen *size) = 0; - virtual bool getSize(LLCoordWindow *size) = 0; + virtual bool getFullscreen() const { return mFullscreen; }; + virtual bool getPosition(LLCoordScreen *position) const = 0; + virtual bool getSize(LLCoordScreen *size) const = 0; + virtual bool getSize(LLCoordWindow *size) const = 0; virtual bool setPosition(LLCoordScreen position) = 0; bool setSize(LLCoordScreen size); bool setSize(LLCoordWindow size); @@ -100,7 +100,7 @@ class LLWindow : public LLInstanceTracker virtual bool setCursorPosition(LLCoordWindow position) = 0; virtual bool getCursorPosition(LLCoordWindow *position) = 0; #if LL_WINDOWS && !LL_SDL_WINDOW - virtual bool getCursorDelta(LLCoordCommon* delta) = 0; + virtual bool getCursorDelta(LLCoordCommon* delta) const = 0; #endif virtual bool isWrapMouse() const = 0; virtual void showCursor() = 0; @@ -143,14 +143,14 @@ class LLWindow : public LLInstanceTracker virtual bool copyTextToPrimary(const LLWString &src); virtual void flashIcon(F32 seconds) = 0; - virtual F32 getGamma() = 0; + virtual F32 getGamma() const = 0; virtual bool setGamma(const F32 gamma) = 0; // Set the gamma virtual void setFSAASamples(const U32 fsaa_samples) = 0; //set number of FSAA samples - virtual U32 getFSAASamples() = 0; + virtual U32 getFSAASamples() const = 0; virtual bool restoreGamma() = 0; // Restore original gamma table (before updating gamma) - virtual ESwapMethod getSwapMethod() { return mSwapMethod; } + ESwapMethod getSwapMethod() { return mSwapMethod; } virtual void processMiscNativeEvents(); - virtual void gatherInput() = 0; + virtual void gatherInput(bool app_has_focus) = 0; virtual void delayInputProcessing() = 0; virtual void swapBuffers() = 0; virtual void bringToFront() = 0; @@ -158,12 +158,12 @@ class LLWindow : public LLInstanceTracker // handy coordinate space conversion routines // NB: screen to window and vice verse won't work on width/height coordinate pairs, // as the conversion must take into account left AND right border widths, etc. - virtual bool convertCoords( LLCoordScreen from, LLCoordWindow *to) = 0; - virtual bool convertCoords( LLCoordWindow from, LLCoordScreen *to) = 0; - virtual bool convertCoords( LLCoordWindow from, LLCoordGL *to) = 0; - virtual bool convertCoords( LLCoordGL from, LLCoordWindow *to) = 0; - virtual bool convertCoords( LLCoordScreen from, LLCoordGL *to) = 0; - virtual bool convertCoords( LLCoordGL from, LLCoordScreen *to) = 0; + virtual bool convertCoords( LLCoordScreen from, LLCoordWindow *to) const = 0; + virtual bool convertCoords( LLCoordWindow from, LLCoordScreen *to) const = 0; + virtual bool convertCoords( LLCoordWindow from, LLCoordGL *to) const = 0; + virtual bool convertCoords( LLCoordGL from, LLCoordWindow *to) const = 0; + virtual bool convertCoords( LLCoordScreen from, LLCoordGL *to) const = 0; + virtual bool convertCoords( LLCoordGL from, LLCoordScreen *to) const = 0; // query supported resolutions virtual LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) = 0; @@ -179,7 +179,7 @@ class LLWindow : public LLInstanceTracker virtual bool dialogColorPicker(F32 *r, F32 *g, F32 *b); // return a platform-specific window reference (HWND on Windows, WindowRef on the Mac, Gtk window on Linux) - virtual void *getPlatformWindow() = 0; + virtual void *getPlatformWindow() const = 0; // control platform's Language Text Input mechanisms. virtual void allowLanguageTextInput(LLPreeditor *preeditor, bool b) {} @@ -194,10 +194,10 @@ class LLWindow : public LLInstanceTracker static LLFontFallbackMatch findFallbackFontForChar(llwchar wch); // Provide native key event data - virtual LLSD getNativeKeyData() { return LLSD::emptyMap(); } + virtual LLSD getNativeKeyData() const { return LLSD::emptyMap(); } // Get system UI size based on DPI (for 96 DPI UI size should be 1.0) - virtual F32 getSystemUISize() { return 1.0; } + virtual F32 getSystemUISize() const { return 1.0; } static std::vector getDisplaysResolutionList(); @@ -211,7 +211,7 @@ class LLWindow : public LLInstanceTracker return false; }; - virtual S32 getRefreshRate() { return mRefreshRate; } + virtual S32 getRefreshRate() const { return mRefreshRate; } virtual void initWatchdog() {} // windows runs window as a thread and it needs a watchdog protected: @@ -334,4 +334,4 @@ extern const S32 gURLProtocolWhitelistCount; extern const std::string gURLProtocolWhitelist[]; //extern const std::string gURLProtocolWhitelistHandler[]; -#endif // _LL_window_h_ +#endif // LL_LLWINDOW_H diff --git a/indra/llwindow/llwindowheadless.h b/indra/llwindow/llwindowheadless.h index 8b7b0ef55c6..4d949cdea35 100644 --- a/indra/llwindow/llwindowheadless.h +++ b/indra/llwindow/llwindowheadless.h @@ -32,78 +32,71 @@ class LLWindowHeadless : public LLWindow { public: - /*virtual*/ void show() override {} - /*virtual*/ void hide() override {} - /*virtual*/ void close() override {} - /*virtual*/ bool getVisible() override {return false;} - /*virtual*/ bool getMinimized() override {return false;} - /*virtual*/ bool getMaximized() override {return false;} - /*virtual*/ bool maximize() override {return false;} - /*virtual*/ void minimize() override {} - /*virtual*/ void restore() override {} - // TODO: LLWindow::getFullscreen() is (intentionally?) NOT virtual. - // Apparently the coder of LLWindowHeadless didn't realize that. Is it a - // mistake to shadow the base-class method with an LLWindowHeadless - // override when called on the subclass, yet call the base-class method - // when indirecting through a polymorphic pointer or reference? - bool getFullscreen() {return false;} - /*virtual*/ bool getPosition(LLCoordScreen *position) override {return false;} - /*virtual*/ bool getSize(LLCoordScreen *size) override {return false;} - /*virtual*/ bool getSize(LLCoordWindow *size) override {return false;} - /*virtual*/ bool setPosition(LLCoordScreen position) override {return false;} - /*virtual*/ bool setSizeImpl(LLCoordScreen size) override {return false;} - /*virtual*/ bool setSizeImpl(LLCoordWindow size) override {return false;} - /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override {return false;} + void show() override {} + void hide() override {} + void close() override {} + bool getVisible() const override {return false;} + bool getMinimized() const override {return false;} + bool getMaximized() const override {return false;} + bool maximize() override {return false;} + void minimize() override {} + void restore() override {} + bool getFullscreen() const override {return false;}; + bool getPosition(LLCoordScreen *position) const override {return false;} + bool getSize(LLCoordScreen *size) const override {return false;} + bool getSize(LLCoordWindow *size) const override {return false;} + bool setPosition(LLCoordScreen position) override {return false;} + bool setSizeImpl(LLCoordScreen size) override {return false;} + bool setSizeImpl(LLCoordWindow size) override {return false;} + bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override {return false;} void* createSharedContext() override { return nullptr; } void makeContextCurrent(void*) override {} void destroySharedContext(void*) override {} - /*virtual*/ void toggleVSync(bool enable_vsync) override { } - /*virtual*/ bool setCursorPosition(LLCoordWindow position) override {return false;} - /*virtual*/ bool getCursorPosition(LLCoordWindow *position) override {return false;} + void toggleVSync(bool enable_vsync) override { } + bool setCursorPosition(LLCoordWindow position) override {return false;} + bool getCursorPosition(LLCoordWindow *position) override {return false;} #if LL_WINDOWS && !LL_SDL_WINDOW - /*virtual*/ bool getCursorDelta(LLCoordCommon* delta) override { return false; } + bool getCursorDelta(LLCoordCommon* delta) const override { return false; } #endif - /*virtual*/ bool isWrapMouse() const override { return true; } - /*virtual*/ void showCursor() override {} - /*virtual*/ void hideCursor() override {} - /*virtual*/ void showCursorFromMouseMove() override {} - /*virtual*/ void hideCursorUntilMouseMove() override {} - /*virtual*/ bool isCursorHidden() override {return false;} - /*virtual*/ void updateCursor() override {} - //virtual ECursorType getCursor() override { return mCurrentCursor; } - /*virtual*/ void captureMouse() override {} - /*virtual*/ void releaseMouse() override {} - /*virtual*/ void setMouseClipping( bool b ) override {} - /*virtual*/ bool isClipboardTextAvailable() override {return false; } - /*virtual*/ bool pasteTextFromClipboard(LLWString &dst) override {return false; } - /*virtual*/ bool copyTextToClipboard(const LLWString &src) override {return false; } - /*virtual*/ void flashIcon(F32 seconds) override {} - /*virtual*/ F32 getGamma() override {return 1.0f; } - /*virtual*/ bool setGamma(const F32 gamma) override {return false; } // Set the gamma - /*virtual*/ void setFSAASamples(const U32 fsaa_samples) override { } - /*virtual*/ U32 getFSAASamples() override { return 0; } - /*virtual*/ bool restoreGamma() override {return false; } // Restore original gamma table (before updating gamma) - //virtual ESwapMethod getSwapMethod() override { return mSwapMethod; } - /*virtual*/ void gatherInput() override {} - /*virtual*/ void delayInputProcessing() override {} - /*virtual*/ void swapBuffers() override; + bool isWrapMouse() const override { return true; } + void showCursor() override {} + void hideCursor() override {} + void showCursorFromMouseMove() override {} + void hideCursorUntilMouseMove() override {} + bool isCursorHidden() override {return false;} + void updateCursor() override {} + void captureMouse() override {} + void releaseMouse() override {} + void setMouseClipping( bool b ) override {} + bool isClipboardTextAvailable() override {return false; } + bool pasteTextFromClipboard(LLWString &dst) override {return false; } + bool copyTextToClipboard(const LLWString &src) override {return false; } + void flashIcon(F32 seconds) override {} + F32 getGamma() const override {return 1.0f; } + bool setGamma(const F32 gamma) override {return false; } // Set the gamma + void setFSAASamples(const U32 fsaa_samples) override { } + U32 getFSAASamples() const override { return 0; } + bool restoreGamma() override {return false; } // Restore original gamma table (before updating gamma) + void gatherInput(bool app_has_focus) override {} + void delayInputProcessing() override {} + void swapBuffers() override; // handy coordinate space conversion routines - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to) override { return false; } - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to) override { return false; } + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override { return false; } + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override { return false; } + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override { return false; } + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override { return false; } + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override { return false; } + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override { return false; } - /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override { return NULL; } - /*virtual*/ F32 getNativeAspectRatio() override { return 1.0f; } - /*virtual*/ F32 getPixelAspectRatio() override { return 1.0f; } - /*virtual*/ void setNativeAspectRatio(F32 ratio) override {} + LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override { return NULL; } + F32 getNativeAspectRatio() override { return 1.0f; } + F32 getPixelAspectRatio() override { return 1.0f; } + void setNativeAspectRatio(F32 ratio) override {} - /*virtual*/ void *getPlatformWindow() override { return 0; } - /*virtual*/ void bringToFront() override {} + void* getPlatformWindow() const override { return 0; } + void bringToFront() override {} LLWindowHeadless(LLWindowCallbacks* callbacks, const std::string& title, const std::string& name, diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 7cb681aefed..d858333f745 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -28,6 +28,7 @@ #include "llwindowmacosx.h" +#include "llgamecontrol.h" #include "llkeyboardmacosx.h" #include "llwindowcallbacks.h" #include "llpreeditor.h" @@ -724,7 +725,7 @@ void LLWindowMacOSX::updateMouseDeltas(float* deltas) } } -void LLWindowMacOSX::getMouseDeltas(float* delta) +void LLWindowMacOSX::getMouseDeltas(float* delta) const { delta[0] = mCursorLastEventDeltaX; delta[1] = mCursorLastEventDeltaY; @@ -929,7 +930,7 @@ bool LLWindowMacOSX::isValid() return (mWindow != NULL); } -bool LLWindowMacOSX::getVisible() +bool LLWindowMacOSX::getVisible() const { bool result = false; @@ -944,12 +945,12 @@ bool LLWindowMacOSX::getVisible() return(result); } -bool LLWindowMacOSX::getMinimized() +bool LLWindowMacOSX::getMinimized() const { return mMinimized; } -bool LLWindowMacOSX::getMaximized() +bool LLWindowMacOSX::getMaximized() const { return mMaximized; } @@ -963,17 +964,13 @@ bool LLWindowMacOSX::maximize() return mMaximized; } -bool LLWindowMacOSX::getFullscreen() -{ - return mFullscreen; -} - -void LLWindowMacOSX::gatherInput() +void LLWindowMacOSX::gatherInput(bool app_has_focus) { updateCursor(); + LLGameControl::processEvents(app_has_focus); } -bool LLWindowMacOSX::getPosition(LLCoordScreen *position) +bool LLWindowMacOSX::getPosition(LLCoordScreen *position) const { S32 err = -1; @@ -1000,7 +997,7 @@ bool LLWindowMacOSX::getPosition(LLCoordScreen *position) return (err == noErr); } -bool LLWindowMacOSX::getSize(LLCoordScreen *size) +bool LLWindowMacOSX::getSize(LLCoordScreen *size) const { S32 err = -1; @@ -1026,7 +1023,7 @@ bool LLWindowMacOSX::getSize(LLCoordScreen *size) return (err == noErr); } -bool LLWindowMacOSX::getSize(LLCoordWindow *size) +bool LLWindowMacOSX::getSize(LLCoordWindow *size) const { S32 err = -1; @@ -1100,7 +1097,7 @@ void LLWindowMacOSX::restoreGLContext() CGLSetCurrentContext(mContext); } -F32 LLWindowMacOSX::getGamma() +F32 LLWindowMacOSX::getGamma() const { F32 result = 2.2; // Default to something sane @@ -1134,7 +1131,7 @@ F32 LLWindowMacOSX::getGamma() return result; } -U32 LLWindowMacOSX::getFSAASamples() +U32 LLWindowMacOSX::getFSAASamples() const { return mFSAASamples; } @@ -1467,21 +1464,21 @@ LLWindow::LLWindowResolution* LLWindowMacOSX::getSupportedResolutions(S32 &num_r return mSupportedResolutions; } -bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to) +bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to) const { to->mX = from.mX; to->mY = from.mY; return true; } -bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to) +bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to) const { to->mX = from.mX; to->mY = from.mY; return true; } -bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) +bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) const { if(mWindow) { @@ -1501,7 +1498,7 @@ bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) return false; } -bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) +bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) const { if(mWindow) { @@ -1520,14 +1517,14 @@ bool LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) return false; } -bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to) +bool LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to) const { LLCoordWindow window_coord; return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); } -bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to) +bool LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to) const { LLCoordWindow window_coord; @@ -2413,7 +2410,7 @@ bool LLWindowMacOSX::getInputDevices(U32 device_type_filter, return return_value; } -LLSD LLWindowMacOSX::getNativeKeyData() +LLSD LLWindowMacOSX::getNativeKeyData() const { LLSD result = LLSD::emptyMap(); @@ -2460,7 +2457,7 @@ bool LLWindowMacOSX::dialogColorPicker( F32 *r, F32 *g, F32 *b) return (retval); } -void *LLWindowMacOSX::getPlatformWindow() +void *LLWindowMacOSX::getPlatformWindow() const { // NOTE: this will be NULL in fullscreen mode. Plan accordingly. return (void*)mWindow; @@ -2597,6 +2594,7 @@ void LLWindowMacOSX::interruptLanguageTextInput() commitCurrentPreedit(mGLView); } +// static std::vector LLWindowMacOSX::getDisplaysResolutionList() { std::vector resolution_list; @@ -2626,7 +2624,7 @@ std::vector LLWindowMacOSX::getDisplaysResolutionList() return resolution_list; } -//static +// static std::vector LLWindowMacOSX::getDynamicFallbackFontList() { // Fonts previously in getFontListSans() have moved to fonts.xml. @@ -2649,7 +2647,7 @@ MASK LLWindowMacOSX::modifiersToMask(S16 modifiers) return mask; } -F32 LLWindowMacOSX::getSystemUISize() +F32 LLWindowMacOSX::getSystemUISize() const { return ::getDeviceUnitSize(mGLView); } diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h index 9534eadf8e6..b0c110b6cfe 100644 --- a/indra/llwindow/llwindowmacosx.h +++ b/indra/llwindow/llwindowmacosx.h @@ -42,16 +42,15 @@ class LLWindowMacOSX : public LLWindow void show() override; void hide() override; void close() override; - bool getVisible() override; - bool getMinimized() override; - bool getMaximized() override; + bool getVisible() const override; + bool getMinimized() const override; + bool getMaximized() const override; bool maximize() override; void minimize() override; void restore() override; - bool getFullscreen(); - bool getPosition(LLCoordScreen *position) override; - bool getSize(LLCoordScreen *size) override; - bool getSize(LLCoordWindow *size) override; + bool getPosition(LLCoordScreen *position) const override; + bool getSize(LLCoordScreen *size) const override; + bool getSize(LLCoordWindow *size) const override; bool setPosition(LLCoordScreen position) override; bool setSizeImpl(LLCoordScreen size) override; bool setSizeImpl(LLCoordWindow size) override; @@ -73,23 +72,22 @@ class LLWindowMacOSX : public LLWindow bool pasteTextFromClipboard(LLWString &dst) override; bool copyTextToClipboard(const LLWString & src) override; void flashIcon(F32 seconds) override; - F32 getGamma() override; + F32 getGamma() const override; bool setGamma(const F32 gamma) override; // Set the gamma - U32 getFSAASamples() override; + U32 getFSAASamples() const override; void setFSAASamples(const U32 fsaa_samples) override; bool restoreGamma() override; // Restore original gamma table (before updating gamma) - ESwapMethod getSwapMethod() override { return mSwapMethod; } - void gatherInput() override; + void gatherInput(bool app_has_focus) override; void delayInputProcessing() override {}; void swapBuffers() override; // handy coordinate space conversion routines - bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; - bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; - bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; - bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override; LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override; F32 getNativeAspectRatio() override; @@ -101,13 +99,13 @@ class LLWindowMacOSX : public LLWindow bool dialogColorPicker(F32 *r, F32 *g, F32 *b) override; - void *getPlatformWindow() override; + void *getPlatformWindow() const override; void bringToFront() override {}; void allowLanguageTextInput(LLPreeditor *preeditor, bool b) override; void interruptLanguageTextInput() override; void spawnWebBrowser(const std::string& escaped_url, bool async) override; - F32 getSystemUISize() override; + F32 getSystemUISize() const override; bool getInputDevices(U32 device_type_filter, std::function osx_callback, @@ -120,14 +118,14 @@ class LLWindowMacOSX : public LLWindow static LLFontFallbackMatch findFallbackFontForChar(llwchar wch); // Provide native key event data - LLSD getNativeKeyData() override; + LLSD getNativeKeyData() const override; void* getWindow() { return mWindow; } LLWindowCallbacks* getCallbacks() { return mCallbacks; } LLPreeditor* getPreeditor() { return mPreeditor; } void updateMouseDeltas(float* deltas); - void getMouseDeltas(float* delta); + void getMouseDeltas(float* delta) const; void handleDragNDrop(std::string url, LLWindowCallbacks::DragNDropAction action); diff --git a/indra/llwindow/llwindowmesaheadless.h b/indra/llwindow/llwindowmesaheadless.h index 47cd6320b1e..b908376bbbf 100644 --- a/indra/llwindow/llwindowmesaheadless.h +++ b/indra/llwindow/llwindowmesaheadless.h @@ -38,16 +38,16 @@ class LLWindowMesaHeadless : public LLWindow void show() override {}; void hide() override {}; void close() override {}; - bool getVisible() override {return false;}; - bool getMinimized() override {return false;}; - bool getMaximized() override {return false;}; + bool getVisible() const override {return false;}; + bool getMinimized() const override {return false;}; + bool getMaximized() const override {return false;}; bool maximize() override {return false;}; void minimize() override {}; void restore() override {}; - bool getFullscreen() {return false;}; - bool getPosition(LLCoordScreen *position) override {return false;}; - bool getSize(LLCoordScreen *size) override {return false;}; - bool getSize(LLCoordWindow *size) override {return false;}; + bool getFullscreen() const override {return false;}; + bool getPosition(LLCoordScreen *position) const override {return false;}; + bool getSize(LLCoordScreen *size) const override {return false;}; + bool getSize(LLCoordWindow *size) const override {return false;}; bool setPosition(LLCoordScreen position) override {return false;}; bool setSizeImpl(LLCoordScreen size) override {return false;}; bool switchContext(bool fullscreen, const LLCoordScreen &size, bool disable_vsync, const LLCoordScreen * const posp = NULL) override {return false;}; @@ -60,7 +60,7 @@ class LLWindowMesaHeadless : public LLWindow void hideCursorUntilMouseMove() override {}; bool isCursorHidden() override {return false;}; void updateCursor() override {}; - //virtual ECursorType getCursor() override { return mCurrentCursor; }; + //ECursorType getCursor() override { return mCurrentCursor; }; void captureMouse() override {}; void releaseMouse() override {}; void setMouseClipping( bool b ) override {}; @@ -68,13 +68,13 @@ class LLWindowMesaHeadless : public LLWindow bool pasteTextFromClipboard(LLWString &dst) override {return false; }; bool copyTextToClipboard(const LLWString &src) override {return false; }; void flashIcon(F32 seconds) override {}; - F32 getGamma() override {return 1.0f; }; + F32 getGamma() const override {return 1.0f; }; bool setGamma(const F32 gamma) override {return false; }; // Set the gamma bool restoreGamma() override {return false; }; // Restore original gamma table (before updating gamma) void setFSAASamples(const U32 fsaa_samples) override { /* FSAA not supported yet on Mesa headless.*/ } - U32 getFSAASamples() override { return 0; } - //virtual ESwapMethod getSwapMethod() override { return mSwapMethod; } - void gatherInput() override {}; + U32 getFSAASamples() const override { return 0; } + //ESwapMethod getSwapMethod() override { return mSwapMethod; } + void gatherInput(bool app_has_focus) override {}; void delayInputProcessing() override {}; void swapBuffers() override; @@ -85,19 +85,19 @@ class LLWindowMesaHeadless : public LLWindow bool setSizeImpl(LLCoordWindow size) override { return false; }; // handy coordinate space conversion routines - bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override { return false; }; - bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override { return false; }; - bool convertCoords(LLCoordWindow from, LLCoordGL *to) override { return false; }; - bool convertCoords(LLCoordGL from, LLCoordWindow *to) override { return false; }; - bool convertCoords(LLCoordScreen from, LLCoordGL *to) override { return false; }; - bool convertCoords(LLCoordGL from, LLCoordScreen *to) override { return false; }; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override { return false; }; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override { return false; }; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override { return false; }; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override { return false; }; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override { return false; }; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override { return false; }; LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override { return NULL; }; F32 getNativeAspectRatio() override { return 1.0f; }; F32 getPixelAspectRatio() override { return 1.0f; }; void setNativeAspectRatio(F32 ratio) override {} - void *getPlatformWindow() override { return 0; }; + void *getPlatformWindow() const override { return nullptr; }; void bringToFront() override {}; LLWindowMesaHeadless(LLWindowCallbacks* callbacks, diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index e3ae9493a6e..4aaa4b18078 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -39,6 +39,7 @@ #include "llfindlocale.h" #include "llpreeditor.h" #include "llsdl.h" +#include "llgamecontrol.h" #if LL_LINUX #ifdef LL_GLIB @@ -97,8 +98,6 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, : LLWindow(callbacks, fullscreen, flags), mGamma(1.0f), mFlashing(false) { - SDL_GL_LoadLibrary(nullptr); - // Initialize the keyboard gKeyboard = new LLKeyboardSDL(); gKeyboard->setCallbacks(callbacks); @@ -211,7 +210,7 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b if (width == 0) width = 1024; if (height == 0) - width = 768; + height = 768; if (x == 0) x = SDL_WINDOWPOS_UNDEFINED; if (y == 0) @@ -638,7 +637,7 @@ bool LLWindowSDL::isValid() return mWindow != nullptr; } -bool LLWindowSDL::getVisible() +bool LLWindowSDL::getVisible() const { bool result = true; if (mWindow) @@ -652,7 +651,7 @@ bool LLWindowSDL::getVisible() return result; } -bool LLWindowSDL::getMinimized() +bool LLWindowSDL::getMinimized() const { bool result = false; if (mWindow) @@ -666,7 +665,7 @@ bool LLWindowSDL::getMinimized() return result; } -bool LLWindowSDL::getMaximized() +bool LLWindowSDL::getMaximized() const { bool result = false; if (mWindow) @@ -691,7 +690,7 @@ bool LLWindowSDL::maximize() return false; } -bool LLWindowSDL::getPosition(LLCoordScreen *position) +bool LLWindowSDL::getPosition(LLCoordScreen *position) const { if (mWindow) { @@ -701,7 +700,7 @@ bool LLWindowSDL::getPosition(LLCoordScreen *position) return false; } -bool LLWindowSDL::getSize(LLCoordScreen *size) +bool LLWindowSDL::getSize(LLCoordScreen *size) const { if (mWindow) { @@ -712,7 +711,7 @@ bool LLWindowSDL::getSize(LLCoordScreen *size) return false; } -bool LLWindowSDL::getSize(LLCoordWindow *size) +bool LLWindowSDL::getSize(LLCoordWindow *size) const { if (mWindow) { @@ -768,7 +767,7 @@ void LLWindowSDL::swapBuffers() LL_PROFILER_GPU_COLLECT; } -U32 LLWindowSDL::getFSAASamples() +U32 LLWindowSDL::getFSAASamples() const { return mFSAASamples; } @@ -778,7 +777,7 @@ void LLWindowSDL::setFSAASamples(const U32 samples) mFSAASamples = samples; } -F32 LLWindowSDL::getGamma() +F32 LLWindowSDL::getGamma() const { return 1.f / mGamma; } @@ -997,9 +996,11 @@ LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_reso if ((w >= 800) && (h >= 600)) { // make sure we don't add the same resolution multiple times! + // A row is "the same" only when both width AND height match the previous row; + // if either dimension differs we keep it. if ( (mNumSupportedResolutions == 0) || - ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && - (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) + (mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) || + (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h) ) { mSupportedResolutions[mNumSupportedResolutions].mWidth = w; mSupportedResolutions[mNumSupportedResolutions].mHeight = h; @@ -1035,8 +1036,7 @@ std::vector LLWindowSDL::getDisplaysResolutionList() return ret; } - -bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) +bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) const { if (!to) return false; @@ -1055,7 +1055,7 @@ bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) return true; } -bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) +bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) const { if (!to) return false; @@ -1073,7 +1073,7 @@ bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) return true; } -bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) +bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) const { if (!to) return false; @@ -1084,7 +1084,7 @@ bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordWindow* to) return true; } -bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) +bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) const { if (!to) return false; @@ -1095,14 +1095,14 @@ bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordScreen *to) return true; } -bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordGL *to) +bool LLWindowSDL::convertCoords(LLCoordScreen from, LLCoordGL *to) const { LLCoordWindow window_coord; return convertCoords(from, &window_coord) && convertCoords(window_coord, to); } -bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordScreen *to) +bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordScreen *to) const { LLCoordWindow window_coord; @@ -1220,7 +1220,7 @@ void LLWindowSDL::processMiscNativeEvents() } } -void LLWindowSDL::gatherInput() +void LLWindowSDL::gatherInput(bool app_has_focus) { // This is for the case where SDL is not driving the main event loop if(!gSDLMainHandled) @@ -1230,12 +1230,14 @@ void LLWindowSDL::gatherInput() // Handle all outstanding SDL events while (SDL_PollEvent(&event)) { - handleEvent(event); + handleEvent(event, app_has_focus); } } updateCursor(); + LLGameControl::processEvents(app_has_focus); + // This is a good time to stop flashing the icon if our mFlashTimer has // expired. if (mFlashing && mFlashTimer.hasExpired()) @@ -1245,7 +1247,7 @@ void LLWindowSDL::gatherInput() } } -SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) +SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event, bool app_has_focus) { switch(event.type) { @@ -1449,6 +1451,7 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) break; } default: + LLGameControl::handleEvent(event, app_has_focus); break; } @@ -1456,11 +1459,11 @@ SDL_AppResult LLWindowSDL::handleEvent(const SDL_Event& event) } // static -SDL_AppResult LLWindowSDL::handleEvents(const SDL_Event& event) +SDL_AppResult LLWindowSDL::handleEvents(const SDL_Event& event, bool app_has_focus) { if(!gWindowImplementation) return SDL_APP_CONTINUE; - return gWindowImplementation->handleEvent(event); + return gWindowImplementation->handleEvent(event, app_has_focus); } static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty) @@ -1794,7 +1797,7 @@ bool LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) Make the raw keyboard data available - used to poke through to LLQtWebKit so that Qt/Webkit has access to the virtual keycodes etc. that it needs */ -LLSD LLWindowSDL::getNativeKeyData() +LLSD LLWindowSDL::getNativeKeyData() const { LLSD result = LLSD::emptyMap(); @@ -1851,7 +1854,7 @@ void LLWindowSDL::spawnWebBrowser(const std::string& escaped_url, bool async) LL_INFOS() << "spawn_web_browser returning." << LL_ENDL; } -void* LLWindowSDL::getPlatformWindow() +void* LLWindowSDL::getPlatformWindow() const { void* ret = nullptr; if (mWindow) @@ -1953,7 +1956,7 @@ void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position) SDL_SetTextInputArea(mWindow, &r, 0); } -F32 LLWindowSDL::getSystemUISize() +F32 LLWindowSDL::getSystemUISize() const { if(mWindow) { diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 7156ad66b7d..650f27fae90 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -29,6 +29,7 @@ // Simple Directmedia Layer (http://libsdl.org/) implementation of LLWindow class +#if LL_LINUX #include "llwindow.h" #include "lltimer.h" #include "llmutex.h" @@ -56,19 +57,19 @@ class LLWindowSDL final : public LLWindow void close() override; - bool getVisible() override; + bool getVisible() const override; - bool getMinimized() override; + bool getMinimized() const override; - bool getMaximized() override; + bool getMaximized() const override; bool maximize() override; void minimize() override; - bool getPosition(LLCoordScreen *position) override; + bool getPosition(LLCoordScreen *position) const override; - bool getSize(LLCoordScreen *size) override; - bool getSize(LLCoordWindow *size) override; + bool getSize(LLCoordScreen *size) const override; + bool getSize(LLCoordWindow *size) const override; bool setPosition(LLCoordScreen position) override; @@ -109,31 +110,31 @@ class LLWindowSDL final : public LLWindow void flashIcon(F32 seconds) override; void maybeStopFlashIcon(); - F32 getGamma() override; + F32 getGamma() const override; bool setGamma(const F32 gamma) override; // Set the gamma bool restoreGamma() override; // Restore original gamma table (before updating gamma) - U32 getFSAASamples() override; + U32 getFSAASamples() const override; void setFSAASamples(const U32 samples) override; void processMiscNativeEvents() override; - void gatherInput() override; + void gatherInput(bool app_has_focus) override; - SDL_AppResult handleEvent(const SDL_Event& event); - static SDL_AppResult handleEvents(const SDL_Event& event); + SDL_AppResult handleEvent(const SDL_Event& event, bool app_has_focus); + static SDL_AppResult handleEvents(const SDL_Event& event, bool app_has_focus); void swapBuffers() override; void delayInputProcessing() override {}; // handy coordinate space conversion routines - bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; - bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; - bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; - bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override; LLWindowResolution *getSupportedResolutions(S32 &num_resolutions) override; @@ -146,7 +147,7 @@ class LLWindowSDL final : public LLWindow bool dialogColorPicker(F32 *r, F32 *g, F32 *b) override; - void *getPlatformWindow() override; + void *getPlatformWindow() const override; void bringToFront() override; @@ -164,7 +165,7 @@ class LLWindowSDL final : public LLWindow void destroySharedContext(void *context) override; void toggleVSync(bool enable_vsync) override; - F32 getSystemUISize() override; + F32 getSystemUISize() const override; static std::vector getDisplaysResolutionList(); @@ -185,7 +186,7 @@ class LLWindowSDL final : public LLWindow bool isValid() override; - LLSD getNativeKeyData() override; + LLSD getNativeKeyData() const override; void initCursors(); void quitCursors(); @@ -210,7 +211,7 @@ class LLWindowSDL final : public LLWindow U32 mGrabbyKeyFlags = 0; S32 mReallyCapturedCount = 0; SDL_Window *mWindow = nullptr; - SDL_GLContext mContext; + SDL_GLContext mContext = nullptr; SDL_Cursor *mSDLCursors[UI_CURSOR_COUNT]; std::string mWindowTitle; @@ -271,4 +272,5 @@ class LLSplashScreenSDL : public LLSplashScreen S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type); +#endif //LL_LINUX #endif //LL_LLWINDOWSDL_H diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index a93a3150e13..d04137dfe18 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -31,6 +31,7 @@ #include "llwindowwin32.h" // LLWindow library includes +#include "llgamecontrol.h" #include "llkeyboardwin32.h" #include "lldragdropwin32.h" #include "llpreeditor.h" @@ -1123,17 +1124,17 @@ void LLWindowWin32::setThreadPriorityNormal() } } -bool LLWindowWin32::getVisible() +bool LLWindowWin32::getVisible() const { return (mWindowHandle && IsWindowVisible(mWindowHandle)); } -bool LLWindowWin32::getMinimized() +bool LLWindowWin32::getMinimized() const { return (mWindowHandle && IsIconic(mWindowHandle)); } -bool LLWindowWin32::getMaximized() +bool LLWindowWin32::getMaximized() const { return (mWindowHandle && IsZoomed(mWindowHandle)); } @@ -1158,26 +1159,21 @@ bool LLWindowWin32::maximize() return true; } -bool LLWindowWin32::getFullscreen() -{ - return mFullscreen; -} - -bool LLWindowWin32::getPosition(LLCoordScreen *position) +bool LLWindowWin32::getPosition(LLCoordScreen *position) const { position->mX = mRect.left; position->mY = mRect.top; return true; } -bool LLWindowWin32::getSize(LLCoordScreen *size) +bool LLWindowWin32::getSize(LLCoordScreen *size) const { size->mX = mRect.right - mRect.left; size->mY = mRect.bottom - mRect.top; return true; } -bool LLWindowWin32::getSize(LLCoordWindow *size) +bool LLWindowWin32::getSize(LLCoordWindow *size) const { size->mX = mClientRect.right - mClientRect.left; size->mY = mClientRect.bottom - mClientRect.top; @@ -2112,7 +2108,7 @@ bool LLWindowWin32::getCursorPosition(LLCoordWindow *position) return true; } -bool LLWindowWin32::getCursorDelta(LLCoordCommon* delta) +bool LLWindowWin32::getCursorDelta(LLCoordCommon* delta) const { if (delta == nullptr) { @@ -2300,7 +2296,7 @@ void LLWindowWin32::delayInputProcessing() } -void LLWindowWin32::gatherInput() +void LLWindowWin32::gatherInput(bool app_has_focus) { ASSERT_MAIN_THREAD(); LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32; @@ -2380,6 +2376,8 @@ void LLWindowWin32::gatherInput() mInputProcessingPaused = false; updateCursor(); + + LLGameControl::processEvents(app_has_focus); } static LLTrace::BlockTimerStatHandle FTM_KEYHANDLER("Handle Keyboard"); @@ -3622,7 +3620,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ return ret; } -bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) +bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) const { S32 client_height; RECT client_rect; @@ -3642,7 +3640,7 @@ bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordWindow *to) return true; } -bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) +bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) const { S32 client_height; RECT client_rect; @@ -3661,7 +3659,7 @@ bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordGL* to) return true; } -bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) +bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) const { POINT mouse_point; @@ -3678,7 +3676,7 @@ bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordWindow* to) return result; } -bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) +bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) const { POINT mouse_point; @@ -3695,7 +3693,7 @@ bool LLWindowWin32::convertCoords(LLCoordWindow from, LLCoordScreen *to) return result; } -bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) +bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) const { LLCoordWindow window_coord; @@ -3709,7 +3707,7 @@ bool LLWindowWin32::convertCoords(LLCoordScreen from, LLCoordGL *to) return true; } -bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordScreen *to) +bool LLWindowWin32::convertCoords(LLCoordGL from, LLCoordScreen *to) const { LLCoordWindow window_coord; @@ -3828,7 +3826,7 @@ void LLWindowWin32::setMouseClipping( bool b ) } } -bool LLWindowWin32::getClientRectInScreenSpace( RECT* rectp ) +bool LLWindowWin32::getClientRectInScreenSpace( RECT* rectp ) const { bool success = false; @@ -3872,7 +3870,7 @@ void LLWindowWin32::flashIcon(F32 seconds) }); } -F32 LLWindowWin32::getGamma() +F32 LLWindowWin32::getGamma() const { return mCurrentGamma; } @@ -3934,7 +3932,7 @@ void LLWindowWin32::setFSAASamples(const U32 fsaa_samples) mFSAASamples = fsaa_samples; } -U32 LLWindowWin32::getFSAASamples() +U32 LLWindowWin32::getFSAASamples() const { return mFSAASamples; } @@ -4287,7 +4285,7 @@ void LLWindowWin32::spawnWebBrowser(const std::string& escaped_url, bool async) Make the raw keyboard data available - used to poke through to LLQtWebKit so that Qt/Webkit has access to the virtual keycodes etc. that it needs */ -LLSD LLWindowWin32::getNativeKeyData() +LLSD LLWindowWin32::getNativeKeyData() const { LLSD result = LLSD::emptyMap(); @@ -4333,7 +4331,7 @@ bool LLWindowWin32::dialogColorPicker( F32 *r, F32 *g, F32 *b ) return (retval); } -void *LLWindowWin32::getPlatformWindow() +void* LLWindowWin32::getPlatformWindow() const { return (void*)mWindowHandle; } @@ -5256,7 +5254,7 @@ void LLWindowWin32::initWatchdog() clearHighPerformanceGPURequest(); } -F32 LLWindowWin32::getSystemUISize() +F32 LLWindowWin32::getSystemUISize() const { F32 scale_value = 1.f; HWND hWnd = (HWND)getPlatformWindow(); diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 0b104c6da82..ee21402706f 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -54,16 +54,15 @@ class LLWindowWin32 : public LLWindow void show() override; void hide() override; void close() override; - bool getVisible() override; - bool getMinimized() override; - bool getMaximized() override; + bool getVisible() const override; + bool getMinimized() const override; + bool getMaximized() const override; bool maximize() override; void minimize() override; void restore() override; - bool getFullscreen(); - bool getPosition(LLCoordScreen *position) override; - bool getSize(LLCoordScreen *size) override; - bool getSize(LLCoordWindow *size) override; + bool getPosition(LLCoordScreen *position) const override; + bool getSize(LLCoordScreen *size) const override; + bool getSize(LLCoordWindow *size) const override; bool setPosition(LLCoordScreen position) override; bool setSizeImpl(LLCoordScreen size) override; bool setSizeImpl(LLCoordWindow size) override; @@ -75,7 +74,7 @@ class LLWindowWin32 : public LLWindow void toggleVSync(bool enable_vsync) override; bool setCursorPosition(LLCoordWindow position) override; bool getCursorPosition(LLCoordWindow *position) override; - bool getCursorDelta(LLCoordCommon* delta) override; + bool getCursorDelta(LLCoordCommon* delta) const override; bool isWrapMouse() const override { return !mAbsoluteCursorPosition; }; void showCursor() override; void hideCursor() override; @@ -91,23 +90,22 @@ class LLWindowWin32 : public LLWindow bool pasteTextFromClipboard(LLWString &dst) override; bool copyTextToClipboard(const LLWString &src) override; void flashIcon(F32 seconds) override; - F32 getGamma() override; + F32 getGamma() const override; bool setGamma(const F32 gamma) override; // Set the gamma void setFSAASamples(const U32 fsaa_samples) override; - U32 getFSAASamples() override; + U32 getFSAASamples() const override; bool restoreGamma() override; // Restore original gamma table (before updating gamma) - ESwapMethod getSwapMethod() override { return mSwapMethod; } - void gatherInput() override; + void gatherInput(bool app_has_focus) override; void delayInputProcessing() override; void swapBuffers() override; // handy coordinate space conversion routines - bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; - bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; - bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; - bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; - bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) const override; + bool convertCoords(LLCoordWindow from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordWindow *to) const override; + bool convertCoords(LLCoordScreen from, LLCoordGL *to) const override; + bool convertCoords(LLCoordGL from, LLCoordScreen *to) const override; LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override; F32 getNativeAspectRatio() override; @@ -116,7 +114,7 @@ class LLWindowWin32 : public LLWindow bool dialogColorPicker(F32 *r, F32 *g, F32 *b ) override; - void *getPlatformWindow() override; + void* getPlatformWindow() const override; void bringToFront() override; void focusClient() override; @@ -126,7 +124,7 @@ class LLWindowWin32 : public LLWindow void interruptLanguageTextInput() override; void spawnWebBrowser(const std::string& escaped_url, bool async) override; - F32 getSystemUISize() override; + F32 getSystemUISize() const override; LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url ); @@ -142,7 +140,7 @@ class LLWindowWin32 : public LLWindow void* win_callback, void* userdata) override; - U32 getRawWParam() { return mRawWParam; } + U32 getRawWParam() const { return mRawWParam; } void initWatchdog() override; @@ -159,7 +157,7 @@ class LLWindowWin32 : public LLWindow void setThreadPriorityHigh(); void setThreadPriorityNormal(); void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); - virtual LLSD getNativeKeyData(); + LLSD getNativeKeyData() const override; // Changes display resolution. Returns true if successful bool setDisplayResolution(S32 width, S32 height, S32 refresh); @@ -196,7 +194,7 @@ class LLWindowWin32 : public LLWindow // Platform specific methods // - bool getClientRectInScreenSpace(RECT* rectp); + bool getClientRectInScreenSpace(RECT* rectp) const; static LRESULT CALLBACK mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param); @@ -297,9 +295,9 @@ class LLSplashScreenWin32 : public LLSplashScreen LLSplashScreenWin32(); virtual ~LLSplashScreenWin32(); - /*virtual*/ void showImpl(); - /*virtual*/ void updateImpl(const std::string& mesg); - /*virtual*/ void hideImpl(); + void showImpl() override; + void updateImpl(const std::string& mesg) override; + void hideImpl() override; #if LL_WINDOWS static LRESULT CALLBACK windowProc(HWND h_wnd, UINT u_msg, diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7aa255c5dac..b1014449b7f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -325,6 +325,7 @@ set(viewer_SOURCE_FILES llfollowcam.cpp llfriendcard.cpp llflyoutcombobtn.cpp + llflycam.cpp llgesturelistener.cpp llgesturemgr.cpp llgiveinventory.cpp @@ -498,6 +499,7 @@ set(viewer_SOURCE_FILES llpanelplaceprofile.cpp llpanelplaces.cpp llpanelplacestab.cpp + llpanelpreferencegamecontrol.cpp llpanelpresetscamerapulldown.cpp llpanelpresetspulldown.cpp llpanelprimmediacontrols.cpp @@ -1013,6 +1015,7 @@ set(viewer_HEADER_FILES llfollowcam.h llfriendcard.h llflyoutcombobtn.h + llflycam.h llgesturelistener.h llgesturemgr.h llgiveinventory.h @@ -1175,6 +1178,7 @@ set(viewer_HEADER_FILES llpanelplaceprofile.h llpanelplaces.h llpanelplacestab.h + llpanelpreferencegamecontrol.h llpanelpresetscamerapulldown.h llpanelpresetspulldown.h llpanelprimmediacontrols.h @@ -1754,6 +1758,7 @@ set(viewer_APPSETTINGS_FILES app_settings/toolbars.xml app_settings/trees.xml app_settings/viewerart.xml + app_settings/gamecontrollerdb.txt app_settings/message.xml ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg packages-info.txt @@ -2449,7 +2454,7 @@ if (LL_TESTS) set_property(SOURCE ${viewer_TEST_SOURCE_FILES} PROPERTY - LL_TEST_ADDITIONAL_LIBRARIES + LL_TEST_ADDITIONAL_LIBRARIES ${test_libs} ) diff --git a/indra/newview/app_settings/gamecontrollerdb.txt b/indra/newview/app_settings/gamecontrollerdb.txt new file mode 100644 index 00000000000..e7a236ea53d --- /dev/null +++ b/indra/newview/app_settings/gamecontrollerdb.txt @@ -0,0 +1,2209 @@ +# Game Controller DB for SDL in 2.0.16 format +# Source: https://github.com/mdqinc/SDL_GameControllerDB + +# Windows +03000000300f00000a01000000000000,3 In 1 Conversion Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows, +03000000fa190000918d000000000000,3 In 1 Conversion Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows, +03000000fa2d00000100000000000000,3dRudder Foot Motion Controller,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows, +03000000d0160000040d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows, +03000000d0160000050d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows, +03000000d0160000060d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows, +03000000d0160000070d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows, +03000000d0160000600a000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows, +03000000c82d00001930000000000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b11,platform:Windows, +03000000c82d00000031000000000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000531000000000000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000951000000000000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows, +03000000008000000210000000000000,8BitDo F30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +030000003512000011ab000000000000,8BitDo F30 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000c82d00001028000000000000,8BitDo F30 Arcade Joystick,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d000011ab000000000000,8BitDo F30 Arcade Joystick,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000801000000900000000000000,8BitDo F30 Arcade Stick,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001038000000000000,8BitDo F30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006a28000000000000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Windows, +03000000c82d00001251000000000000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001151000000000000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000150000000000000,8BitDo M30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000151000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000650000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00005106000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,guide:b2,leftshoulder:b8,lefttrigger:b9,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00002090000000000000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000310000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000451000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows, +03000000c82d00002028000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00008010000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d0000e002000000000000,8BitDo N30,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b6,platform:Windows, +03000000c82d00000190000000000000,8BitDo N30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000290000000000000,8BitDo N64,+rightx:b9,+righty:b3,-rightx:b4,-righty:b8,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,platform:Windows, +03000000c82d00003038000000000000,8BitDo N64,+rightx:b9,+righty:b3,-rightx:b4,-righty:b8,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,platform:Windows, +03000000c82d00006928000000000000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b11,platform:Windows, +03000000c82d00002590000000000000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000003512000012ab000000000000,8BitDo NES30,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Windows, +03000000c82d000012ab000000000000,8BitDo NES30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000022000000090000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000203800000900000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000751000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000851000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000360000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000361000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000960000000000000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000131000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000231000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000331000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000431000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00002867000000000000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a2,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b10,x:b3,y:b4,platform:Windows, +03000000c82d00000130000000000000,8BitDo SF30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000102800000900000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000021ab000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00003028000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +030000003512000020ab000000000000,8BitDo SN30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000030000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000351000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a2,rightshoulder:b7,rightx:a3,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001290000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000020ab000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00004028000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006228000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000021000000000000,8BitDo SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000260000000000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000261000000000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001230000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001260000000000000,8BitDo Ultimate 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001b30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001c30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001d30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001530000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001630000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001730000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001130000000000000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001330000000000000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000121000000000000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000a00500003232000000000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +030000008f0e00001200000000000000,Acme GA02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000c01100000355000000000000,Acrux,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000fa190000f0ff000000000000,Acteck AGJ 3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d1180000402c000000000000,ADT1,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a3,rightx:a2,righty:a5,x:b3,y:b4,platform:Windows, +030000006f0e00008801000000000000,Afterglow Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001301000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00001302000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00001304000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00001413000000000000,Afterglow Xbox Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00003901000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ab1200000103000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b000000f9000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000100000008200000000000000,Akishop Customs PS360,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000007c1800000006000000000000,Alienware Dual Compatible PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000491900001904000000000000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows, +03000000710100001904000000000000,Amazon Luna Controller,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b8,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b4,rightstick:b7,rightx:a3,righty:a4,start:b6,x:b3,y:b2,platform:Windows, +0300000008100000e501000000000000,Anbernic Game Pad,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000020500000913000000000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000373500000710000000000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000373500004610000000000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000190e00000110000000000000,Aquaplus Piece,a:b1,b:b0,back:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b2,platform:Windows, +03000000830500000160000000000000,Arcade,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b3,x:b4,y:b4,platform:Windows, +03000000120c0000100e000000000000,Armor 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000490b00004406000000000000,ASCII Seamic Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000869800002500000000000000,Astro C40 TR PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000a30c00002700000000000000,Astro City Mini,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000a30c00002800000000000000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a3,lefty:a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000050b00000579000000000000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000050b00000679000000000000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,start:b3,platform:Windows, +03000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +03000000380800001889000000000000,AtGames Legends Gamer Pro,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b14,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000008a3500000102000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows, +030000008a3500000201000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows, +030000008a3500000302000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows, +030000008a3500000402000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows, +03000000e4150000103f000000000000,Batarang,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d6200000e557000000000000,Batarang PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows, +030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ad1b000001f9000000000000,BB 070,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000bc2000005250000000000000,Beitong G3,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a3,righty:a4,start:b15,x:b3,y:b4,platform:Windows, +030000000d0500000208000000000000,Belkin Nostromo N40,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000000055000000000000,Betop BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000790000000700000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000808300000300000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006321000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000006f0e00006401000000000000,BF One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows, +03000000300f00000202000000000000,Bigben,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a5,righty:a2,start:b7,x:b2,y:b3,platform:Windows, +030000006b1400000209000000000000,Bigben,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000120c0000200e000000000000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000210e000000000000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000f10e000000000000,Brook PS2 Adapter,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000120c0000310c000000000000,Brook Super Converter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000d81d00000b00000000000000,Buffalo BSGP1601 Series,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows, +030000005a1c00002400000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows, +030000005b1c00002400000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows, +030000005b1c00002500000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows, +030000006d04000042c2000000000000,ChillStream,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000457500000401000000000000,Cobra,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000b0400003365000000000000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Windows, +030000004c050000c505000000000000,CronusMax Adapter,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000d814000007cd000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000d8140000cefa000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows, +030000003807000002cb000000000000,Cyborg,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000a306000022f6000000000000,Cyborg V.3 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000f806000000a3000000000000,DA Leader,a:b7,b:b6,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b0,leftstick:b8,lefttrigger:b1,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:b3,rightx:a2,righty:a3,start:b12,x:b4,y:b5,platform:Windows, +030000001a1c00000001000000000000,Datel Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000791d00000103000000000000,Dual Box Wii,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000004f040000070f000000000000,Dual Power,a:b8,b:b9,back:b4,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,leftshoulder:b13,leftstick:b6,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b12,rightstick:b7,righttrigger:b15,start:b5,x:b10,y:b11,platform:Windows, +030000004f04000012b3000000000000,Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +030000004f04000020b3000000000000,Dual Trigger,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +03000000bd12000002e0000000000000,Dual Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +03000000ff1100003133000000000000,DualForce,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b1,platform:Windows, +030000006f0e00003001000000000000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000fc0400000250000000000000,Easy Grip,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000bc2000000091000000000000,EasySMX Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000006e0500000a20000000000000,Elecom DUX60 MMO,a:b2,b:b3,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b14,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b15,righttrigger:b13,rightx:a3,righty:a4,start:b20,x:b0,y:b1,platform:Windows, +03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000095090000010000000000000000,Elecom JC-U609,a:b0,b:b1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows, +0300004112000000e500000000000000,Elecom JC-U909Z,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows, +03000041120000001050000000000000,Elecom JC-U911,a:b1,b:b2,back:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b0,x:b4,y:b5,platform:Windows, +030000006e0500000520000000000000,Elecom P301U PlayStation Controller Adapter,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +03000000250900000218000000000000,Elecom PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000411200004450000000000000,Elecom U1012,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000320000000000000,Elecom U3613M,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000e20000000000000,Elecom U3912T,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000f20000000000000,Elecom U4013S,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500001320000000000000,Elecom U4113,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006e0500001020000000000000,Elecom U4113S,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000720000000000000,Elecom W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +030000007d0400000640000000000000,Eliminator AfterShock,a:b1,b:b2,back:b9,dpdown:+a3,dpleft:-a5,dpright:+a5,dpup:-a3,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a4,righty:a2,start:b8,x:b0,y:b3,platform:Windows, +03000000120c0000f61c000000000000,Elite,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000430b00000300000000000000,EMS Production PS2 Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000062000001801000000000000,EMS TrioLinker Plus II,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Windows, +03000000242f000000b7000000000000,ESM 9110,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Windows, +03000000101c0000181c000000000000,Essential,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b4,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000341a00000108000000000000,EXEQ RF Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006f0e00008401000000000000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00008101000000000000,Faceoff Deluxe Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00008001000000000000,Faceoff Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000021000000090000000000000,FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +0300000011040000c600000000000000,FC801,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +03000000852100000201000000000000,FF GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ad1b000028f0000000000000,Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b00002ef0000000000000,Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b000038f0000000000000,Fightpad TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, +03005036852100000000000000000000,Final Fantasy XIV Online Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000f806000001a3000000000000,Firestorm,a:b9,b:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b0,leftstick:b10,lefttrigger:b1,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b12,x:b8,y:b4,platform:Windows, +03000000b50700000399000000000000,Firestorm 2,a:b2,b:b4,back:b10,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b9,start:b11,x:b3,y:b5,platform:Windows, +03000000b50700001302000000000000,Firestorm D3,a:b0,b:b2,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,x:b1,y:b3,platform:Windows, +03000000b40400001024000000000000,Flydigi Apex,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000151900004000000000000000,Flydigi Vader 2,a:b27,b:b26,back:b19,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b23,leftstick:b17,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b22,rightstick:b16,righttrigger:b20,rightx:a3,righty:a4,start:b18,x:b25,y:b24,platform:Windows, +03000000b40400001124000000000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b14,paddle1:b4,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b2,y:b3,platform:Windows, +03000000b40400001224000000000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000008305000000a0000000000000,G08XU,a:b0,b:b1,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b5,x:b2,y:b3,platform:Windows, +0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows, +03000000260900002625000000000000,GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows, +03000000341a000005f7000000000000,GameCube Controller,a:b2,b:b3,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b1,y:b0,platform:Windows, +03000000430b00000500000000000000,GameCube Controller,a:b0,b:b2,dpdown:b10,dpleft:b8,dpright:b9,dpup:b11,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a3,rightx:a5,righty:a2,start:b7,x:b1,y:b3,platform:Windows, +03000000790000004718000000000000,GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000790000004618000000000000,GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +030000008f0e00000d31000000000000,Gamepad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ac0500003d03000000000000,GameSir G3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ac0500005b05000000000000,GameSir G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000ac0500002d02000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000ac0500004d04000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ac0500001a06000000000000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000373500009410000000000000,GameSir Tegenaria Lite,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000004c0e00001035000000000000,Gamester,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +030000000d0f00001110000000000000,GameStick Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +0300000047530000616d000000000000,GameStop,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000b62500000100000000000000,Gametel GT004 01,a:b3,b:b0,dpdown:b10,dpleft:b9,dpright:b8,dpup:b11,leftshoulder:b4,rightshoulder:b5,start:b7,x:b1,y:b2,platform:Windows, +030000008f0e00001411000000000000,Gamo2 Divaller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000120c0000a857000000000000,Gator Claw,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000c21100000791000000000000,Be1 GC101 Controller 1.03,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c9110000f055000000000000,Be1 GC100XF Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000300f00000b01000000000000,GGE909 Recoil,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c283000000000000,Gioteck PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f025000031c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c383000000000000,Gioteck VX2 PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c483000000000000,Gioteck VX2 PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d11800000094000000000000,Google Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +030000004f04000026b3000000000000,GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +0300000079000000d418000000000000,GPD Win,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c6240000025b000000000000,GPX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000007d0400000840000000000000,Gravis Destroyer Tilt,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,x:b0,y:b3,platform:Windows, +030000007d0400000540000000000000,Gravis Eliminator Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000280400000140000000000000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a3,dpup:-a4,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000610000000000000,GreenAsia,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a5,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +03000000ac0500006b05000000000000,GT2a,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000008a2e0000dd10000000000000,Hand Held Legend GC Ultimate,a:b0,b:b2,back:b17,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b18,leftshoulder:b10,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b19,misc2:b24,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b1,y:b3,platform:Windows, +030000008a2e0000df10000000000000,Hand Held Legend ProGCC,a:b1,b:b0,back:b17,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b18,leftshoulder:b10,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b19,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b3,y:b2,platform:Windows, +030000000d0f00004900000000000000,Hatsune Miku Sho PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000001008000001e1000000000000,Havit HV G60,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows, +030000000d0f00000c00000000000000,HEXT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d81400000862000000000000,HitBox Edition Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +03000000632500002605000000000000,HJD X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000000d0f00000a00000000000000,Hori DOA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00008500000000000000,Hori Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002500000000000000,Hori Fighting Commander 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002d00000000000000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005f00000000000000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005e00000000000000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000000d0f00008400000000000000,Hori Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006201000000000000,Hori Fighting Commander Octa,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006401000000000000,Hori Fighting Commander Octa,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00005100000000000000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008600000000000000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f0000ba00000000000000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00008800000000000000,Hori Fighting Stick mini 4 PS3,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows, +030000000d0f00008700000000000000,Hori Fighting Stick mini 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000000d0f00001000000000000000,Hori Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00003200000000000000,Hori Fightstick 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000c000000000000000,Hori Fightstick 4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00000d00000000000000,Hori Fightstick EX2,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +030000000d0f00003701000000000000,Hori Fightstick Mini,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Windows, +030000000d0f00004000000000000000,Hori Fightstick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002100000000000000,Hori Fightstick V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002700000000000000,Hori Fightstick V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000a000000000000000,Hori Grip TAC4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b13,x:b0,y:b3,platform:Windows, +030000000d0f0000a500000000000000,Hori Miku Project Diva X HD PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000000d0f0000a600000000000000,Hori Miku Project Diva X HD PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000000d0f00000101000000000000,Hori Mini Hatsune Miku FT,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00003801000000000000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Windows, +030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002301000000000000,Hori PS4 Controller Light,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +030000000d0f00001100000000000000,Hori Real Arcade Pro 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002600000000000000,Hori Real Arcade Pro 3P,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004b00000000000000,Hori Real Arcade Pro 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006a00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006b00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008a00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008b00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006f00000000000000,Hori Real Arcade Pro 4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00007000000000000000,Hori Real Arcade Pro 4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00003d00000000000000,Hori Real Arcade Pro N3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b10,leftstick:b4,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b6,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000ae00000000000000,Hori Real Arcade Pro N4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00008c00000000000000,Hori Real Arcade Pro P4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f0000aa00000000000000,Hori Real Arcade Pro S,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000d800000000000000,Hori Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Windows, +030000000d0f00002200000000000000,Hori Real Arcade Pro V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005b00000000000000,Hori Real Arcade Pro V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005c00000000000000,Hori Real Arcade Pro V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000af00000000000000,Hori Real Arcade Pro VHS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00001b00000000000000,Hori Real Arcade Pro VX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b000002f5000000000000,Hori Real Arcade Pro VX,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Windows, +030000000d0f00009c00000000000000,Hori TAC Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000c900000000000000,Hori Taiko Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006400000000000000,Horipad 3TP,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00001300000000000000,Horipad 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005500000000000000,Horipad 4 FPS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006e00000000000000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006600000000000000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000000d0f00004200000000000000,Horipad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000ad1b000001f5000000000000,Horipad EXT2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f0000ee00000000000000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000c100000000000000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000f600000000000000,Horipad Nintendo Switch Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000000d0f00000202000000000000,Horipad O Nintendo Switch 2 Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,misc2:b14,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +030000000d0f00006700000000000000,Horipad One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00009601000000000000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc2:b2,paddle1:b5,paddle2:b15,paddle3:b18,paddle4:b19,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000000d0f0000dc00000000000000,Horipad Switch,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000242e00000b20000000000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Windows, +03000000242e0000ff0b000000000000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Windows, +03000000790000004e95000000000000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Windows, +03000000242e00006a48000000000000,Hyperkin RetroN Sq,a:b3,b:b7,back:b5,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b0,rightshoulder:b1,start:b4,x:b2,y:b6,platform:Windows, +03000000242f00000a20000000000000,Hyperkin Scout,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000242e00000a20000000000000,Hyperkin Scout Premium SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000242e00006a38000000000000,Hyperkin Trooper 2,a:b0,b:b1,back:b4,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b3,start:b5,platform:Windows, +03000000f00300008d04000000000000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:-a2,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:+a5,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000d81d00000e00000000000000,iBuffalo AC02 Arcade Joystick,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,rightx:a2,righty:a5,start:b8,x:b4,y:b5,platform:Windows, +03000000d81d00000f00000000000000,iBuffalo BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d81d00001000000000000000,iBuffalo BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000005c0a00000285000000000000,iDroidCon,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b6,platform:Windows, +03000000696400006964000000000000,iDroidCon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000511d00000230000000000000,iGUGU Gamecore,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b1,leftstick:b4,lefttrigger:b3,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b2,platform:Windows, +03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +030000006f0e00002401000000000000,Injustice Fightstick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000830500005130000000000000,InterAct ActionPad,a:b0,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000ef0500000300000000000000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows, +03000000fd0500000230000000000000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a5,start:b11,x:b0,y:b1,platform:Windows, +03000000fd0500000030000000000000,Interact GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Windows, +03000000fd0500003902000000000000,InterAct Hammerhead,a:b3,b:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b2,lefttrigger:b8,rightshoulder:b7,rightstick:b5,righttrigger:b9,start:b10,x:b0,y:b1,platform:Windows, +03000000fd0500002a26000000000000,InterAct Hammerhead FX,a:b3,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b0,y:b1,platform:Windows, +03000000fd0500002f26000000000000,InterAct Hammerhead FX,a:b4,b:b5,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b1,y:b2,platform:Windows, +03000000fd0500005302000000000000,InterAct ProPad,a:b3,b:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Windows, +03000000ac0500002c02000000000000,Ipega Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000491900000204000000000000,Ipega PG9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000491900000304000000000000,Ipega PG9087,+righty:+a5,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows, +030000007e0500000620000000000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows, +030000007e0500000720000000000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000250900000017000000000000,Joypad Adapter,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows, +03000000bd12000003c0000000000000,Joypad Alpha Shock,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ff1100004033000000000000,JPD FFB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a2,start:b15,x:b3,y:b0,platform:Windows, +03000000242f00002d00000000000000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000242f00008a00000000000000,JYS Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +03000000c4100000c082000000000000,KADE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000828200000180000000000000,Keio,a:b4,b:b5,back:b8,leftshoulder:b2,lefttrigger:b3,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b9,x:b0,y:b1,platform:Windows, +03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000001e0000000000000,Leadership,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000006f0e00000103000000000000,Logic3,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00000104000000000000,Logic3,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000008f0e00001300000000000000,Logic3,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows, +030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400001dc2000000000000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400001ec2000000000000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006d04000019c2000000000000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006d0400001ac2000000000000,Logitech Precision,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000009c2000000000000,Logitech WingMan,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows, +030000006d0400000bc2000000000000,Logitech WingMan Action Pad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b8,lefttrigger:a5~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:a2~,start:b8,x:b3,y:b4,platform:Windows, +030000006d0400000ac2000000000000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows, +03000000380700005645000000000000,Lynx,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000222200006000000000000000,Macally,a:b1,b:b2,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b33,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700003888000000000000,Mad Catz Arcade Fightstick TE S Plus PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008532000000000000,Mad Catz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700006352000000000000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700006652000000000000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000380700005032000000000000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700005082000000000000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000380700008031000000000000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000003807000038b7000000000000,Mad Catz Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, +03000000380700008433000000000000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008483000000000000,Mad Catz Fightstick TE S PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000380700008134000000000000,Mad Catz Fightstick TE2 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008184000000000000,Mad Catz Fightstick TE2 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000380700006252000000000000,Mad Catz Micro CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008232000000000000,Mad Catz PlayStation Brawlpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008731000000000000,Mad Catz PlayStation Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000003807000056a8000000000000,Mad Catz PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700001888000000000000,Mad Catz SFIV Fightstick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000380700008081000000000000,Mad Catz SFV Arcade Fightstick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000380700001847000000000000,Mad Catz Street Fighter 4 Xbox 360 FightStick,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, +03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +030000009f000000adbb000000000000,MaxJoypad Virtual Controller,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows, +030000008f0e00001330000000000000,Mayflash Controller Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000242f00003700000000000000,Mayflash F101,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000790000003018000000000000,Mayflash F300 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000242f00003900000000000000,Mayflash F300 Elite Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000242f00007300000000000000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +0300000079000000d218000000000000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d620000010a7000000000000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000242f0000f500000000000000,Mayflash N64 Adapter,a:b2,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows, +03000000242f0000f400000000000000,Mayflash N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows, +03000000790000007918000000000000,Mayflash N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,righttrigger:b7,rightx:a3,righty:a2,start:b8,platform:Windows, +030000008f0e00001030000000000000,Mayflash Saturn Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +0300000025090000e803000000000000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000790000000318000000000000,Mayflash Wii DolphinBar,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000000790000000018000000000000,Mayflash Wii U Pro Adapter,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000002418000000000000,Mega Drive Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b2,start:b9,x:b3,y:b4,platform:Windows, +0300000079000000ae18000000000000,Mega Drive Controller,a:b0,b:b1,back:b7,dpdown:b14,dpleft:b15,dpright:b13,dpup:b2,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000c0160000990a000000000000,Mega Drive Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,righttrigger:b2,start:b3,platform:Windows, +030000005e0400002800000000000000,Microsoft Dual Strike,a:b3,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,rightx:a0,righty:a1~,start:b5,x:b1,y:b0,platform:Windows, +030000005e0400000300000000000000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows, +030000005e0400000700000000000000,Microsoft SideWinder,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +030000005e0400000e00000000000000,Microsoft SideWinder Freestyle Pro,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows, +030000005e0400002700000000000000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Windows, +03000000280d00000202000000000000,Miller Lite Cantroller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b5,x:b2,y:b3,platform:Windows, +03000000ad1b000023f0000000000000,MLG,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a6,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +03000000ad1b00003ef0000000000000,MLG Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, +03000000380700006382000000000000,MLG PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004523000015e0000000000000,Mobapad Chitu HD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000491900000904000000000000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ffff00000000000000000000,Mocute M053,a:b3,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b11,leftstick:b7,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b6,righttrigger:b4,rightx:a3,righty:a4,start:b8,x:b1,y:b0,platform:Windows, +03000000d6200000e589000000000000,Moga 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows, +03000000d62000007162000000000000,Moga Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows, +03000000d6200000ad0d000000000000,Moga Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c62400002a89000000000000,Moga XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400002b89000000000000,Moga XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400001a89000000000000,Moga XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400001b89000000000000,Moga XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000091200004488000000000000,MUSIA PlayStation 2 Input Display,a:b0,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:b11,rightx:a2,righty:a3,start:b5,x:b1,y:b3,platform:Windows, +03000000f70600000100000000000000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Windows, +030000006b140000010c000000000000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006b1400001106000000000000,Nacon Revolution 3 PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +0300000085320000170d000000000000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +0300000085320000190d000000000000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000006b140000100d000000000000,Nacon Revolution Infinity PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000006b140000080d000000000000,Nacon Revolution Unlimited Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000bd12000001c0000000000000,Nebular,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000eb0300000000000000000000,NeGcon Adapter,a:a2,b:b13,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,lefttrigger:a4,leftx:a1,righttrigger:b11,start:b3,x:a3,y:b12,platform:Windows, +0300000038070000efbe000000000000,NEO SE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +0300000092120000474e000000000000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Windows, +03000000921200004b46000000000000,NES 2 port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Windows, +03000000000f00000100000000000000,NES Controller,a:b1,b:b0,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows, +03000000921200004346000000000000,NES Controller,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows, +03000000790000004518000000000000,NEXILUX GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Windows, +03000000050b00000045000000000000,Nexus,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Windows, +03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000ec110000e1a7000000000000,Nintendo Switch,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000007e0500006920000000000000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Windows, +030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows, +030000007e0500007320000000000000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Windows, +030000007e0500001920000000000000,NSO N64 Controller,+rightx:b8,+righty:b2,-rightx:b3,-righty:b7,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Windows, +030000007e0500001720000000000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Windows, +03000000550900001472000000000000,NVIDIA Controller,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows, +03000000550900001072000000000000,NVIDIA Shield,a:b9,b:b8,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b3,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b2,righttrigger:a4,rightx:a2,righty:a5,start:b0,x:b7,y:b6,platform:Windows, +030000005509000000b4000000000000,NVIDIA Virtual,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000120c00000288000000000000,Nyko Air Flo Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +030000004b120000014d000000000000,NYKO Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000d62000001d57000000000000,Nyko Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000791d00000900000000000000,Nyko Playpad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000782300000a10000000000000,Onlive Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows, +030000000d0f00000401000000000000,Onyx,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000008916000001fd000000000000,Onza CE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a3,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000008916000000fd000000000000,Onza TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000362800000100000000000000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows, +03000000120c0000f60e000000000000,P4 Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +03000000790000002201000000000000,PC Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000006f0e00008501000000000000,PDP Fightpad Pro GameCube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006f0e00000901000000000000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00008901000000000000,PDP Realmz Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00004100000000000000,PlaySega,a:b1,b:b0,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b4,y:b3,platform:Windows, +03000000d620000011a7000000000000,PowerA Core Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000dd62000015a7000000000000,PowerA Fusion Nintendo Switch Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d620000012a7000000000000,PowerA Fusion Nintendo Switch Fight Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000dd62000016a7000000000000,PowerA Fusion Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d620000013a7000000000000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000003340000000000000,PowerA OPS Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000d62000002640000000000000,PowerA OPS Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +0300000062060000d570000000000000,PowerA PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d620000014a7000000000000,PowerA Spectra Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000084ca000000000000,Precision,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c62400001a53000000000000,Pro Ex Mini,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000120c0000110e000000000000,Pro5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000250900000088000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000250900006888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b6,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000151a00006222000000000000,PS2 Dual Plus Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000120a00000100000000000000,PS3 Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000120c00001307000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000120c00001cf1000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000120c0000f90e000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000250900000118000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows, +030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows, +030000004f1f00000800000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Windows, +03000000888800000804000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows, +030000008f0e00000300000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows, +030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ba2200002010000000000000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b2,platform:Windows, +03000000120c00000807000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000111e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000121e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000130e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000150e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000180e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000181e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000191e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c00001e0e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000a957000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000aa57000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000f21c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000f31c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000f41c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000f51c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120c0000f70e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000120e0000120c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000160e0000120c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000001a1e0000120c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000004c0500005f0e000000000000,PS5 Access Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000004c050000f20d000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000830500005020000000000000,PSX,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Windows, +03000000300f00000111000000000000,Qanba 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00000211000000000000,Qanba 2P,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000300f00000011000000000000,Qanba Arcade Stick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows, +03000000300f00001611000000000000,Qanba Arcade Stick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows, +03000000222c00000025000000000000,Qanba Dragon Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000222c00000020000000000000,Qanba Drone Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001211000000000000,Qanba Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001210000000000000,Qanba Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000341a00000104000000000000,Qanba Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows, +03000000222c00000223000000000000,Qanba Obsidian Arcade Stick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000222c00000023000000000000,Qanba Obsidian Arcade Stick PS4,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000008a2400006682000000000000,R1 Mobile Controller,a:b3,b:b1,back:b7,leftx:a0,lefty:a1,start:b6,x:b4,y:b0,platform:Windows, +03000000086700006626000000000000,RadioShack,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows, +03000000ff1100004733000000000000,Ramox FPS Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows, +030000009b2800002300000000000000,Raphnet 3DO Adapter,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b2,start:b3,platform:Windows, +030000009b2800006900000000000000,Raphnet 3DO Adapter,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b2,start:b3,platform:Windows, +030000009b2800000800000000000000,Raphnet Dreamcast Adapter,a:b2,b:b1,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,lefttrigger:a2,leftx:a0,righttrigger:a3,righty:a1,start:b3,x:b10,y:b9,platform:Windows, +030000009b280000d000000000000000,Raphnet Dreamcast Adapter,a:b1,b:b0,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,lefttrigger:+a5,leftx:a0,lefty:a1,righttrigger:+a2,start:b3,x:b5,y:b4,platform:Windows, +030000009b2800006200000000000000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000009b2800003200000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000009b2800006000000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000009b2800001800000000000000,Raphnet Jaguar Adapter,a:b2,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b10,start:b3,x:b11,y:b12,platform:Windows, +030000009b2800003c00000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows, +030000009b2800006100000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows, +030000009b2800006300000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows, +030000009b2800006400000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows, +030000009b2800000200000000000000,Raphnet NES Adapter,a:b7,b:b6,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b4,platform:Windows, +030000009b2800004400000000000000,Raphnet PS1 and PS2 Adapter,a:b1,b:b2,back:b5,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b9,rightx:a3,righty:a4,start:b4,x:b0,y:b3,platform:Windows, +030000009b2800004300000000000000,Raphnet Saturn,a:b0,b:b1,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows, +030000009b2800000500000000000000,Raphnet Saturn Adapter 2.0,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +030000009b2800000300000000000000,Raphnet SNES Adapter,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, +030000009b2800002600000000000000,Raphnet SNES Adapter,a:b1,b:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800002e00000000000000,Raphnet SNES Adapter,a:b1,b:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800002f00000000000000,Raphnet SNES Adapter,a:b1,b:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800005600000000000000,Raphnet SNES Adapter,a:b1,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800005700000000000000,Raphnet SNES Adapter,a:b1,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800001e00000000000000,Raphnet Vectrex Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a1,lefty:a2,x:b2,y:b3,platform:Windows, +030000009b2800002b00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800002c00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows, +030000009b2800008000000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows, +03000000790000008f18000000000000,Rapoo Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows, +0300000032150000a602000000000000,Razer Huntsman V3 Pro,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b12,dpright:b13,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000f8270000bf0b000000000000,Razer Kishi,a:b6,b:b7,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,leftshoulder:b12,leftstick:b19,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b13,rightstick:b20,righttrigger:b15,rightx:a3,righty:a4,start:b17,x:b9,y:b10,platform:Windows, +03000000321500000204000000000000,Razer Panthera PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000104000000000000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000321500000010000000000000,Razer Raiju,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000321500000710000000000000,Razer Raiju TE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000a10000000000000,Razer Raiju TE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000410000000000000,Razer Raiju UE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000910000000000000,Razer Raiju UE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000011000000000000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000921200004547000000000000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b6,x:b3,y:b4,platform:Windows, +03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +03000000830500006020000000000000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, +03000000632500007805000000000000,Retro Fighters Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, +03000000bd12000013d0000000000000,Retrolink Sega Saturn Classic Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows, +03000000bd12000015d0000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000341200000400000000000000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Windows, +0300000000f000000300000000000000,RetroUSB RetroPad,a:b1,b:b5,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, +0300000000f00000f100000000000000,RetroUSB Super RetroPort,a:b1,b:b5,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, +03000000830500000960000000000000,Revenger,a:b0,b:b1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b3,x:b4,y:b5,platform:Windows, +030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006b140000020d000000000000,Revolution Pro Controller 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001f01000000000000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00004601000000000000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c6240000fefa000000000000,Rock Candy Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00008701000000000000,Rock Candy Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000830500007030000000000000,Rockfire Space Ranger,a:b0,b:b1,back:b5,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b9,righttrigger:b8,start:b2,x:b3,y:b4,platform:Windows, +03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows, +03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows, +03000000050b00005819000000000000,ROG Chakram Core,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows, +03000000050b0000181a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows, +03000000050b00001a1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows, +03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows, +030000004f04000001d0000000000000,Rumble Force,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +030000000d0f0000ad00000000000000,RX Gamepad,a:b0,b:b4,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,rightshoulder:b6,start:b9,x:b2,y:b1,platform:Windows, +030000008916000000fe000000000000,Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c6240000045d000000000000,Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e00001311000000000000,Saffun Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b0,platform:Windows, +03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000023f6000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001201000000000000,Saitek Dual Analog,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000a30600000701000000000000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Windows, +03000000a30600000cff000000000000,Saitek P2500 Force Rumble,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b0,y:b1,platform:Windows, +03000000a30600000d5f000000000000,Saitek P2600,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows, +03000000a30600000dff000000000000,Saitek P2600,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b8,x:b0,y:b3,platform:Windows, +03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000018f5000000000000,Saitek P3200,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001001000000000000,Saitek P480 Rumble,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000a30600000901000000000000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b8,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b5,rightx:a3,righty:a2,x:b0,y:b1,platform:Windows, +03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000a30600002106000000000000,Saitek PS1000 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000020f6000000000000,Saitek PS2700 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001101000000000000,Saitek Rumble,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000e804000000a0000000000000,Samsung EIGP20,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c01100000252000000000000,Sanwa Easy Grip,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000c01100004350000000000000,Sanwa Micro Grip P3,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,x:b3,y:b2,platform:Windows, +03000000411200004550000000000000,Sanwa Micro Grip Pro,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a1,righty:a2,start:b9,x:b1,y:b3,platform:Windows, +03000000c01100004150000000000000,Sanwa Micro Grip Pro,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +03000000c01100004450000000000000,Sanwa Online Grip,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b11,righttrigger:b9,rightx:a3,righty:a2,start:b14,x:b3,y:b4,platform:Windows, +03000000730700000401000000000000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows, +030000009d0d00001130000000000000,Sanwa PlayStation Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000830500006120000000000000,Sanwa Smart Grip II,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,x:b1,y:b3,platform:Windows, +03000000c01100000051000000000000,Satechi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +030000004f04000028b3000000000000,Score A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000952e00002577000000000000,Scuf PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000a30c00002500000000000000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Windows, +03000000a30c00002400000000000000,Sega Mega Drive Mini 6B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000d804000086e6000000000000,Sega Multi Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a2,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows, +0300000000050000289b000000000000,Sega Saturn Adapter,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +0300000000f000000800000000000000,Sega Saturn Controller,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b7,righttrigger:b3,start:b0,x:b5,y:b6,platform:Windows, +03000000730700000601000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000b40400000a01000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows, +030000003b07000004a1000000000000,SFX,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Windows, +03000000f82100001900000000000000,Shogun Bros Chameleon X1,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000120c00001c1e000000000000,SnakeByte 4S PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000140300000918000000000000,SNES Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +0300000081170000960a000000000000,SNES Controller,a:b4,b:b0,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b5,y:b1,platform:Windows, +03000000811700009d0a000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, +030000008b2800000300000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, +03000000921200004653000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, +030000008f0e00000910000000000000,Sony DualShock 2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +03000000317300000100000000000000,Sony DualShock 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000666600006706000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows, +03000000e30500009605000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000fe1400002a23000000000000,Sony PlayStation Adapter,a:b0,b:b1,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,x:b2,y:b3,platform:Windows, +030000004c050000da0c000000000000,Sony PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000632500002306000000000000,Sony PlayStation Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000f0250000c183000000000000,Sony PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d9040000160f000000000000,Sony PlayStation Controller Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000ff000000cb01000000000000,Sony PlayStation Portable,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +030000004c0500003713000000000000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000341a00000208000000000000,Speedlink 6555,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000908000000000000,Speedlink 6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000380700001722000000000000,Speedlink Competition Pro,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,x:b2,y:b3,platform:Windows, +030000008f0e00000800000000000000,Speedlink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000de280000fc11000000000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000de280000ff11000000000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000120c0000160e000000000000,Steel Play Metaltech PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000381000001214000000000000,SteelSeries Free,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000381000003014000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000381000003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,platform:Windows, +03000000380700003847000000000000,Street Fighter Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,start:b7,x:b2,y:b3,platform:Windows, +030000001f08000001e4000000000000,Super Famicom Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000790000000418000000000000,Super Famicom Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b33,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +03000000341200001300000000000000,Super Racer,a:b2,b:b3,back:b8,leftshoulder:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b7,x:b0,y:b1,platform:Windows, +03000000457500002211000000000000,Szmy Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f0400000ab1000000000000,T16000M,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b10,x:b2,y:b3,platform:Windows, +030000000d0f00007b00000000000000,TAC GEAR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000e40a00000307000000000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Windows, +03000000e40a00000207000000000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b9,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Windows, +03000000d814000001a0000000000000,TE Kitty,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000ba2200000701000000000000,Technology Innovation PS2 Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b2,platform:Windows, +03000000c61100001000000000000000,Tencent Xianyou Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows, +03000000790000001c18000000000000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000790000002601000000000000,TGZ Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows, +03000000591c00002400000000000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +03000000591c00002600000000000000,THEGamepad,a:b2,b:b1,back:b6,leftx:a0,lefty:a1,start:b7,x:b3,y:b0,platform:Windows, +030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +030000004f04000023b3000000000000,Thrustmaster Dual Trigger PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f0400000ed0000000000000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000008d0000000000000,Thrustmaster Ferrari 150 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows, +030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +030000004f04000003d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000009d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000088ca000000000000,Thunderpad,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +03000000666600000288000000000000,TigerGame PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000666600000488000000000000,TigerGame PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +030000004f04000007d0000000000000,TMini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000571d00002100000000000000,Tomee NES Controller Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Windows, +03000000571d00002000000000000000,Tomee SNES Controller Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c01100000055000000000000,Tronsmart,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows, +03000000411200000450000000000000,Twin Shock,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Windows, +03000000d90400000200000000000000,TwinShock PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000151900005678000000000000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000000b0400003065000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows, +03000000242f00006e00000000000000,USB Controller,a:b1,b:b4,back:b10,leftshoulder:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b7,rightx:a2,righty:a5,start:b11,x:b0,y:b3,platform:Windows, +03000000300f00000701000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000341a00002308000000000000,USB Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000666600000188000000000000,USB Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +030000006b1400000203000000000000,USB Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000790000000a00000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000b404000081c6000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows, +03000000b50700001503000000000000,USB Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000bd12000012d0000000000000,USB Controller,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows, +03000000ff1100004133000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000632500002305000000000000,USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000882800000305000000000000,V5 Game Pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,x:b2,y:b3,platform:Windows, +03000000790000001a18000000000000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000001b18000000000000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000302000000000000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +030000006f0e00000702000000000000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows, +0300000034120000adbe000000000000,vJoy Device,a:b0,b:b1,back:b15,dpdown:b6,dpleft:b7,dpright:b8,dpup:b5,guide:b16,leftshoulder:b9,leftstick:b13,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b14,righttrigger:b12,rightx:a3,righty:a4,start:b4,x:b2,y:b3,platform:Windows, +03000000120c0000ab57000000000000,Warrior Joypad JS083,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000007e0500003003000000000000,Wii U Pro,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,leftshoulder:b6,leftstick:b11,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b12,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +0300000032150000030a000000000000,Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +0300000032150000140a000000000000,Wolverine,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000002e160000efbe000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,rightshoulder:b5,righttrigger:b11,start:b7,x:b2,y:b3,platform:Windows, +03000000380700001647000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000380700002045000000000000,Xbox 360 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700002644000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a2,righty:a5,start:b8,x:b2,y:b3,platform:Windows, +03000000380700002647000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000003807000026b7000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000380700003647000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a7,righty:a5,start:b7,x:b2,y:b3,platform:Windows, +030000005e0400001907000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e0400008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e0400009102000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b000000fd000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b000001fd000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b000016f0000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000ad1b00008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c62400000053000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c6240000fdfa000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000380700002847000000000000,Xbox 360 Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000a102000000000000,Xbox 360 Wireless Receiver,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000120c00000a88000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a2,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +03000000120c00001088000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2~,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5~,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000002a0600002000000000000000,Xbox Controller,a:b0,b:b1,back:b13,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b5,leftstick:b14,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b15,righttrigger:b7,rightx:a2,righty:a5,start:b12,x:b2,y:b3,platform:Windows, +03000000380700001645000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +03000000380700002645000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000380700003645000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +03000000380700008645000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e0400000202000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +030000005e0400008502000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e0400008702000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b7,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +030000005e0400008902000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b10,leftstick:b8,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b9,righttrigger:b4,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, +030000005e0400000c0b000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000d102000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000dd02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000e002000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000e302000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000fd02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ff02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e0000a802000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000006f0e0000c802000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000c62400003a54000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000130b000000000000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000450c00002043000000000000,Xeox SL6556BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006f0e00000300000000000000,XGear,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000e0ff00000201000000000000,Xiaomi Black Shark (L),back:b0,dpdown:b11,dpleft:b9,dpright:b10,dpup:b8,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,platform:Windows, +03000000172700004431000000000000,Xiaomi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000172700003350000000000000,Xiaomi XMGP01YM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000bc2000005060000000000000,Xiaomi XMGP01YM,+lefty:+a2,+righty:+a5,-lefty:-a1,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000c0160000e105000000000000,XinMo Dual Arcade,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000007d0400000340000000000000,Xterminator Digital Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:-a4,lefttrigger:+a4,leftx:a0,lefty:a1,paddle1:b7,paddle2:b6,rightshoulder:b5,rightstick:b9,righttrigger:b2,rightx:a3,righty:a5,start:b8,x:b3,y:b4,platform:Windows, +030000002c3600000100000000000000,Yawman Arrow,+rightx:h0.2,+righty:h0.4,-rightx:h0.8,-righty:h0.1,a:b4,b:b5,back:b6,dpdown:b15,dpleft:b14,dpright:b16,dpup:b13,leftshoulder:b10,leftstick:b0,lefttrigger:-a4,leftx:a0,lefty:a1,paddle1:b11,paddle2:b12,rightshoulder:b8,rightstick:b9,righttrigger:+a4,start:b3,x:b1,y:b2,platform:Windows, +03000000790000004f18000000000000,ZDT Android Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000073500000400000000000000,Zenaim Arcade Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000120c00000500000000000000,Zeroplus Adapter,a:b2,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows, +03000000120c0000101e000000000000,Zeroplus P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, + +# Mac OS X +030000008f0e00000300000009010000,2 In 1 Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000c82d00001930000000000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X, +03000000c82d00001930000000020000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X, +03000000c82d00001930000001000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X, +03000000c82d00000031000001000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00000531000000020000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X, +03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00006a28000000010000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Mac OS X, +03000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001251000000020000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001151000000020000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000a30c00002400000006020000,8BitDo M30,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,guide:b9,leftshoulder:b6,lefttrigger:b5,rightshoulder:b4,righttrigger:b7,start:b8,x:b3,y:b0,platform:Mac OS X, +03000000c82d00000151000000010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00000650000001000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00005106000000010000,8BitDo M30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b2,leftshoulder:b6,lefttrigger:a5,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00002090000000010000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000451000000010000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X, +03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00006928000000010000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X, +03000000c82d00002590000000010000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00002590000001000000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00002690000000010000,8BitDo NEOGEO,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b10,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +030000003512000012ab000001000000,8BitDo NES30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d000012ab000001000000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00002028000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000022000000090000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000190000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000751000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000131000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000231000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000331000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000431000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00002867000000010000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b3,y:b4,platform:Mac OS X, +03000000c82d00003028000000010000,8Bitdo SFC30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000102800000900000000000000,8BitDo SFC30 Joystick,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000351000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001290000001000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00004028000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000260000001000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001b30000001000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001d30000001000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001530000001000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001630000001000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001730000001000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001130000000020000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001330000000020000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001330000001000000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000a00500003232000008010000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000a00500003232000009010000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a31,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000491900001904000001010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Mac OS X, +03000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X, +0300000008100000e501000019040000,Anbernic Handheld,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a4,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000373500004610000001000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000a30c00002700000003030000,Astro City Mini,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000a30c00002800000003030000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a3,lefty:a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000050b00000579000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b42,paddle1:b9,paddle2:b11,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X, +03000000050b00000679000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b23,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X, +03000000503200000110000045010000,Atari VCS Classic,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b3,start:b2,platform:Mac OS X, +03000000503200000110000047010000,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b3,start:b2,platform:Mac OS X, +03000000503200000210000047010000,Atari VCS Modern Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Mac OS X, +030000008a3500000102000000010000,Backbone One,a:b0,b:b1,back:b16,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b17,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b15,x:b2,y:b3,platform:Mac OS X, +030000008a3500000201000000010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000008a3500000202000000010000,Backbone One,a:b0,b:b1,back:b16,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b17,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b15,x:b2,y:b3,platform:Mac OS X, +030000008a3500000402000000010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000008a3500000302000000010000,Backbone One PlayStation Edition,a:b0,b:b1,back:b16,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b17,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b15,x:b2,y:b3,platform:Mac OS X, +03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X, +03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +03000000120c0000200e000000010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +03000000120c0000210e000000010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000d8140000cecf000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X, +03000000a306000022f6000001030000,Cyborg V3 Rumble Pad PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000791d00000103000009010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000006e0500000720000010020000,Elecom JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Mac OS X, +030000006f0e00008401000003010000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000151900004000000001000000,Flydigi Vader 2,a:b14,b:b15,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Mac OS X, +03000000b40400001124000001040000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000b40400001224000003030000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000790000004618000000010000,GameCube Controller Adapter,a:b4,b:b0,dpdown:b56,dpleft:b60,dpright:b52,dpup:b48,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X, +03000000ac0500001a06000002020000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000373500000411000023000000,GameSir X4A Xbox Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000ad1b000001f9000000000000,Gamestop BB070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000006f0e00000102000000000000,GameStop Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000ff1100003133000007010000,GameWare PC Control Pad,a:b2,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Mac OS X, +03000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +030000007d0400000540000001010000,Gravis Eliminator Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000280400000140000000020000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00000300000007010000,GreenAsia Joystick,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Mac OS X, +030000000d0f00002d00000000100000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005f00000000000000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005f00000000010000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005e00000000000000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005e00000000010000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000000d0f00008400000000010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00008500000000010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000341a00000302000014010000,Hori Fighting Stick Mini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00008800000000010000,Hori Fighting Stick mini 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00008700000000010000,Hori Fighting Stick mini 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000000d0f00004d00000000000000,Hori Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00003801000008010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Mac OS X, +030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f0000aa00000072050000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000000d0f00000002000017010000,Hori Split Pad Fit,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00000002000015010000,Hori Switch Split Pad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006e00000000010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006600000000010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006600000000000000,Horipad FPS Plus 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f0000ee00000000010000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f0000c100000072050000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000242e0000ff0b000000010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Mac OS X, +03000000790000004e95000000010000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Mac OS X, +03000000830500006020000000000000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X, +03000000ef0500000300000000020000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Mac OS X, +03000000fd0500000030000010010000,Interact GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Mac OS X, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000242f00002d00000007010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000006d04000019c2000000000000,Logitech Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000019c2000000020000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000000000,Logitech F310,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000006d04000018c2000000010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700005032000000010000,Mad Catz PS3 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700005082000000010000,Mad Catz PS4 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +03000000380700008483000000010000,Mad Catz PS4 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +0300000049190000020400001b010000,Manba One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000790000000600000007010000,Marvo GT-004,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000008f0e00001330000011010000,Mayflash Controller Adapter,a:b2,b:b4,back:b16,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b12,lefttrigger:b16,leftx:a0,lefty:a2,rightshoulder:b14,rightx:a6~,righty:a4,start:b18,x:b0,y:b6,platform:Mac OS X, +03000000790000004318000000010000,Mayflash GameCube Adapter,a:b4,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X, +03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000242f00007300000000020000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Mac OS X, +0300000079000000d218000026010000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d620000010a7000003010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00001030000011010000,Mayflash Saturn Adapter,a:b0,b:b2,dpdown:b28,dpleft:b30,dpright:b26,dpup:b24,leftshoulder:b10,lefttrigger:b14,rightshoulder:b12,righttrigger:b4,start:b18,x:b6,y:b8,platform:Mac OS X, +0300000025090000e803000000000000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X, +03000000790000000318000000010000,Mayflash Wii DolphinBar,a:b8,b:b12,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b44,leftshoulder:b16,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b4,platform:Mac OS X, +03000000790000000018000000000000,Mayflash Wii U Pro Adapter,a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X, +03000000790000000018000000010000,Mayflash Wii U Pro Adapter,a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X, +030000005e0400002800000002010000,Microsoft Dual Strike,a:b3,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,rightx:a0,righty:a1~,start:b5,x:b1,y:b0,platform:Mac OS X, +030000005e0400000300000006010000,Microsoft SideWinder,a:b0,b:b1,back:b9,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Mac OS X, +030000005e0400000700000006010000,Microsoft SideWinder,a:b0,b:b1,back:b8,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Mac OS X, +030000005e0400002700000001010000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Mac OS X, +030000004523000015e0000072050000,Mobapad Chitu HD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X, +03000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c62400002b89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000853200008906000000010000,Nacon Revolution X Unlimited,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000632500007505000000020000,NeoGeo mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000921200004b46000003020000,NES 2-port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Mac OS X, +030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000ec110000e1a7000001010000,Nintendo Switch,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +030000007e0500006920000001010000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Mac OS X, +030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +030000007e0500000920000010020000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:Mac OS X, +050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:Mac OS X, +030000007e0500007320000001010000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Mac OS X, +030000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Mac OS X, +030000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000550900001472000025050000,NVIDIA Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X, +030000004b120000014d000000010000,Nyko Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Mac OS X, +0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Mac OS X, +030000006f0e00000901000002010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00000300000000000000,Piranha Xtreme PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d620000011a7000000020000,PowerA Core Plus Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000d620000011a7000010050000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000100800000300000006010000,PS2 Adapter,a:b2,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c0500006802000072050000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +0300004b4c0500005f0e000000010000,PS5 Access Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000004c050000f20d000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +050000004c050000f20d000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000005e040000e002000001000000,PXN P30 Pro Mobile,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +03000000222c00000225000000010000,Qanba Dragon Arcade Joystick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000222c00000020000000010000,Qanba Drone Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000009b2800005600000020020000,Raphnet SNES Adapter,a:b1,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Mac OS X, +030000009b2800008000000022020000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Mac OS X, +030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000321500000204000000010000,Razer Panthera PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000104000000010000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +03000000321500000010000000010000,Razer Raiju,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000321500000011000000010000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +030000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000632500008005000000010000,Redgear,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000632500002305000000010000,Redragon Saturn,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000921200004547000000020000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b2,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,lefttrigger:b14,rightshoulder:b10,righttrigger:b4,start:b12,x:b6,y:b8,platform:Mac OS X, +03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000790000001100000005010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000830500006020000000010000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Mac OS X, +0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Mac OS X, +03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000341200000400000000000000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Mac OS X, +030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c0500006802000002100000,Rii RK707,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b2,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b3,righttrigger:b9,rightx:a2,righty:a3,start:b1,x:b15,y:b12,platform:Mac OS X, +030000006f0e00008701000005010000,Rock Candy Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000c6240000fefa000000000000,Rock Candy PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000e804000000a000001b010000,Samsung EIGP20,a:b1,b:b3,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b11,leftx:a1,lefty:a3,rightshoulder:b12,rightx:a4,righty:a5,start:b16,x:b7,y:b9,platform:Mac OS X, +03000000730700000401000000010000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Mac OS X, +03000000a30c00002500000006020000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Mac OS X, +03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X, +03000000b40400000a01000000000000,Sega Saturn,a:b0,b:b1,back:b5,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b2,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X, +030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +0300000000f00000f100000000000000,SNES RetroPort,a:b2,b:b3,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,rightshoulder:b7,start:b6,x:b0,y:b1,platform:Mac OS X, +030000004c050000a00b000000000000,Sony DualShock 4 Adapter,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +03000000666600006706000088020000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Mac OS X, +030000004c050000da0c000000010000,Sony PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +030000004c0500003713000000010000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X, +030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +05000000484944204465766963650000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b15,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b14,x:b2,y:b3,platform:Mac OS X, +050000004e696d6275732b0000000000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b15,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b14,x:b2,y:b3,platform:Mac OS X, +03000000381000003014000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000381000003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X, +030000000d0f0000f600000000010000,Switch Hori Pad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000457500002211000000010000,SZMY Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000e40a00000307000001000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X, +03000000e40a00000207000001000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X, +03000000790000001c18000000010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000790000001c18000003100000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000591c00002400000021000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X, +03000000591c00002600000021000000,THEGamepad,a:b2,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Mac OS X, +030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X, +030000004f0400000ed0000000020000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X, +03000000571d00002100000021000000,Tomee NES Controller Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Mac OS X, +03000000bd12000015d0000000010000,Tomee Retro Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000bd12000015d0000000000000,Tomee SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000571d00002000000021000000,Tomee SNES Controller Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005f140000c501000000020000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X, +03000000632500002605000000010000,Uberwith Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c0160000e105000000040000,Ultimate Atari Fight Stick,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,platform:Mac OS X, +03000000151900005678000010010000,Uniplay U6,a:b3,b:b6,back:b25,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b17,leftstick:b31,lefttrigger:b21,leftx:a1,lefty:a3,rightshoulder:b19,rightstick:b33,righttrigger:b23,rightx:a4,righty:a5,start:b27,x:b11,y:b13,platform:Mac OS X, +030000006f0e00000302000025040000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +030000006f0e00000702000003060000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X, +050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X, +050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X, +030000005e0400008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4~,start:b8,x:b2,y:b3,platform:Mac OS X, +030000006f0e00000104000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000c6240000045d000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000050b000003090000,Xbox Elite Controller Series 2,a:b0,b:b1,back:b31,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b53,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000011050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000200b000011050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000200b000013050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000200b000015050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000d102000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000dd02000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000e002000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e040000e002000003090000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e040000e302000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000fd02000003090000,Xbox One Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000220b000013050000,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X, +03000000c62400003a54000000000000,Xbox One PowerA Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000009050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000015050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000007050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000022050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000220b000021050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000120c0000100e000000010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000120c0000101e000000010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, + +# Linux +03000000c82d00001930000011010000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux, +05000000c82d00001930000001000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux, +03000000c82d00000031000011010000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000631000000010000,8BitDo Adapter 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux, +03000000021000000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006a28000000010000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Linux, +03000000c82d00001251000011010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00001151000011010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000151000000010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000650000011010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000a20000000020000,8BitDo M30 Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00002090000011010000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00002090000000010000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000451000000010000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux, +03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00006928000011010000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux, +05000000c82d00006928000000010000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux, +05000000c82d00002590000001000000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000008000000210000011010000,8BitDo NES30,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000c82d00000310000011010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00008010000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux, +03000000022000000090000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000751000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00001030000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000020000000000000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +06000000c82d00000020000006010000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000131000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000231000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000331000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000431000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00002867000000010000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b3,y:b4,platform:Linux, +03000000c82d00000060000011010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +030000003512000012ab000010010000,8BitDo SFC30,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux, +030000003512000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000102800000900000000010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00003028000000010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000351000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00001290000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006228000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000260000011010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000202800000900000000010000,8BitDo SNES30,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000a31000014010000,8BitDo Ultimate 2C,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00001d30000011010000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00001b30000001000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001530000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001630000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001730000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001130000011010000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000631000010010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00000631000014010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00000760000011010000,8BitDo Ultimate Wireless,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00001230000011010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001330000011010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00000121000011010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00000121000000010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000a00500003232000001000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux, +05000000a00500003232000008010000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c01100000355000011010000,Acrux Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00008801000011010000,Afterglow Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000013020000,Afterglow Prismatic Controller 048-007-NA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00001302000000010000,Afterglow Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000020060000,Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000100000008200000011010000,Akishop Customs PS360,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000007c1800000006000010010000,Alienware Dual Compatible Game PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +05000000491900000204000021000000,Amazon Fire Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux, +05000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +0300000008100000e501000001010000,Anbernic Handheld,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a4,start:b11,x:b3,y:b4,platform:Linux, +03000000020500000913000010010000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000373500000710000010010000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000373500004610000001000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000190e00000110000010010000,Aquaplus Piece,a:b1,b:b0,back:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b2,platform:Linux, +03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000a30c00002700000011010000,Astro City Mini,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000a30c00002800000011010000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux, +05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux, +03000000050b00000579000011010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000050b00000679000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux, +03000000503200000110000011010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux, +05000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux, +05000000503200000110000044010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux, +05000000503200000110000046010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux, +03000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Linux, +03000000503200000210000011010000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux, +05000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux, +05000000503200000210000045010000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux, +05000000503200000210000046010000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux, +05000000503200000210000047010000,Atari VCS Modern Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:-a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Linux, +030000008a3500000201000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000008a3500000202000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000008a3500000302000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000008a3500000402000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c62400001b89000011010000,BDA MOGA XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000c21100000791000011010000,Be1 GC101 Controller 1.03,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000c31100000791000011010000,Be1 GC101 Controller 1.03,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e0400008e02000003030000,Be1 GC101 Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000bc2000004d50000011010000,Beitong A1T2 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000bc2000000055000001000000,Betop AX1 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000bc2000006412000011010000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b30,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006b1400000209000011010000,Bigben,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000120c0000300e000011010000,Brook Audio Fighting Board PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000120c0000310e000011010000,Brook Audio Fighting Board PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000120c0000200e000011010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000120c0000210e000011010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000d81d00000b00000010010000,Buffalo BSGP1601,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Linux, +03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000af1e00002400000010010000,Clockwork Pi DevTerm,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b9,x:b3,y:b0,platform:Linux, +030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux, +03000000632500007a05000001020000,Cosmic Byte Ares Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux, +03000000a306000022f6000011010000,Cyborg V3 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +030000005e0400008e02000002010000,Data Frog S80,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux, +03000000791d00000103000010010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000c11100000191000011010000,EasySMX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000242f00009100000000010000,EasySMX ESM-9101,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006e0500000320000010010000,Elecom U3613M,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux, +030000006e0500000720000010010000,Elecom W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux, +030000007d0400000640000010010000,Eliminator AfterShock,a:b1,b:b2,back:b9,dpdown:+a3,dpleft:-a5,dpright:+a5,dpup:-a3,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a4,righty:a2,start:b8,x:b0,y:b3,platform:Linux, +03000000430b00000300000000010000,EMS Production PS2 Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00008401000011010000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00008101000011010000,Faceoff Deluxe Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00008001000011010000,Faceoff Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000852100000201000010010000,FF GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +05000000b40400001224000001010000,Flydigi APEX 4,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b20,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000b40400001124000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000b40400001224000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000151900004000000001000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000007e0500003703000000000000,GameCube Adapter,a:b0,b:b1,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux, +19000000030000000300000002030000,GameForce Controller,a:b1,b:b0,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000373500000b10000019010000,GameSir Cyclone 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000ac0500005b05000010010000,GameSir G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000558500001b06000010010000,GameSir G4 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000ac0500002d0200001b010000,GameSir G4s,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b33,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000ac0500007a05000011010000,GameSir G5,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000373500009710000001020000,GameSir Kaleid Flux,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000ac0500001a06000011010000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000bc2000005656000011010000,GameSir T4w,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000373500009410000010010000,GameSir Tegenaria Lite,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008f0e00000800000010010000,Gasia PlayStation Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000451300000010000010010000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000f025000021c1000010010000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000f0250000c283000010010000,Gioteck VX2 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +190000004b4800000010000000010000,GO-Advance Controller,a:b1,b:b0,back:b10,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,leftshoulder:b4,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b13,start:b15,x:b2,y:b3,platform:Linux, +190000004b4800000010000001010000,GO-Advance Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b13,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b16,righttrigger:b15,start:b17,x:b2,y:b3,platform:Linux, +190000004b4800000011000000010000,GO-Super Gamepad,a:b0,b:b1,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b3,y:b2,platform:Linux, +03000000f0250000c183000010010000,Goodbetterbest Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000001010000,GPD Win Max 2 6800U Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000007d0400000540000000010000,Gravis Eliminator Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000280400000140000000010000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00000610000000010000,GreenAsia Electronics Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux, +030000008f0e00001200000010010000,GreenAsia Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000008a2e0000dd10000011010000,Hand Held Legend GC Ultimate,a:b0,b:b2,back:b17,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b18,leftshoulder:b10,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b19,misc2:b24,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b1,y:b3,platform:Linux, +030000008a2e0000df10000011010000,Hand Held Legend ProGCC,a:b1,b:b0,back:b17,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b18,leftshoulder:b10,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b19,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b3,y:b2,platform:Linux, +06000000adde0000efbe000002010000,Hidromancer Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d81400000862000011010000,HitBox PS3 PC Analog Mode,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux, +03000000c9110000f055000011010000,HJC Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00006d00000020010000,Hori EDGE 301,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:+a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00008400000011010000,Hori Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00005f00000011010000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00005e00000011010000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000000d0f00005001000009040000,Hori Fighting Commander Octa Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00008500000010010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00008600000002010000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00003701000013010000,Hori Fighting Stick Mini,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b3,y:b2,platform:Linux, +030000000d0f00008800000011010000,Hori Fighting Stick mini 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00008700000011010000,Hori Fighting Stick mini 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,rightstick:b11,righttrigger:a4,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000000d0f00001000000011010000,Hori Fightstick 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b000003f5000033050000,Hori Fightstick VX,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b8,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00004d00000011010000,Hori Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00003801000011010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Linux, +030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00001100000011010000,Hori Real Arcade Pro 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00002200000011010000,Hori Real Arcade Pro 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006a00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006b00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00001600000000010000,Hori Real Arcade Pro EXSE,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f0000aa00000011010000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000000d0f00008501000017010000,Hori Split Pad Fit,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00008501000015010000,Hori Switch Split Pad Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00006e00000011010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006600000011010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000000d0f0000ee00000011010000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000c100000011010000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006700000001010000,Horipad One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f0000ab01000011010000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000000d0f00009601000091000000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000000d0f0000f600000001000000,Horipad Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000341a000005f7000010010000,HuiJia GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +05000000242e00000b20000001000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Linux, +03000000242e0000ff0b000011010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Linux, +03000000242e00006a38000010010000,Hyperkin Trooper 2,a:b0,b:b1,back:b4,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b3,start:b5,platform:Linux, +03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000f00300008d03000011010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000830500006020000010010000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +03000000d80400004bea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux, +03000000d80400004aea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux, +030000008a2e0000d910000011010000,icedragon.io STAC2 Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux, +030000008a2e0000e910000011010000,icedragon.io STAC2 Dance Pad,a:b8,b:b9,x:b10,y:b11,back:b12,platform:Linux, +030000008f0e00001330000001010000,iCode Retro Adapter,b:b3,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b1,start:b7,x:b2,y:b0,platform:Linux, +050000006964726f69643a636f6e0000,idroidcon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700001503000010010000,Impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000d80400008200000003000000,IMS PCU0,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux, +03000000120c00000500000010010000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux, +03000000ef0500000300000000010000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux, +03000000fd0500000030000000010000,InterAct GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Linux, +03000000fd0500002a26000000010000,InterAct HammerHead FX,a:b3,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b2,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux, +0500000049190000020400001b010000,Ipega PG 9069,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000632500007505000011010000,Ipega PG 9099,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +0500000049190000030400001b010000,Ipega PG9099,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000491900000204000000000000,Ipega PG9118,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000300f00001101000010010000,Jess Tech Colour Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000300f00001001000010010000,Jess Tech Dual Analog Rumble,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000ba2200002010000001010000,Jess Technology Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux, +050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux, +03000000bd12000003c0000010010000,Joypad Alpha Shock,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000242f00002d00000011010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000242f00008a00000011010000,JYS Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux, +030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d040000d1ca000000000000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001dc2000014400000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000019200000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000020200000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000019c2000011010000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001fc2000005030000,Logitech F710,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux, +030000006d0400000ac2000010010000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux, +05000000380700006652000025010000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008532000010010000,Mad Catz Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000380700005032000011010000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700005082000011010000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000380700008031000011010000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008081000011010000,Mad Catz FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008034000011010000,Mad Catz Fightstick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008084000011010000,Mad Catz Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000380700008433000011010000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008483000011010000,Mad Catz Fightstick TE S PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000380700001888000010010000,Mad Catz Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700003888000010010000,Mad Catz Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700001647000010040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000380700003847000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000120c00000500000000010000,Manta DualShock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +030000008f0e00001330000010010000,Mayflash Controller Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000790000004318000010010000,Mayflash GameCube Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux, +0300000079000000d218000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d620000010a7000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000242f0000f700000001010000,Mayflash Magic S Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008f0e00001030000010010000,Mayflash Saturn Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Linux, +0300000025090000e803000001010000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +03000000790000000318000011010000,Mayflash Wii DolphinBar,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux, +03000000790000000018000011010000,Mayflash Wii U Pro Adapter,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700001203000010010000,Mega World Logic 3 Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000b50700004f00000000010000,Mega World Logic 3 Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux, +03000000780000000600000010010000,Microntek Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +030000005e0400002800000000010000,Microsoft Dual Strike,a:b3,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,rightx:a0,righty:a1~,start:b5,x:b1,y:b0,platform:Linux, +030000005e0400000300000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux, +030000005e0400000700000000010000,Microsoft SideWinder,a:b0,b:b1,back:b8,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Linux, +030000005e0400000e00000000010000,Microsoft SideWinder Freestyle Pro,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux, +030000005e0400002700000000010000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Linux, +030000005e0400008502000000010000,Microsoft Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e0400008902000021010000,Microsoft Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e0400008e02000001000000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.1,dpleft:h0.2,dpright:h0.8,dpup:h0.4,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000004010000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000056210000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000062230000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000001010000,Microsoft Xbox One a,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000003020000,Microsoft Xbox One b,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000dd02000003020000,Microsoft Xbox One c,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000008040000,Microsoft Xbox One d,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea0200000f050000,Microsoft Xbox One e,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b000009050000,Microsoft Xbox One d,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000e302000003020000,Microsoft Xbox One Elite,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000000b000007040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000000b000008040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000050b000003090000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e0400008e02000030110000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b00000b050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000016050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000017050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b000001050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +03000000790000001c18000010010000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +05000000e80400006e0400001b010000,Mocute 053X M59,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000004d4f435554452d3035305800,Mocute 054X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000d6200000e589000001000000,Moga 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +05000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +03000000c62400002b89000011010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c62400001a89000000010000,MOGA XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000250900006688000000010000,MP8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +030000005e0400008e02000010020000,MSI GC20 V2,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000f70600000100000000010000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Linux, +030000006b1400000906000014010000,Nacon Asymmetric Wireless PS4 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006b140000010c000010010000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000853200000706000012010000,Nacon GC-100,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +05000000853200000503000000010000,Nacon MG-X Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +0300000085320000170d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +0300000085320000190d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux, +03000000790000004518000010010000,Nexilux GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Linux, +060000007e0500003713000000000000,Nintendo 3DS,a:b0,b:b1,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b2,y:b3,platform:Linux, +03000000ec110000e1a7000010010000,Nintendo Switch,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000007e0500006920000011010000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Linux, +060000004e696e74656e646f20537700,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +060000007e0500000620000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +060000007e0500000820000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +050000004c69632050726f20436f6e00,Nintendo Switch Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000620000001800000,Nintendo Switch Left Joy-Con,a:b16,b:b15,back:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,platform:Linux, +030000007e0500000920000000026803,Nintendo Switch Pro Controller,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Linux, +030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +050000007e0500000720000001800000,Nintendo Switch Right Joy-Con,a:b1,b:b2,back:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,platform:Linux, +05000000010000000100000003000000,Nintendo Wii Remote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500003003000001000000,Nintendo Wii U Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +050000005a1d00000218000003000000,Nokia GC 5000,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000000d0500000308000010010000,Nostromo n45 Dual Analog,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux, +030000007e0500007320000011010000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Linux, +030000007e0500001920000011810000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux, +050000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Linux, +050000007e0500001920000001800000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux, +030000007e0500001e20000011810000,NSO Sega Genesis Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,misc1:b3,rightshoulder:b2,righttrigger:b4,start:b5,platform:Linux, +030000007e0500001720000011810000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux, +050000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b8,start:b10,x:b3,y:b2,platform:Linux, +050000007e0500001720000001800000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux, +03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000550900001472000011010000,NVIDIA Controller,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux, +05000000550900001472000001000000,NVIDIA Controller,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux, +030000004b120000014d000000010000,NYKO Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +19000000010000000100000001010000,ODROID Go 2,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux, +19000000010000000200000011000000,ODROID Go 2,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux, +05000000362800000100000002010000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux, +05000000362800000100000003010000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux, +05000000362800000100000004010000,OUYA Controller,a:b0,b:b3,back:b14,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,start:b16,x:b1,y:b2,platform:Linux, +03000000830500005020000010010000,Padix Rockfire PlayStation Bridge,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Linux, +03000000ff1100003133000010010000,PC Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e0000b802000001010000,PDP Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000b802000013020000,PDP Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000d702000006640000,PDP Black Camo Wired Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b13,dpup:b14,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00008501000011010000,PDP Fightpad Pro Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00002801000011010000,PDP PS3 Rock Candy Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00000901000011010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00002f01000011010000,PDP Wired PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b000004f9000000010000,PDP Xbox 360 Versus Fighting,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000f102000000000000,PDP Xbox Atomic,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000a802000023020000,PDP Xbox One Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e0000a702000023020000,PDP Xbox One Raven Black,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000d802000006640000,PDP Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000ef02000007640000,PDP Xbox Series Kinetic Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000540000001010000,PowerA Advantage Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d620000011a7000011010000,PowerA Core Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000dd62000015a7000011010000,PowerA Fusion Nintendo Switch Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d620000012a7000011010000,PowerA Fusion Nintendo Switch Fight Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d62000000140000001010000,PowerA Fusion Pro 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000dd62000016a7000000000000,PowerA Fusion Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000c62400001a53000000010000,PowerA Mini Pro Ex,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d620000013a7000011010000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d620000014a7000011010000,PowerA Spectra Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000c62400001a58000001010000,PowerA Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000220000001010000,PowerA Xbox One Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux, +03000000d62000000228000001010000,PowerA Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400001a54000001010000,PowerA Xbox One Mini Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000240000001010000,PowerA Xbox One Spectra Infinity,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000520000050010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000b20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux, +03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000120c0000160e000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000005f1400003102000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +050000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +03000000c01100000140000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000c405000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +0300004b4c0500005f0e000011010000,PS5 Access Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000e60c000011810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000f20d000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000004c050000f20d000011810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +050000004c050000e60c000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000f20d000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +050000004c050000f20d000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +03000000300f00001211000011010000,Qanba Arcade Joystick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux, +03000000222c00000225000011010000,Qanba Dragon Arcade Joystick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000222c00000025000011010000,Qanba Dragon Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000222c00001220000011010000,Qanba Drone 2 Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000222c00001020000011010000,Qanba Drone 2 Arcade Joystick PS5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000222c00000020000011010000,Qanba Drone Arcade PS4 Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000300f00001210000010010000,Qanba Joystick Plus,a:b0,b:b1,back:b8,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,start:b9,x:b2,y:b3,platform:Linux, +03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000009b2800000300000001010000,Raphnet 4nes4snes,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux, +030000009b2800004200000001010000,Raphnet Dual NES Adapter,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Linux, +0300132d9b2800006500000000000000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +0300132d9b2800006500000001010000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800003200000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800006000000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800003c00000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux, +030000009b2800006100000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux, +030000009b2800006300000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux, +030000009b2800006400000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux, +030000009b2800008000000020020000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux, +030000009b2800008000000001010000,Raphnet Wii Classic Adapter V3,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux, +03000000f8270000bf0b000011010000,Razer Kishi,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000204000011010000,Razer Panthera PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000104000011010000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000321500000810000011010000,Razer Panthera PS4 Evo Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000321500000010000011010000,Razer Raiju,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000321500000a10000001000000,Razer Raiju Tournament Edition,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000011000011010000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000b10000011010000,Razer Wolverine PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +0300000032150000140a000001010000,Razer Wolverine Ultimate Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f0000c100000010010000,Retro Bit Legacy16,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b12,leftshoulder:b4,lefttrigger:b6,misc1:b13,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000790000001100000010010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Linux, +0300000003040000c197000011010000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux, +190000004b4800000111000000010000,RetroGame Joypad,a:b1,b:b0,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux, +0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, +00000000526574726f53746f6e653200,RetroStone 2 Controller,a:b1,b:b0,back:b10,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,platform:Linux, +03000000341200000400000000010000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Linux, +030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00008701000011010000,Rock Candy Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000c6240000fefa000000010000,Rock Candy Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00001311000011010000,Saffun Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b0,platform:Linux, +03000000a306000023f6000011010000,Saitek Cyborg PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000a30600001005000000010000,Saitek P150,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b2,righttrigger:b5,x:b3,y:b4,platform:Linux, +03000000a30600000701000000010000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Linux, +03000000a30600000cff000010010000,Saitek P2500 Force Rumble,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b0,y:b1,platform:Linux, +03000000a30600000d5f000010010000,Saitek P2600,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux, +03000000a30600000c04000011010000,Saitek P2900,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux, +03000000a306000018f5000010010000,Saitek P3200 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000300f00001201000010010000,Saitek P380,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux, +03000000a30600000b04000000010000,Saitek P990 Dual Analog,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux, +03000000a306000020f6000011010000,Saitek PS2700 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +05000000e804000000a000001b010000,Samsung EIGP20,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000d81d00000e00000010010000,Savior,a:b0,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b9,x:b4,y:b5,platform:Linux, +03000000952e00004b43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000952e00004d43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000952e00004e43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000a30c00002500000011010000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Linux, +03000000790000001100000011010000,Sega Saturn,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Linux, +03000000b40400000a01000000010000,Sega Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux, +03000000632500002305000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000632500002605000010010000,ShanWan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000632500007505000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000bc2000000055000010010000,ShanWan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000341a00000908000010010000,SL6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000004b2900000430000011000000,Snakebyte Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000004c050000cc09000001000000,Sony DualShock 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +03000000666600006706000000010000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux, +030000004c050000da0c000011010000,Sony PlayStation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000d9040000160f000000010000,Sony PlayStation Controller Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000ff000000cb01000010010000,Sony PlayStation Portable,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux, +030000004c0500003713000011010000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000250900000500000000010000,Sony PS2 pad with SmartJoy Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +030000005e0400008e02000073050000,Speedlink Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000020200000,SpeedLink Xeox Pro Analog,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000512000010010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux, +03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux, +03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000004e696d6275732b0000000000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b10,guide:b11,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Linux, +03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000381000003114000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000ad1b000038f0000090040000,Street Fighter IV Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000003b07000004a1000000010000,Suncom SFX Plus,a:b0,b:b2,back:b7,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Linux, +030000001f08000001e4000010010000,Super Famicom Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, +030000008f0e00000d31000010010000,SZMY Power 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000457500000401000011010000,SZMY Power DS4 Wired Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000457500002211000010010000,SZMY Power Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000008f0e00001431000010010000,SZMY Power PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000e40a00000307000011010000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Linux, +03000000e40a00000207000011010000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Linux, +03000000ba2200000701000001010000,Technology Innovation PS2 Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b2,platform:Linux, +03000000790000001c18000011010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000591c00002400000010010000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux, +03000000591c00002600000010010000,THEGamepad,a:b2,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux, +030000004f04000015b3000001010000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000020b3000010010000,Thrustmaster Dual Trigger,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000023b3000000010000,Thrustmaster Dual Trigger PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004f0400000ed0000011010000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700000399000000010000,Thrustmaster Firestorm Digital 2,a:b2,b:b4,back:b11,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b0,righttrigger:b9,start:b1,x:b3,y:b5,platform:Linux, +030000004f04000003b3000010010000,Thrustmaster Firestorm Dual Analog 2,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b9,rightx:a2,righty:a3,x:b1,y:b3,platform:Linux, +030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux, +030000004f04000004b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000026b3000002040000,Thrustmaster GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000025b000002020000,Thrustmaster GPX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000004f04000008d0000000010000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000009d0000000010000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000007d0000000010000,Thrustmaster T Mini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000012b3000010010000,Thrustmaster Vibrating Gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +03000000571d00002000000010010000,Tomee SNES Adapter,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux, +03000000bd12000015d0000010010000,Tomee SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000d814000007cd000011010000,Toodles 2008 Chimp PC PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux, +030000005e0400008e02000070050000,Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c01100000591000011010000,Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000680a00000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux, +03000000780300000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux, +03000000e00d00000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux, +03000000f00600000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux, +030000005f140000c501000010010000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +06000000f51000000870000003010000,Turtle Beach Recon,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000100800000100000010010000,Twin PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000c0160000e105000010010000,Ultimate Atari Fight Stick,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,platform:Linux, +03000000151900005678000010010000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux, +03000000790000001100000000010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux, +03000000790000001a18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000790000001b18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00000302000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +030000006f0e00000702000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, +05000000ac0500003232000001000000,VR Box Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +05000000434f4d4d414e440000000000,VX Gaming Command Series,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +0000000058626f782033363020576900,Xbox 360 Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux, +030000005e0400001907000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000014010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400009102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000030060000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00001503000000020000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000000010000,Xbox 360 EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000014010000,Xbox 360 Receiver,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0000000058626f782047616d65706100,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400000202000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e0400008e02000072050000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00001304000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000ffff0000ffff000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e0400000a0b000005040000,Xbox One Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000001030000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000e002000003090000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000fd02000003090000,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000dd02000003020000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000e302000002090000,Xbox One Elite,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000220b000013050000,Xbox One Elite 2 Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000050b000002090000,Xbox One Elite Series 2,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000ea02000011050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000015050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000017050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000ea0200000b050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000ea0200000d050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000ea02000016050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000001050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000005050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000009050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000011050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000014050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000015050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000007050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000009050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000011050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000015050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000017050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +060000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000130b000022050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +060000005e040000120b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000200b000023050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000450c00002043000010010000,XEOX SL6556 BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +05000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux, +03000000c0160000e105000001010000,XinMo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux, +030000005e0400008e02000020010000,XInput Adapter,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000120c0000100e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000120c0000101e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000120c0000182e000011010000,Zeroplus PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000790000002201000011010000,ZhiXu GuliKit D,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, + +# Android +38653964633230666463343334313533,8BitDo Adapter,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +36666264316630653965636634386234,8BitDo Adapter 2,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38426974446f20417263616465205374,8BitDo Arcade Stick,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +61393962646434393836356631636132,8BitDo Arcade Stick,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android, +64323139346131306233636562663738,8BitDo Arcade Stick,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android, +64643565386136613265663236636564,8BitDo Arcade Stick,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android, +33313433353539306634656436353432,8BitDo Dogbone,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38426974446f20446f67626f6e65204d,8BitDo Dogbone,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,platform:Android, +34343439373236623466343934376233,8BitDo FC30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b28,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b29,righttrigger:b7,start:b5,x:b30,y:b2,platform:Android, +38426974446f204e4743204d6f646b69,8BitDo GameCube,a:b0,b:b2,back:b4,dpdown:b12,dpleft:b13,dpright:b14,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b18,paddle2:b17,rightshoulder:b15,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b1,y:b3,platform:Android, +38426974446f2038426974446f204c69,8BitDo Lite,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +30643332373663313263316637356631,8BitDo Lite 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f204c6974652032000000,8BitDo Lite 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +62656331626461363634633735353032,8BitDo Lite 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38393936616436383062666232653338,8BitDo Lite SE,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f204c6974652053450000,8BitDo Lite SE,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +39356430616562366466646636643435,8BitDo Lite SE,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000006500000ffff3f00,8BitDo M30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b17,leftshoulder:b9,lefttrigger:a5,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000051060000ffff3f00,8BitDo M30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b17,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,platform:Android, +32323161363037623637326438643634,8BitDo M30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +33656266353630643966653238646264,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:a5,start:b10,x:b19,y:b2,platform:Android, +38426974446f204d3330204d6f646b69,8BitDo M30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +39366630663062373237616566353437,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,start:b6,x:b2,y:b3,platform:Android, +64653533313537373934323436343563,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,start:b6,x:b2,y:b3,platform:Android, +66356438346136366337386437653934,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,start:b18,x:b19,y:b2,platform:Android, +66393064393162303732356665666366,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,platform:Android, +38426974446f204d6963726f2067616d,8BitDo Micro,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:b0,lefty:b1,rightshoulder:b10,righttrigger:a5,rightx:b2,righty:b3,start:b6,x:b3,y:b2,platform:Android, +61653365323561356263373333643266,8BitDo Micro,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:b0,lefty:b1,rightshoulder:b10,righttrigger:a5,rightx:b2,righty:b3,start:b6,x:b3,y:b2,platform:Android, +62613137616239666338343866326336,8BitDo Micro,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:b0,lefty:b1,rightshoulder:b10,righttrigger:a5,rightx:b2,righty:b3,start:b6,x:b3,y:b2,platform:Android, +33663431326134333366393233616633,8BitDo N30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,platform:Android, +38426974446f204e3330204d6f646b69,8BitDo N30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,platform:Android, +05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38323035343766666239373834336637,8BitDo N64,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,platform:Android, +38426974446f204e3634204d6f646b69,8BitDo N64,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,platform:Android, +32363135613966656338666638666237,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35363534633333373639386466346631,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38426974446f204e454f47454f204750,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +39383963623932353561633733306334,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38313433643131656262306631373166,8BitDo P30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38326536643339353865323063616339,8BitDo P30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38426974446f2050333020636c617373,8BitDo P30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35376664343164386333616535333434,8BitDo Pro 2,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,start:b10,x:b19,y:b2,platform:Android, +38426974446f2038426974446f205072,8BitDo Pro 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f2050726f203200000000,8BitDo Pro 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +61333362366131643730353063616330,8BitDo Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +62373739366537363166326238653463,8BitDo Pro 2,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b3,y:b2,platform:Android, +38386464613034326435626130396565,8BitDo Receiver,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f2038426974446f205265,8BitDo Receiver,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +66303230343038613365623964393766,8BitDo Receiver,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f20533330204d6f646b69,8BitDo S30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +66316462353561376330346462316137,8BitDo S30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974646f20534633302050726f00,8BitDo SF30 Pro,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b17,platform:Android, +61623334636338643233383735326439,8BitDo SFC30,a:b0,b:b1,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b3,rightshoulder:b31,start:b5,x:b30,y:b2,platform:Android, +05000000c82d000012900000ffff3f00,8BitDo SN30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000062280000ffff3f00,8BitDo SN30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +38316230613931613964356666353839,8BitDo SN30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f20534e3330204d6f646b,8BitDo SN30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +65323563303231646531383162646335,8BitDo SN30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +35383531346263653330306238353131,8BitDo SN30 PP,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000002600000ffff0f00,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +36653638656632326235346264663661,8BitDo SN30 Pro Plus,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android, +38303232393133383836366330346462,8BitDo SN30 Pro Plus,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android, +38346630346135363335366265656666,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38426974446f20534e33302050726f2b,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +536f6e7920436f6d707574657220456e,8BitDo SN30 Pro Plus,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +66306331643531333230306437353936,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000002028000009000000ffff3f00,8BitDo SNES30,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000003512000020ab000000780f00,8BitDo SNES30,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,platform:Android, +33666663316164653937326237613331,8BitDo Zero,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android, +38426974646f205a65726f2047616d65,8BitDo Zero,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android, +05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +33663434393362303033616630346337,8BitDo Zero 2,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android, +34656330626361666438323266633963,8BitDo Zero 2,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b20,start:b10,x:b19,y:b2,platform:Android, +63396666386564393334393236386630,8BitDo Zero 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +63633435623263373466343461646430,8BitDo Zero 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android, +32333634613735616163326165323731,Amazon Luna Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +4c696e757820342e31392e3137322077,Anbernic Handheld,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a4,start:b6,x:b2,y:b3,platform:Android, +417374726f2063697479206d696e6920,Astro City Mini,a:b23,b:b22,back:b29,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b25,righttrigger:b26,start:b30,x:b24,y:b21,platform:Android, +35643263313264386134376362363435,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,start:b6,platform:Android, +32353831643566306563643065356239,Atari VCS Modern Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4f64696e20436f6e74726f6c6c657200,AYN Odin,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b14,dpright:b13,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +32303165626138343962363666346165,Brook Mars PS4 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +38383337343564366131323064613561,Brook Mars PS4 Controller,a:b1,b:b19,back:b17,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +34313430343161653665353737323365,Elecom JC-W01U,a:b23,b:b24,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b22,platform:Android, +4875694a6961204a432d573031550000,Elecom JC-W01U,a:b23,b:b24,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b22,platform:Android, +30363230653635633863366338623265,Evo VR,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,x:b2,y:b3,platform:Android, +05000000b404000011240000dfff3f00,Flydigi Vader 2,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b17,paddle2:b18,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +34323662653333636330306631326233,Google Nexus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35383633353935396534393230616564,Google Stadia Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +476f6f676c65204c4c43205374616469,Google Stadia Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +5374616469614e3848532d6532633400,Google Stadia Controller,a:b0,b:b1,back:b15,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, +05000000d6020000e5890000dfff3f80,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,platform:Android, +66633030656131663837396562323935,Hori Battle,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +35623466343433653739346434636330,Hori Fighting Commander 3 Pro,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +484f524920434f2e2c4c54442e203130,Hori Fighting Commander 3 Pro,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b20,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b9,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +484f524920434f2e2c4c544420205041,Hori Gem Pad 3,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b0,y:b2,platform:Android, +65656436646661313232656661616130,Hori PC Engine Mini Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b18,platform:Android, +31303433326562636431653534636633,Hori Real Arcade Pro 3,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +32656664353964393561366362333636,Hori Switch Split Pad Pro,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +30306539356238653637313730656134,HORIPAD Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android, +48797065726b696e2050616400000000,Hyperkin Admiral N64 Controller,+rightx:b6,+righty:b7,-rightx:b17,-righty:b5,a:b1,b:b0,leftshoulder:b3,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,platform:Android, +62333331353131353034386136626636,Hyperkin Admiral N64 Controller,+rightx:b6,+righty:b7,-rightx:b17,-righty:b5,a:b1,b:b0,leftshoulder:b3,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,platform:Android, +31306635363562663834633739396333,Hyperkin N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android, +5368616e57616e202020202048797065,Hyperkin N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android, +0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b2,y:b3,platform:Android, +5553422c322d6178697320382d627574,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b17,rightshoulder:b18,start:b10,x:b3,y:b2,platform:Android, +64306137363261396266353433303531,InterAct GoPad,a:b24,b:b25,leftshoulder:b23,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,x:b21,y:b22,platform:Android, +532e542e442e20496e74657261637420,InterAct HammerHead FX,a:b23,b:b24,back:b30,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,leftstick:b22,lefttrigger:b28,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b25,righttrigger:b29,rightx:a2,righty:a3,start:b31,x:b20,y:b21,platform:Android, +65346535636333663931613264643164,Joy-Con,a:b21,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b23,y:b24,platform:Android, +33346566643039343630376565326335,Joy-Con (L),a:b0,b:b1,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b17,x:b19,y:b2,platform:Android, +35313531613435623366313835326238,Joy-Con (L),a:b0,b:b1,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b17,x:b19,y:b2,platform:Android, +4a6f792d436f6e20284c290000000000,Joy-Con (L),a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b17,x:b19,y:b2,platform:Android, +38383665633039363066383334653465,Joy-Con (R),a:b0,b:b1,back:b5,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android, +39363561613936303237333537383931,Joy-Con (R),a:b0,b:b1,back:b5,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android, +39373064396565646338333134303131,Joy-Con (R),a:b1,b:b2,back:b5,leftstick:b8,leftx:a1~,lefty:a0,start:b6,x:b0,y:b3,platform:Android, +4a6f792d436f6e202852290000000000,Joy-Con (R),a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android, +39656136363638323036303865326464,JYS Aapter,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +63316564383539663166353034616434,JYS Adapter,a:b1,b:b3,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b0,y:b2,platform:Android, +64623163333561643339623235373232,Logitech F310,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35623364393661626231343866613337,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4c6f6769746563682047616d65706164,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +64396331333230326333313330336533,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +39653365373864633935383236363438,Logitech G Cloud,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +416d617a6f6e2047616d6520436f6e74,Luna Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +4c756e612047616d6570616400000000,Luna Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +30363066623539323534363639323363,Magic NS,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +31353762393935386662336365626334,Magic NS,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +39623565346366623931666633323530,Magic NS,a:b1,b:b3,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b0,y:b2,platform:Android, +6d6179666c617368206c696d69746564,Mayflash GameCube Adapter,a:b22,b:b21,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a5,righty:a2,start:b30,x:b23,y:b24,platform:Android, +436f6e74726f6c6c6572000000000000,Mayflash N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android, +65666330633838383061313633326461,Mayflash N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android, +37316565396364386635383230353365,Mayflash Saturn Adapter,a:b21,b:b22,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,lefttrigger:b28,rightshoulder:b27,righttrigger:b23,start:b30,x:b24,y:b25,platform:Android, +4875694a696120205553422047616d65,Mayflash Saturn Adapter,a:b21,b:b22,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,lefttrigger:b28,rightshoulder:b27,righttrigger:b23,start:b30,x:b24,y:b25,platform:Android, +535a4d792d706f776572204c54442043,Mayflash Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b31,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android, +30653962643666303631376438373532,Mayflash Wii DolphinBar,a:b23,b:b24,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b0,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b22,platform:Android, +39346131396233376535393665363161,Mayflash Wii U Pro Adapter,a:b22,b:b23,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,leftstick:b31,lefttrigger:b27,rightshoulder:b26,rightstick:b0,righttrigger:b28,rightx:a0,righty:a1,start:b30,x:b21,y:b24,platform:Android, +31323564663862633234646330373138,Mega Drive,a:b23,b:b22,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b25,righttrigger:b26,start:b30,x:b24,y:b21,platform:Android, +37333564393261653735306132613061,Mega Drive,a:b21,b:b22,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,lefttrigger:b28,rightshoulder:b27,righttrigger:b23,start:b30,x:b24,y:b25,platform:Android, +64363363336633363736393038313464,Mega Drive,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b9,x:b2,y:b3,platform:Android, +33323763323132376537376266393366,Microsoft Dual Strike,a:b24,b:b23,back:b25,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b29,rightshoulder:b78,rightx:a0,righty:a1~,start:b26,x:b22,y:b21,platform:Android, +30306461613834333439303734316539,Microsoft SideWinder Pro,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b20,lefttrigger:b9,rightshoulder:b19,righttrigger:b10,start:b17,x:b2,y:b3,platform:Android, +32386235353630393033393135613831,Microsoft Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4d4f42415041442050726f2d48440000,Mobapad Chitu HD,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4d4f435554452d303533582d4d35312d,Mocute 053X,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +33343361376163623438613466616531,Mocute M053,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +39306635663061636563316166303966,Mocute M053,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +7573622067616d657061642020202020,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Android, +050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,platform:Android, +31316661666466633938376335383661,Nintendo Switch Pro Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,start:b6,x:b3,y:b2,platform:Android, +34323437396534643531326161633738,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,leftstick:b7,lefttrigger:b17,misc1:b5,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +50726f20436f6e74726f6c6c65720000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b2,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b10,rightx:a2,righty:a3,start:b18,y:b3,platform:Android, +36326533353166323965623661303933,NSO N64 Controller,+rightx:b17,+righty:b10,-rightx:b2,-righty:b19,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,righttrigger:b15,start:b18,platform:Android, +4e363420436f6e74726f6c6c65720000,NSO N64 Controller,+rightx:b17,+righty:b10,-rightx:b2,-righty:b19,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,righttrigger:b15,start:b18,platform:Android, +534e455320436f6e74726f6c6c657200,NSO SNES Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android, +64623863346133633561626136366634,NSO SNES Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android, +050000005509000003720000cf7f3f00,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000010720000ffff3f00,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000014720000df7f3f00,NVIDIA Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, +050000005509000014720000df7f3f80,NVIDIA Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,platform:Android, +37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +39383335313438623439373538343266,OUYA Controller,a:b0,b:b2,dpdown:b18,dpleft:b15,dpright:b16,dpup:b17,leftshoulder:b3,leftstick:b9,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,x:b1,y:b19,platform:Android, +4f5559412047616d6520436f6e74726f,OUYA Controller,a:b0,b:b2,dpdown:b18,dpleft:b15,dpright:b6,dpup:b17,leftshoulder:b3,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b19,platform:Android, +506572666f726d616e63652044657369,PDP PS3 Rock Candy Controller,a:b1,b:b17,back:h0.2,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b0,y:b2,platform:Android, +61653962353232366130326530363061,Pokken,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,rightshoulder:b20,righttrigger:b10,start:b18,x:b0,y:b2,platform:Android, +32666633663735353234363064386132,PS2,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a3,righty:a2,start:b30,x:b24,y:b21,platform:Android, +050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +536f6e7920504c415953544154494f4e,PS3 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +61363034663839376638653463633865,PS3 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +66366539656564653432353139356536,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +66383132326164626636313737373037,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000c405000000783f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000c4050000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000cc090000fffe3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +30303839663330346632363232623138,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +31326235383662333266633463653332,PS4 Controller,a:b1,b:b16,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b17,x:b0,y:b2,platform:Android, +31373231336561636235613666323035,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +31663838336334393132303338353963,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +34613139376634626133336530386430,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +37626233336235343937333961353732,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +37626464343430636562316661643863,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38393161636261653636653532386639,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +63313733393535663339656564343962,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +63393662363836383439353064663939,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65366465656364636137653363376531,PS4 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +66613532303965383534396638613230,PS4 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +050000004c050000e60c0000fffe3f00,PS5 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000e60c0000fffe3f80,PS5 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a3,rightx:a4,righty:a5,start:b16,x:b2,y:b17,platform:Android, +050000004c050000e60c0000ffff3f00,PS5 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +32346465346533616263386539323932,PS5 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +32633532643734376632656664383733,PS5 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +37363764353731323963323639666565,PS5 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +61303162353165316365336436343139,PS5 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +64336263393933626535303339616332,Qanba 4RAF,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b20,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b9,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android, +36626666353861663864336130363137,Razer Junglecat,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000f8270000bf0b0000ffff3f00,Razer Kishi,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +62653861643333663663383332396665,Razer Kishi,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +5a6869587520526574726f2042697420,Retro Bit Saturn Controller,a:b21,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b26,rightshoulder:b27,righttrigger:b28,start:b30,x:b23,y:b24,platform:Android, +32417865732031314b6579732047616d,Retro Bit SNES Controller,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android, +36313938306539326233393732613361,Retro Bit SNES Controller,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android, +526574726f466c616720576972656420,Retro Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b17,rightshoulder:b18,start:b10,x:b2,y:b3,platform:Android, +61343739353764363165343237303336,Retro Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b17,lefttrigger:b18,leftx:a0,lefty:a1,start:b10,x:b2,y:b3,platform:Android, +526574726f696420506f636b65742043,Retroid Pocket,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b18,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +582d426f7820436f6e74726f6c6c6572,Retroid Pocket,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b18,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +64633735616665613536653363336132,Retroid Pocket,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b19,paddle2:b20,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38653130373365613538333235303036,Retroid Pocket 2,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +64363363336633363736393038313463,Retrolink,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b6,platform:Android, +37393234373533633333323633646531,RetroUSB N64 RetroPort,+rightx:b17,+righty:b15,-rightx:b18,-righty:b6,a:b10,b:b9,dpdown:b19,dpleft:b1,dpright:b0,dpup:b2,leftshoulder:b7,lefttrigger:b20,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Android, +5365616c6965436f6d707574696e6720,RetroUSB N64 RetroPort,+rightx:b17,+righty:b15,-rightx:b18,-righty:b6,a:b10,b:b9,dpdown:b19,dpleft:b1,dpright:b0,dpup:b2,leftshoulder:b7,lefttrigger:b20,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Android, +526574726f5553422e636f6d20534e45,RetroUSB SNES RetroPort,a:b1,b:b20,back:b19,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b2,x:b0,y:b3,platform:Android, +64643037633038386238303966376137,RetroUSB SNES RetroPort,a:b1,b:b20,back:b19,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b2,x:b0,y:b3,platform:Android, +37656564346533643138636436356230,Rock Candy Switch Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android, +33373336396634316434323337666361,RumblePad 2,a:b22,b:b23,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b24,platform:Android, +36363537303435333566386638366333,Samsung EIGP20,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +53616d73756e672047616d6520506164,Samsung EIGP20,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +66386565396238363534313863353065,Sanwa PlayOnline Mobile,a:b21,b:b22,back:b23,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b24,platform:Android, +32383165316333383766336338373261,Saturn,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:a4,righttrigger:a5,x:b2,y:b3,platform:Android, +38613865396530353338373763623431,Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,lefttrigger:b10,rightshoulder:b20,righttrigger:b19,start:b17,x:b2,y:b3,platform:Android, +61316232336262373631343137633631,Saturn,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:a4,righttrigger:a5,x:b2,y:b3,platform:Android, +30353835333338613130373363646337,SG H510,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android, +66386262366536653765333235343634,SG H510,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +66633132393363353531373465633064,SG H510,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android, +62653761636366393366613135366338,SN30 PP,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +38376662666661636265313264613039,SNES,a:b0,b:b1,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b3,rightshoulder:b20,start:b10,x:b19,y:b2,platform:Android, +5346432f555342205061640000000000,SNES Adapter,a:b0,b:b1,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b3,rightshoulder:b20,start:b10,x:b19,y:b2,platform:Android, +5553422047616d657061642000000000,SNES Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +62653335326261303663356263626339,Sony PlayStation Classic Controller,a:b19,b:b1,back:b17,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,lefttrigger:b3,rightshoulder:b10,righttrigger:b20,start:b18,x:b2,y:b0,platform:Android, +536f6e7920496e746572616374697665,Sony PlayStation Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +576972656c65737320436f6e74726f6c,Sony PlayStation Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +63303964303462366136616266653561,Sony PSP,a:b21,b:b22,back:b27,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b28,x:b23,y:b24,platform:Android, +63376637643462343766333462383235,Sony Vita,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a3,righty:a4,start:b18,x:b0,y:b2,platform:Android, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android, +0500000011010000201400000f7e0f00,SteelSeries Nimbus,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,x:b19,y:b2,platform:Android, +35306436396437373135383665646464,SteelSeries Nimbus Plus,a:b0,b:b1,leftshoulder:b3,leftstick:b17,lefttrigger:b9,leftx:a0,rightshoulder:b20,rightstick:b18,righttrigger:b10,rightx:a2,x:b19,y:b2,platform:Android, +33313930373536613937326534303931,Taito Egret II Mini Control Panel,a:b25,b:b23,back:b27,guide:b30,leftx:a0,lefty:a1,rightshoulder:b21,righttrigger:b22,start:b28,x:b29,y:b24,platform:Android, +54475a20436f6e74726f6c6c65720000,TGZ Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +62363434353532386238336663643836,TGZ Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +37323236633763666465316365313236,THEC64 Joystick,a:b21,b:b22,back:b27,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b27,x:b23,y:b24,platform:Android, +38346162326232346533316164363336,THEGamepad,a:b23,b:b22,back:b27,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b28,x:b24,y:b21,platform:Android, +050000004f0400000ed00000fffe3f00,Thrustmaster eSwap Pro Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +5477696e20555342204a6f7973746963,Twin Joystick,a:b22,b:b21,back:b28,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android, +30623739343039643830333266346439,Valve Steam Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,paddle1:b24,paddle2:b23,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +31643365666432386133346639383937,Valve Steam Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,paddle1:b24,paddle2:b23,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +30386438313564306161393537333663,Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android, +33333034646336346339646538643633,Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android, +050000005e0400008e02000000783f00,Xbox 360 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +30396232393162346330326334636566,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +38313038323730383864666463383533,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +58626f782033363020576972656c6573,Xbox 360 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65353331386662343338643939643636,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65613532386633373963616462363038,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +47656e6572696320582d426f78207061,Xbox Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4d6963726f736f667420582d426f7820,Xbox Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +64633436313965656664373634323364,Xbox Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e04000091020000ff073f00,Xbox One Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +050000005e04000091020000ff073f80,Xbox One Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000e00200000ffe3f00,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android, +050000005e040000e00200000ffe3f80,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,platform:Android, +050000005e040000e0020000ffff3f00,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b4,leftshoulder:b3,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android, +050000005e040000e0020000ffff3f80,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b4,leftshoulder:b3,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b7,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,platform:Android, +050000005e040000fd020000ffff3f00,Xbox One Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +33356661323266333733373865656366,Xbox One Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +34356136633366613530316338376136,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android, +35623965373264386238353433656138,Xbox One Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +36616131643361333337396261666433,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +58626f7820576972656c65737320436f,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65316262316265373335666131623538,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000000b000000783f00,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +050000005e040000000b000000783f80,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000050b0000ffff3f00,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000e002000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000ea02000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000120b000000783f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +050000005e040000120b000000783f80,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65633038363832353634653836396239,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000001727000044310000ffff3f00,XiaoMi Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, + +# iOS +05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS, +05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS, +05000000ac05000004000000a8986d04,8BitDo Micro,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,platform:iOS, +05000000ac05000004000000fd216d04,8BitDo Pro 2,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,platform:iOS, +05000000ac05000004000000209f6d04,8Bitdo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,platform:iOS, +05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,platform:iOS, +050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS, +4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS, +050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,platform:iOS, +050000007e050000062000004f060000,Nintendo Switch Joy-Con (L),+leftx:h0.1,+lefty:h0.2,-leftx:h0.4,-lefty:h0.8,dpdown:b2,dpleft:b0,dpright:b3,dpup:b1,leftshoulder:b4,misc1:b6,rightshoulder:b5,platform:iOS, +050000007e05000008200000df070000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:iOS, +050000007e0500000e200000df070000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:iOS, +050000007e050000072000000f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,platform:iOS, +050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,platform:iOS, +050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:iOS, +050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,platform:iOS, +05000000ac050000040000008e586d04,PlayStation VR2 Sense Controller (L),+leftx:+a3,+lefty:+a1,-leftx:+a2,-lefty:+a4,back:b2,leftstick:b4,lefttrigger:+a7,paddle2:b3,x:b0,y:b1,platform:iOS, +05000000ac050000040000000eb86d04,PlayStation VR2 Sense Controller (R),+rightx:+a3,+righty:+a1,-rightx:+a2,-righty:+a4,a:b0,b:b1,paddle1:b3,rightstick:b4,righttrigger:+a7,start:b2,platform:iOS, +050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000004c050000cc090000df870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff876d01,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000004c050000e60c0000df870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,touchpad:b10,x:b2,y:b3,platform:iOS, +050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS, +05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS, +05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS, +050000005e040000050b0000df070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000005e040000130b0000df870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000005e040000130b0000ff870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, diff --git a/indra/newview/app_settings/key_bindings.xml b/indra/newview/app_settings/key_bindings.xml index 8d5349550f9..826212e8bd8 100644 --- a/indra/newview/app_settings/key_bindings.xml +++ b/indra/newview/app_settings/key_bindings.xml @@ -1,5 +1,5 @@ - + @@ -7,6 +7,7 @@ + @@ -21,9 +22,20 @@ - + + + + + + + + + + + + @@ -34,6 +46,7 @@ + @@ -51,6 +64,20 @@ + + + + + + + + + + + + + + @@ -87,6 +114,11 @@ + + + + + @@ -160,6 +192,11 @@ + + + + + @@ -178,8 +215,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index ad307f96488..528a2339b41 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -93,16 +93,16 @@ AFKTimeout Comment - - Time before automatically setting AFK (away from keyboard) mode (seconds, 0=never). - Valid values are: 0, 120, 300, 600, 1800 - + + Time before automatically setting AFK (away from keyboard) mode (seconds, 0=never). + Valid values are: 0, 120, 300, 600, 1800 + Persist 1 Type S32 Value - 300 + 300 AckCollectTime @@ -294,7 +294,7 @@ AudioLevelMedia Comment - Audio level of Quicktime movies + Audio level of parcel Media Persist 1 Type @@ -401,7 +401,7 @@ Value 0 - AutoAcceptNewInventory + AutoAcceptNewInventory Comment Automatically accept new notecards/textures/landmarks @@ -2623,7 +2623,7 @@ Value 0 - DoubleClickTeleport + DoubleClickTeleport Comment Enable double-click to teleport where allowed (afects minimap and people panel) @@ -2722,6 +2722,28 @@ Value 1 + DebugShowControllerState + + Comment + Display internal controller state values + Persist + 1 + Type + Boolean + Value + 0 + + GameControl + + Comment + Game controller settings: global per-mode action mappings plus per-device hardware config. Empty means use built-in defaults. + Persist + 1 + Type + LLSD + Value + + EnableGestureSounds Comment @@ -9361,7 +9383,7 @@ Value 1.0 - + RenderReflectionProbeDrawDistance Comment @@ -9371,7 +9393,7 @@ Type F32 Value - 64 + 64.0 RenderReflectionProbeAmbiance @@ -9560,7 +9582,7 @@ Value 1.0 - + RenderReflectionProbeMaxLocalLightAmbiance Comment @@ -9946,7 +9968,7 @@ Type U32 Value - 10 + 0 MaxAttachmentComplexity @@ -11117,7 +11139,7 @@ Boolean Value 0 - + NearbyListShowMap Comment @@ -16339,7 +16361,7 @@ Type Boolean Value - 1 + 1 UpdateAppWindowTitleBar diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 2f0ad15d182..30e982329ff 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -84,7 +84,6 @@ #include "llurlentry.h" #include "llviewercontrol.h" #include "llviewerdisplay.h" -#include "llviewerjoystick.h" #include "llviewermediafocus.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" @@ -454,7 +453,7 @@ LLAgent::LLAgent() : mCurrentFidget(0), mFirstLogin(false), mOutfitChosen(false), - + mCrouch(false), mVoiceConnected(false), mMouselookModeInSignal(nullptr), @@ -787,13 +786,17 @@ void LLAgent::moveUp(S32 direction) mLastJumpInputTime = LLTimer::getTotalSeconds(); } setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP); + mCrouch = false; } else if (direction < 0) { setControlFlags(AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP); } - gAgentCamera.resetView(); + if (!mCrouch) + { + gAgentCamera.resetView(); + } } //----------------------------------------------------------------------------- @@ -844,6 +847,11 @@ void LLAgent::movePitch(F32 mag) } } +bool LLAgent::isCrouching() const +{ + return mCrouch && !getFlying(); +} + // Does this parcel allow you to fly? bool LLAgent::canFly() { @@ -911,6 +919,7 @@ void LLAgent::setFlying(bool fly, bool fail_sound) { add(LLStatViewer::FLY, 1); } + mCrouch = false; setControlFlags(AGENT_CONTROL_FLY); } else @@ -1430,7 +1439,7 @@ LLVector3 LLAgent::getReferenceUpVector() return up_vector; } -// Radians, positive is forward into ground +// Radians, positive is downward toward ground //----------------------------------------------------------------------------- // pitch() //----------------------------------------------------------------------------- @@ -1444,27 +1453,23 @@ void LLAgent::pitch(F32 angle) LLVector3 skyward = getReferenceUpVector(); + // SL-19286 Avatar is upside down when viewed from below + // after left-clicking the mouse on the avatar and dragging down + // + // The issue is observed on angle below 10 degrees + const F32 look_down_limit = 179.f * DEG_TO_RAD; + const F32 look_up_limit = 10.f * DEG_TO_RAD; + + F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward); + // clamp pitch to limits - if (angle >= 0.f) + if ((angle >= 0.f) && (angle_from_skyward + angle > look_down_limit)) { - const F32 look_down_limit = 179.f * DEG_TO_RAD; - F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward); - if (angle_from_skyward + angle > look_down_limit) - { - angle = look_down_limit - angle_from_skyward; - } + angle = look_down_limit - angle_from_skyward; } - else if (angle < 0.f) + else if ((angle < 0.f) && (angle_from_skyward + angle < look_up_limit)) { - const F32 look_up_limit = 5.f * DEG_TO_RAD; - const LLVector3& viewer_camera_pos = LLViewerCamera::getInstance()->getOrigin(); - LLVector3 agent_focus_pos = getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal()); - LLVector3 look_dir = agent_focus_pos - viewer_camera_pos; - F32 angle_from_skyward = angle_between(look_dir, skyward); - if (angle_from_skyward + angle < look_up_limit) - { - angle = look_up_limit - angle_from_skyward; - } + angle = look_up_limit - angle_from_skyward; } if (fabs(angle) > 1e-4) @@ -4995,6 +5000,402 @@ void LLAgent::renderAutoPilotTarget() } } +static U64 g_lastUpdateTime { 0 }; +static F32 g_deltaTime { 0.0f }; +static S32 g_lastUpdateFrame { 0 }; +static S32 g_deltaFrame { 0 }; + +void LLAgent::updateGameControlMode() +{ + // Auto-derive the active mode from avatar/camera state. Cursor mode takes + // precedence over Flycam: toggling the mouse cursor on while Flycam is engaged + // reports CONTROL_MODE_CURSOR (so the left stick drives the on-screen cursor) + // while isUsingFlycam() stays true underneath -- LLAgent::updateFlycam() keeps + // driving the camera every frame regardless of the reported mode (it is gated + // on LLAgentCamera's own flycam state directly, not on AgentControlMode), and + // LLGameControl::setFlycamEngaged() below tells the input-mapping lookup to + // let Flycam's own axis/button config (Pan/Tilt/Boom/...) keep driving + // whatever physical inputs Cursor mode's own mouse-cursor actions don't claim. + // This is how a single Cursor mode ends up with two flavors -- "other axes" + // control the avatar/camera depending on whether Flycam is concurrently + // engaged -- without needing a distinct mode of its own. + // Next: mouselook (the camera mode can be entered while sitting too, so it is + // checked ahead of Captive), then sitting/controls-taken maps to Captive, + // otherwise the normal Avatar mode. + LLGameControl::AgentControlMode mode; + if (mUsingMouseCursor) + { + mode = LLGameControl::CONTROL_MODE_CURSOR; + } + else if (isUsingFlycam()) + { + mode = LLGameControl::CONTROL_MODE_FLYCAM; + } + else if (gAgentCamera.cameraMouselook()) + { + mode = LLGameControl::CONTROL_MODE_MOUSELOOK; + } + else if (isSitting()) + { + mode = LLGameControl::CONTROL_MODE_CAPTIVE; + } + else + { + mode = LLGameControl::CONTROL_MODE_AVATAR; + } + LLGameControl::setFlycamEngaged(isUsingFlycam()); + LLGameControl::setAgentControlMode(mode); +} + +void LLAgent::applyExternalActions(const LLGameControl::AgentActions& actions) +{ + llassert(LLCoros::on_main_thread_main_coro()); + // Valid whenever game control is driving avatar or flycam: in FlyCam mode this + // only services the flycam on/off toggle (movement bits are ignored below). + assert(LLGameControl::isEnabled() + && (LLGameControl::willControlAvatar() || LLGameControl::willControlFlycam())); + + // Process the non-flag actions first (see AgentActions comment in + // llgamecontrol.h). Each bit in mMiscActionBits is already edge-triggered + // by LLGameControllerManager::computeAgentActions() (set only on the + // frame the bound button transitions from not-pressed to pressed), so no + // further debouncing is needed here -- just react to the bit directly. + const U32 misc_actions = actions.mMiscActionBits; + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_FLY) + { + setFlying(!getFlying()); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_FLYCAM) + { + toggleFlycam(); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_SIT) + { + if (isSitting()) + { + standUp(); + } + else + { + sitDown(); + } + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_SPEAK) + { + LLVoiceClient::getInstance()->toggleUserPTTState(); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_MOUSELOOK) + { + // Mirrors LLViewMouselook's "View.Mouselook" toggle. + if (!gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToMouselook(); + } + else + { + gAgentCamera.changeCameraToDefault(); + } + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_TOGGLE_MOUSE_CURSOR) + { + toggleMouseCursorMode(); + } + + if (misc_actions & LLGameControl::AVATAR_ACTION_ESCAPE) + { + resetToAvatarMode(); + } + + // CONTROL_MODE_CURSOR: drive the actual on-screen cursor from + // actions.mMouseCursorDX/DY (the analog deflection of whatever's bound to "Mouse + // left/right"/"Mouse up/down") BEFORE the click dispatch below, so a synthesized + // click lands wherever the stick just moved the cursor to, in the same frame. + if (LLGameControl::getAgentControlMode() == LLGameControl::CONTROL_MODE_CURSOR) + { + constexpr F32 MOUSE_CURSOR_PIXELS_PER_SEC = 1200.f; + U64 mouse_cursor_now = LLFrameTimer::getTotalTime(); + F32 mouse_cursor_dt = (mLastMouseCursorUpdate == 0) + ? 0.f : F32(mouse_cursor_now - mLastMouseCursorUpdate) / (F32)(USEC_PER_SEC); + mLastMouseCursorUpdate = mouse_cursor_now; + + if (actions.mMouseCursorDX != 0.f || actions.mMouseCursorDY != 0.f) + { + LLCoordGL cursor_pos = gViewerWindow->getCurrentMouse(); + S32 new_x = cursor_pos.mX + ll_round(actions.mMouseCursorDX * MOUSE_CURSOR_PIXELS_PER_SEC * mouse_cursor_dt); + S32 new_y = cursor_pos.mY + ll_round(actions.mMouseCursorDY * MOUSE_CURSOR_PIXELS_PER_SEC * mouse_cursor_dt); + gViewerWindow->moveCursorTo(new_x, new_y); + } + } + else + { + mLastMouseCursorUpdate = 0; + } + + // Simulated mouse buttons (see llgamecontrol.h's AvatarMouseButton) are + // level-triggered: actions.mMouseButtonBits reflects which buttons are + // currently held, so the press/release edges are found here by diffing + // against last frame's value. Each edge is dispatched through + // LLViewerWindow exactly like a real mouse click, at the current cursor + // position, so it drives hover/pick/pie-menu/tool-manager the same way a + // hardware click would (e.g. touching/grabbing objects, walk-to-clicked, + // pie menu on right click). + U32 mouse_pressed_edges = actions.mMouseButtonBits & ~mPrevMouseButtonBits; + U32 mouse_released_edges = ~actions.mMouseButtonBits & mPrevMouseButtonBits; + mPrevMouseButtonBits = actions.mMouseButtonBits; + + if (mouse_pressed_edges & LLGameControl::AVATAR_MOUSE_BUTTON_LEFT) + { + gViewerWindow->handleMouseDown(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + if (mouse_released_edges & LLGameControl::AVATAR_MOUSE_BUTTON_LEFT) + { + gViewerWindow->handleMouseUp(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + if (mouse_pressed_edges & LLGameControl::AVATAR_MOUSE_BUTTON_RIGHT) + { + gViewerWindow->handleRightMouseDown(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + if (mouse_released_edges & LLGameControl::AVATAR_MOUSE_BUTTON_RIGHT) + { + gViewerWindow->handleRightMouseUp(gViewerWindow->getWindow(), gViewerWindow->getCurrentMouse(), MASK_NONE); + } + + // actions.mIsRunning is derived purely from how hard the controller's own + // movement axes are pushed (see computeAgentActions()'s analog hysteresis). + // Only let it drive the agent's running state while the controller is + // actually supplying movement this frame -- otherwise an idle-but-connected + // controller would fight the keyboard (e.g. clobbering double-tap-to-run) + // every frame even though the player is moving with the keyboard instead. + constexpr U32 CONTROLLER_MOVEMENT_FLAGS = AGENT_CONTROL_AT_POS | AGENT_CONTROL_AT_NEG + | AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_LEFT_NEG; + if ((actions.mControlFlags & CONTROLLER_MOVEMENT_FLAGS) + && actions.mIsRunning != getRunning()) + { + if (actions.mIsRunning) + { + setRunning(); + } + else + { + clearRunning(); + } + sendWalkRun(actions.mIsRunning); + } + + mExternalActionFlags = actions.mControlFlags; + + // measure delta time and frame + // Note: it is possible for the deltas to be very large + // and it is the duty of the code that uses them to clamp as necessary + U64 now = LLFrameTimer::getTotalTime(); + g_deltaTime = F32(now - g_lastUpdateTime) / (F32)(USEC_PER_SEC); + g_lastUpdateTime = now; + + S32 frame_count = LLFrameTimer::getFrameCount(); + g_deltaFrame = frame_count - g_lastUpdateFrame; + g_lastUpdateFrame = frame_count; + + if (isUsingFlycam()) + { + // Flycam will be updated later by exernal context + return; + } + + S32 direction = (S32)(mExternalActionFlags & AGENT_CONTROL_AT_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_AT_NEG) >> 1); + if (direction != 0) + { + moveAt(direction); + } + + static U32 last_non_fly_frame = 0; + static U64 last_non_fly_time = 0; + direction = (S32)(mExternalActionFlags & AGENT_CONTROL_UP_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_UP_NEG) >> 1); + if (direction != 0) + { + // HACK: this auto-fly logic based on original code still extant in llviewerinput.cpp::agent_jump() + // but has been cleaned up. + // TODO?: DRY this logic + if (direction > 0) + { + if (!getFlying() + && !upGrabbed() + && gSavedSettings.getBOOL("AutomaticFly")) + { + constexpr F32 FLY_TIME = 0.5f; + constexpr U32 FLY_FRAMES = 4; + F32 delta_time = (F32)(now - last_non_fly_time) / (F32)(USEC_PER_SEC); + U32 delta_frames = frame_count - last_non_fly_frame; + if( delta_time > FLY_TIME + && delta_frames > FLY_FRAMES) + { + setFlying(TRUE); + } + } + } + else + { + last_non_fly_frame = frame_count; + last_non_fly_time = now; + } + + moveUp(direction); + } + else if (!getFlying()) + { + last_non_fly_frame = frame_count; + last_non_fly_time = now; + } + + direction = (S32)(mExternalActionFlags & AGENT_CONTROL_LEFT_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_LEFT_NEG) >> 1); + if (direction != 0) + { + moveLeft(direction); + } + + direction = (S32)(mExternalActionFlags & AGENT_CONTROL_YAW_POS) + - (S32)((mExternalActionFlags & AGENT_CONTROL_YAW_NEG) >> 1); + if (direction != 0) + { + F32 sign = (direction < 0 ? -1.0f : 1.0f); + // actions.mYawAmplitude is the analog stick deflection ([-1, 1]) that + // set this flag; use its magnitude to modulate turn speed instead of + // always snapping to full rate + moveYaw(sign * fabs(actions.mYawAmplitude)); + } + + { + F32 pitch_sign = ((mExternalActionFlags & AGENT_CONTROL_PITCH_POS) > 0 ? 1.0f : 0.0f) + - ((mExternalActionFlags & AGENT_CONTROL_PITCH_NEG) > 0 ? 1.0f : 0.0f); + // Same analog modulation as yaw above, via actions.mPitchAmplitude. + movePitch(pitch_sign * fabs(actions.mPitchAmplitude)); + } + + // actions.mZoomAmplitude ([-1, 1], from "Zoom +/-"/"Zoom +"/"Zoom -") drives the + // camera's field-of-view zoom. Positive amplitude zooms in (narrows the FOV); + if (actions.mZoomAmplitude != 0.f) + { + constexpr F32 FOV_ZOOM_RATE = 1.2f; + F32 fov_scale = powf(FOV_ZOOM_RATE, -actions.mZoomAmplitude * g_deltaTime); + LLViewerCamera* camera = LLViewerCamera::getInstance(); + camera->setDefaultFOV(camera->getDefaultFOV() * fov_scale); + gSavedSettings.setF32("CameraAngle", camera->getView()); + } + + if (mExternalActionFlags & AGENT_CONTROL_STOP) + { + setControlFlags(AGENT_CONTROL_STOP); + } + + // HACK: AGENT_CONTROL_NUDGE_AT_POS is used to toggle running + if ((mExternalActionFlags & AGENT_CONTROL_NUDGE_AT_POS) > 0) + { + if (mToggleRun) + { + if (getRunning()) + { + clearRunning(); + sendWalkRun(false); + } + else + { + setRunning(); + sendWalkRun(true); + } + } + mToggleRun = false; + } + else + { + mToggleRun = true; + } + + if ((mExternalActionFlags & AGENT_CONTROL_SIT_ON_GROUND) > 0 + && isAgentAvatarValid() && !isSitting() && !getFlying() && !gAgentAvatarp->mInAir) + { + // "Sit": only sit if currently standing; a no-op otherwise + // (already sitting, flying, or airborne). + sitDown(); + } + else if ((mExternalActionFlags & AGENT_CONTROL_STAND_UP) > 0 && isSitting()) + { + // "Stand up": only stand if currently sitting. + standUp(); + } +} + +bool LLAgent::isUsingFlycam() const +{ + return gAgentCamera.isUsingFlycam(); +} + +void LLAgent::toggleFlycam() +{ + gAgentCamera.toggleFlycam(); +} + +void LLAgent::setFlycamKeyInput(U8 channel, F32 value) +{ + gAgentCamera.setFlycamKeyInput(channel, value); +} + +void LLAgent::setFlycamKeyUnroll(bool unroll) +{ + gAgentCamera.setFlycamKeyUnroll(unroll); +} + +bool LLAgent::isCameraExternallyDriven() const +{ + // Both flycam input sources now share one toggle state on LLAgentCamera -- + // LLViewerJoystick::getOverrideCamera() forwards to the same flag. + return gAgentCamera.isUsingFlycam(); +} + +void LLAgent::toggleMouseCursorMode() +{ + mUsingMouseCursor = !mUsingMouseCursor; + mLastMouseCursorUpdate = 0; // avoid a jump from a stale delta-time baseline +} + +void LLAgent::resetToAvatarMode() +{ + if (mUsingMouseCursor) + { + toggleMouseCursorMode(); + } + if (isUsingFlycam()) + { + toggleFlycam(); + } + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } +} + +void LLAgent::pressGameControlButton(U8 button_index) +{ + mGameControlButtonsFromKeys |= (1U << button_index); +} + +void LLAgent::releaseGameControlButton(U8 button_index) +{ + mGameControlButtonsFromKeys &= ~(1U << button_index); +} + +void LLAgent::updateFlycam() +{ + gAgentCamera.updateFlycam(g_deltaTime); +} + /********************************************************************************/ //----------------------------------------------------------------------------- diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index e6d9623957f..c21f197b781 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -33,6 +33,8 @@ #include "llcharacter.h" #include "llcoordframe.h" // for mFrameAgent #include "llavatarappearancedefines.h" +#include "llgamecontrol.h" +#include "llkeyboard.h" #include "llpermissionsflags.h" #include "llevents.h" #include "v3dmath.h" @@ -348,6 +350,15 @@ class LLAgent : public LLOldEvents::LLObservable F32 mNextFidgetTime; S32 mCurrentFidget; + //-------------------------------------------------------------------- + // Crouch + //-------------------------------------------------------------------- +public: + bool isCrouching() const; + void toggleCrouch() { mCrouch = !mCrouch; } +private: + bool mCrouch; + //-------------------------------------------------------------------- // Fly //-------------------------------------------------------------------- @@ -479,6 +490,25 @@ class LLAgent : public LLOldEvents::LLObservable void resetControlFlags(); bool anyControlGrabbed() const; // True iff a script has taken over a control bool isControlGrabbed(S32 control_index) const; + // Flycam's state now lives on LLAgentCamera (shared with LLViewerJoystick, + // which also drives it); these forward there so existing callers don't + // need to change. + bool isUsingFlycam() const; + void toggleFlycam(); + // True while any flycam-like system (this one, or the legacy NDOF + // joystick's own) is driving LLViewerCamera directly, for code that + // needs to know the camera isn't under its usual control (e.g. tool + // selection, teleport look-at, camera-follow rendering). + bool isCameraExternallyDriven() const; + bool isUsingMouseCursor() const { return mUsingMouseCursor; } + void toggleMouseCursorMode(); + + // Drops straight back to plain Avatar mode, unwinding whichever of + // Mouselook/FlyCam/Cursor mode is currently active (any combination -- + // see updateGameControlMode()). Bound to the game-control "ESC" action + // in those modes (see AVATAR_ACTION_ESCAPE/FLYCAM_ACTION_ESCAPE). + void resetToAvatarMode(); + // Send message to simulator to force grabbed controls to be // released, in case of a poorly written script. void forceReleaseControls(); @@ -486,9 +516,57 @@ class LLAgent : public LLOldEvents::LLObservable private: S32 mControlsTakenCount[TOTAL_CONTROLS]; S32 mControlsTakenPassedOnCount[TOTAL_CONTROLS]; - U32 mControlFlags; // Replacement for the mFooKey's + // mControlFlags is a bitmask of behavior instructions for compact + // transmission to the server. It does NOT represent "input", rather + // the consequences of it, which will sometimes depend on "state". + U32 mControlFlags; F64 mLastJumpInputTime; // Time of last jump input (key-down) in seconds from LLTimer::getTotalSeconds() + //-------------------------------------------------------------------- + // GameControls + //-------------------------------------------------------------------- +public: + // ActionFlags are similar to, but not the same as, ControlFlags! + // An 'ActionFlags' bitmask stores 'simplified input' from key/button + // presses that correspond to avatar/camera movement actions + // whereas 'mControlFlags' are a more complicated set of behavior bits + // computed as a function of input and state, and are sent to the server + // to steer its character controller for the avatar. + // + void applyExternalActions(const LLGameControl::AgentActions& actions); + void updateFlycam(); + + // Auto-derive the active game-control AgentControlMode from current avatar + // state (flycam active -> FlyCam, else mouse-cursor mode active -> Mouse, else + // mouselook camera -> Mouselook, else sitting -> Captive, else Avatar) and push + // it to LLGameControl. Called once per frame before game-control input is + // processed so the runtime mapping/gating tracks avatar state automatically. + void updateGameControlMode(); + + void pressGameControlButton(U8 button); + void releaseGameControlButton(U8 button); + U32 getGameControlButtonsFromKeys() const { return mGameControlButtonsFromKeys; } + + void setFlycamKeyInput(U8 channel, F32 value); + void setFlycamKeyUnroll(bool unroll); + +private: + + U32 mExternalActionFlags { 0 }; + U32 mGameControlButtonsFromKeys { 0 }; + // Previous frame's LLGameControl::AgentActions::mMouseButtonBits, used by + // applyExternalActions() to find the press/release edges of the + // level-triggered simulated mouse buttons. + U32 mPrevMouseButtonBits { 0 }; + bool mToggleRun { true }; + + // CONTROL_MODE_CURSOR: toggled by AVATAR_ACTION_TOGGLE_MOUSE_CURSOR, same pattern + // as LLAgentCamera's flycam toggle. mLastMouseCursorUpdate is the previous frame's + // timestamp used to compute the cursor's per-frame movement delta; reset to 0 + // whenever the mode isn't active so re-entering doesn't jump the cursor. + bool mUsingMouseCursor { false }; + U64 mLastMouseCursorUpdate { 0 }; + //-------------------------------------------------------------------- // Animations //-------------------------------------------------------------------- diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index d884b32ac04..765029b844b 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -181,7 +181,9 @@ LLAgentCamera::LLAgentCamera() : mPanLeftKey(0.f), mPanRightKey(0.f), mPanInKey(0.f), - mPanOutKey(0.f) + mPanOutKey(0.f), + mRollLeftKey(0.f), + mRollRightKey(0.f) { mFollowCam.setMaxCameraDistantFromSubject( MAX_CAMERA_DISTANCE_FROM_AGENT ); @@ -191,6 +193,7 @@ LLAgentCamera::LLAgentCamera() : resetPanDiff(); resetOrbitDiff(); + resetCameraRoll(); } // Requires gSavedSettings to be initialized. @@ -327,7 +330,8 @@ void LLAgentCamera::resetView(bool reset_camera, bool change_camera) //Camera Tool is needed for Free Camera Control Mode if (!LLFloaterCamera::inFreeCameraMode()) { - LLFloaterReg::hideInstance("build"); + if (LLFloaterReg::instanceVisible("build")) + LLFloaterReg::hideInstance("build"); // Switch back to basic toolset LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); @@ -351,6 +355,8 @@ void LLAgentCamera::resetView(bool reset_camera, bool change_camera) setFocusOnAvatar(true, ANIMATE); mCameraFOVZoomFactor = 0.f; + + resetCameraRoll(); } resetPanDiff(); resetOrbitDiff(); @@ -901,6 +907,19 @@ void LLAgentCamera::cameraOrbitOver(const F32 angle) } } +//----------------------------------------------------------------------------- +// cameraRollOver() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraRollOver(const F32 angle) +{ + mRollAngle += fmod(angle, F_TWO_PI); +} + +void LLAgentCamera::resetCameraRoll() +{ + mRollAngle = 0.f; +} + void LLAgentCamera::resetCameraOrbit() { LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); @@ -914,6 +933,7 @@ void LLAgentCamera::resetCameraOrbit() cameraZoomIn(1.f); resetOrbitDiff(); + resetCameraRoll(); } void LLAgentCamera::resetOrbitDiff() @@ -1271,6 +1291,7 @@ void LLAgentCamera::updateCamera() const F32 ORBIT_OVER_RATE = 90.f * DEG_TO_RAD; // radians per second const F32 ORBIT_AROUND_RATE = 90.f * DEG_TO_RAD; // radians per second const F32 PAN_RATE = 5.f; // meters per second + const F32 ROLL_RATE = 45.f * DEG_TO_RAD; // radians per second if (gAgentCamera.getOrbitUpKey() || gAgentCamera.getOrbitDownKey()) { @@ -1312,6 +1333,12 @@ void LLAgentCamera::updateCamera() cameraPanUp(input_rate * PAN_RATE / gFPSClamped ); } + if (getRollLeftKey() || getRollRightKey()) + { + F32 input_rate = getRollRightKey() - getRollLeftKey(); + cameraRollOver(input_rate * ROLL_RATE / gFPSClamped); + } + // Clear camera keyboard keys. gAgentCamera.clearOrbitKeys(); gAgentCamera.clearPanKeys(); @@ -1511,6 +1538,7 @@ void LLAgentCamera::updateCamera() LLVector3 chest_scale = chest_joint->getScale(); // shorten avatar skeleton to avoid foot interpenetration +#if 0 // This screws up mouselook attachments if (!gAgentAvatarp->mInAir) { LLVector3 chest_offset = LLVector3(0.f, 0.f, chest_joint->getPosition().mV[VZ]) * torso_joint->getWorldRotation(); @@ -1524,9 +1552,37 @@ void LLAgentCamera::updateCamera() chest_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); diff.mV[VZ] = 0.f; } +#endif + static LLCachedControl useRealisticMouselook(gSavedSettings, "AlchemyRealisticMouselook", false); + if (useRealisticMouselook) + { + auto frame_agent = gAgent.getFrameAgent(); + LLQuaternion agent_rot(frame_agent.getQuaternion()); + auto avatarp_parent = (LLViewerObject*)gAgentAvatarp->getParent(); + if(avatarp_parent) + { + auto avatarp_root = (LLViewerObject*)gAgentAvatarp->getRoot(); + if(avatarp_root) + { + auto decoupled = avatarp_root->flagCameraDecoupled(); + if(decoupled) + { + agent_rot *= avatarp_parent->getRenderRotation(); + } - // SL-315 - gAgentAvatarp->mPelvisp->setPosition(gAgentAvatarp->mPelvisp->getPosition() + diff); + } + } + + LLViewerCamera::getInstance()->updateCameraLocation(head_pos, + mCameraUpVector, + gAgentAvatarp->mHeadp->getWorldPosition() + + LLVector3(1.0, 0.0, 0.0) * agent_rot); + } + else + { + // SL-315 + gAgentAvatarp->mPelvisp->setPosition(gAgentAvatarp->mPelvisp->getPosition() + diff); + } gAgentAvatarp->mRoot->updateWorldMatrixChildren(); @@ -1553,6 +1609,18 @@ void LLAgentCamera::updateCamera() torso_joint->setScale(torso_scale); chest_joint->setScale(chest_scale); } + + // We have do this at the very end to make sure it takes all previous calculations into + // account and then applies our roll on top of it, besides it wouldn't even work otherwise. + LLQuaternion rot_quat = LLViewerCamera::instance().getQuaternion(); + LLMatrix3 rot_mat(mRollAngle, 0.f, 0.f); + rot_quat = LLQuaternion(rot_mat)*rot_quat; + + LLMatrix3 mat(rot_quat); + + LLViewerCamera::instance().mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::instance().mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::instance().mZAxis = LLVector3(mat.mMatrix[2]); } void LLAgentCamera::updateLastCamera() @@ -1681,7 +1749,6 @@ LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() LLVector3d LLAgentCamera::calcThirdPersonFocusOffset() { // ...offset from avatar - LLVector3d focus_offset; LLQuaternion agent_rot = gAgent.getFrameAgent().getQuaternion(); if (isAgentAvatarValid() && gAgentAvatarp->getParent()) { @@ -2108,6 +2175,22 @@ void LLAgentCamera::handleScrollWheel(S32 clicks) } else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON)) { + MASK mask = gKeyboard->currentMask(true); + if (mask & MASK_SHIFT) + { + LLVector3d offset = gSavedSettings.getVector3d("FocusOffsetRearView"); + offset.mdV[VZ] += 0.1f * (F32)clicks; + gSavedSettings.setVector3d("FocusOffsetRearView", offset); + return; + } + else if (mask & MASK_CONTROL) + { + LLVector3 offset = gSavedSettings.getVector3("CameraOffsetRearView"); + offset.mV[VZ] += 0.1f * (F32)clicks; + gSavedSettings.setVector3("CameraOffsetRearView", offset); + return; + } + F32 camera_offset_initial_mag = getCameraOffsetInitial().magVec(); F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale")); @@ -2165,7 +2248,7 @@ void LLAgentCamera::resetCamera() void LLAgentCamera::changeCameraToMouselook(bool animate) { if (!gSavedSettings.getBOOL("EnableMouselook") - || LLViewerJoystick::getInstance()->getOverrideCamera()) + || gAgent.isCameraExternallyDriven()) { return; } @@ -2199,7 +2282,6 @@ void LLAgentCamera::changeCameraToMouselook(bool animate) updateLastCamera(); mCameraMode = CAMERA_MODE_MOUSELOOK; - const U32 old_flags = gAgent.getControlFlags(); gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK); if (animate) @@ -2220,7 +2302,7 @@ void LLAgentCamera::changeCameraToMouselook(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToDefault() { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return; } @@ -2246,7 +2328,7 @@ void LLAgentCamera::changeCameraToDefault() //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToFollow(bool animate) { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return; } @@ -2301,7 +2383,7 @@ void LLAgentCamera::changeCameraToFollow(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToThirdPerson(bool animate) { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return; } @@ -2371,7 +2453,7 @@ void LLAgentCamera::changeCameraToThirdPerson(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToCustomizeAvatar() { - if (LLViewerJoystick::getInstance()->getOverrideCamera() || !isAgentAvatarValid()) + if (gAgent.isCameraExternallyDriven() || !isAgentAvatarValid()) { return; } @@ -2452,6 +2534,7 @@ void LLAgentCamera::switchCameraPreset(ECameraPreset preset) resetPanDiff(); resetOrbitDiff(); + resetCameraRoll(); gSavedSettings.setU32("CameraPresetType", mCameraPreset); } @@ -2656,9 +2739,9 @@ void LLAgentCamera::setCameraPosAndFocusGlobal(const LLVector3d& camera_pos, con if (mCameraAnimating) { - const F64 ANIM_METERS_PER_SECOND = 10.0; + const F64 ANIM_METERS_PER_SECOND = 25.0; const F64 MIN_ANIM_SECONDS = 0.5; - const F64 MAX_ANIM_SECONDS = 10.0; + const F64 MAX_ANIM_SECONDS = 1.0; F64 anim_duration = llmax( MIN_ANIM_SECONDS, sqrt(focus_delta_squared) / ANIM_METERS_PER_SECOND ); anim_duration = llmin( anim_duration, MAX_ANIM_SECONDS ); setAnimationDuration( (F32)anim_duration ); @@ -2864,6 +2947,223 @@ bool LLAgentCamera::isfollowCamLocked() return mFollowCam.getPositionLocked(); } +//----------------------------------------------------------------------------- +// resetFlycamToCurrentView() +//----------------------------------------------------------------------------- +void LLAgentCamera::resetFlycamToCurrentView() +{ + LLViewerCamera* camera = LLViewerCamera::getInstance(); + mFlycam.setTransform(gAgent.getPosGlobalFromAgent(camera->getOrigin()), camera->getQuaternion()); + mFlycam.setView(camera->getView()); +} + +//----------------------------------------------------------------------------- +// toggleFlycam() +//----------------------------------------------------------------------------- +void LLAgentCamera::toggleFlycam() +{ + mUsingFlycam = !mUsingFlycam; + if (mUsingFlycam) + { + resetFlycamToCurrentView(); + } +} + +//----------------------------------------------------------------------------- +// applyNdofFlycamFrameDelta() +//----------------------------------------------------------------------------- +void LLAgentCamera::applyNdofFlycamFrameDelta(const F32 local_delta[7], + bool auto_level, F32 auto_level_fraction, + bool direct_view, F32 direct_view_value) +{ + mFlycam.applyFrameDelta(local_delta, auto_level, auto_level_fraction, direct_view, direct_view_value); + + LLVector3d pos_global; + LLQuaternion rot; + mFlycam.getTransform(pos_global, rot); + LLVector3 pos = gAgent.getPosAgentFromGlobal(pos_global); + LLMatrix3 mat(rot); + LLViewerCamera::getInstance()->setOrigin(pos); + LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + + LLViewerCamera::getInstance()->setView(mFlycam.getView()); +} + +//----------------------------------------------------------------------------- +// updateFlycam() +//----------------------------------------------------------------------------- +void LLAgentCamera::updateFlycam(F32 delta_time) +{ + // Note: flycam_inputs arrive in range [-1,1] + std::vector flycam_inputs; + U32 flycam_misc_actions = 0; + U32 flycam_modifiers = 0; + LLGameControl::getFlycamInputs(flycam_inputs, flycam_misc_actions, flycam_modifiers); + + // The channel order is defined by LLGameControl::FlycamChannel. + + // Defensive: getFlycamInputs() should return one value per channel. + if ((S32)flycam_inputs.size() < LLGameControl::FLYCAM_NUM_CHANNELS) + { + return; + } + + if (flycam_misc_actions & LLGameControl::FLYCAM_ACTION_ESCAPE) + { + // Bail out of FlyCam mode immediately; no point applying this frame's + // (about to be abandoned) flycam deltas below. + gAgent.resetToAvatarMode(); + return; + } + + // Blend in keyboard-driven flycam input (independent of LLGameControl, + // which only sees a physical controller). mFlycamKeyboardInput is + // level-triggered by flycam_axis_key<> in llviewerinput.cpp -- it must be + // re-set every frame a key is held, so clear it here once consumed. + for (U8 i = 0; i < LLGameControl::FLYCAM_NUM_CHANNELS; ++i) + { + flycam_inputs[i] = llclamp(flycam_inputs[i] + mFlycamKeyboardInput[i], -1.f, 1.f); + } + mFlycamKeyboardInput.fill(0.f); + bool flycam_key_unroll_requested = mFlycamKeyboardUnrollRequested; + mFlycamKeyboardUnrollRequested = false; + + if ((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_UNROLL) || flycam_key_unroll_requested) + { + // Re-orient the flycam in place: keep its current position and + // forward (local X) axis unchanged, but unroll it so its local vertical + // axis lies on the plane defined by forward axis and world-up, with positive + // dot-product with world-up. + // mFlycam.startReset() smoothly lerps into this transform (see + // LLFlycam::integrate()); flycam input has no effect until the lerp + // completes. + constexpr F32 FLYCAM_UNROLL_DURATION = 1.0f; // seconds; may be tuned later + + LLVector3d current_position; + LLQuaternion current_rotation; + mFlycam.getTransform(current_position, current_rotation); + + LLMatrix3 level(current_rotation); + LLVector3 forward(level.getFwdRow()); + forward.normVec(); + + LLVector3 world_up = LLVector3::z_axis; + LLVector3 new_up = world_up - (world_up * forward) * forward; + constexpr F32 MIN_UNROLL_SIN = 0.01f; // ~0.6 degrees off vertical + if (new_up.lengthSquared() > MIN_UNROLL_SIN * MIN_UNROLL_SIN) + { + new_up.normVec(); + // Right-handed (forward, left, up) basis, per + // LLMatrix3::orthogonalize()'s up = forward % left: up % forward == left. + LLVector3 new_left = new_up % forward; + level.setRows(forward, new_left, new_up); + level.orthogonalize(); // numerical cleanup only; rows are already orthonormal by construction + + mFlycam.startReset(current_position, LLQuaternion(level), FLYCAM_UNROLL_DURATION); + } + } + + // Orbit modifier: while held, Truck stops meaning strafe and instead adds + // into Pan's yaw (both sweep the camera left/right around the focal + // point), and Dolly stops meaning advance and instead adjusts the radius + // (see LLFlycam::setOrbitRadialRate()/integrate()). Tilt, Roll, Boom, + // and Zoom keep their normal meaning either way: Tilt already rotates the + // camera about its own local left axis, which is exactly what swings it + // up/down around the focal point once position is derived from + // orientation; Roll still passes straight through (it just changes which + // region-frame axis Tilt's local axis happens to be next frame). + bool flycam_orbit_held = (flycam_modifiers & LLGameControl::FLYCAM_MODIFIER_ORBIT) != 0; + constexpr F32 FLYCAM_ORBIT_DEFAULT_FOCAL_DISTANCE = 20.0f; // meters; hard-coded for now, may be tuned later + constexpr F32 FLYCAM_ORBIT_MAX_TRACE_DISTANCE = 512.0f; // meters; matches the general world-raycast convention used elsewhere (e.g. LLViewerWindow::cursorIntersect) + F32 flycam_orbit_focal_distance = FLYCAM_ORBIT_DEFAULT_FOCAL_DISTANCE; + if (flycam_orbit_held && !mFlycam.isOrbitEngaged()) + { + // Rising edge: ray-trace forward from the flycam to see what it's + // pointing at, so Orbit pivots around that instead of an arbitrary + // fixed distance whenever there's something in view; falls back to + // the default distance if nothing is hit within range. + LLVector3d camera_pos_global; + LLQuaternion camera_rot; + mFlycam.getTransform(camera_pos_global, camera_rot); + LLVector3 camera_pos = gAgent.getPosAgentFromGlobal(camera_pos_global); + LLVector3 forward(LLMatrix3(camera_rot).getFwdRow()); + LLVector3 trace_end = camera_pos + forward * FLYCAM_ORBIT_MAX_TRACE_DISTANCE; + + LLVector4a start, end; + start.load3(camera_pos.mV); + end.load3(trace_end.mV); + + LLVector4a intersection; + S32 face_hit = -1; + LLViewerObject* hit_object = gPipeline.lineSegmentIntersectInWorld( + start, end, + /*pick_transparent=*/ false, + /*pick_rigged=*/ true, + /*pick_unselectable=*/ true, + /*pick_reflection_probe=*/ false, + &face_hit, nullptr, nullptr, + &intersection); + if (hit_object) + { + LLVector3 hit_pos; + hit_pos.set(intersection.getF32ptr()); + flycam_orbit_focal_distance = (hit_pos - camera_pos).length(); + } + } + mFlycam.setOrbitEngaged(flycam_orbit_held, flycam_orbit_focal_distance); + + F32 yaw_input = flycam_inputs[LLGameControl::FLYCAM_PAN]; + F32 truck_input = flycam_inputs[LLGameControl::FLYCAM_TRUCK]; + F32 dolly_input = flycam_inputs[LLGameControl::FLYCAM_DOLLY]; + if (flycam_orbit_held) + { + // Truck/Dolly are negated here (Pan/Tilt/Roll/Boom/Zoom are not) -- + // determined empirically to match the expected orbit sweep/radial + // directions. + truck_input = -truck_input; + dolly_input = -dolly_input; + yaw_input = llclamp(yaw_input + truck_input, -1.f, 1.f); + truck_input = 0.f; + mFlycam.setOrbitRadialRate(dolly_input); + dolly_input = 0.f; + } + else + { + mFlycam.setOrbitRadialRate(0.f); + } + + // Must precede setLinearVelocity()/setPitchRate()/setYawRate()/setRollRate(): + // those setters apply mSpeedFactor/mAllowRoll immediately rather than at + // integrate() time. + mFlycam.setSpeedFactor(LLGameControl::getFlycamSpeedFactor()); + mFlycam.setAllowRoll(LLGameControl::isFlycamRollAllowed()); + + LLVector3 linear_velocity(dolly_input, truck_input, flycam_inputs[LLGameControl::FLYCAM_BOOM]); + constexpr F32 MAX_FLYCAM_SPEED = 10.0f; + mFlycam.setLinearVelocity(MAX_FLYCAM_SPEED * linear_velocity); + + mFlycam.setPitchRate(flycam_inputs[LLGameControl::FLYCAM_TILT]); + mFlycam.setYawRate(yaw_input); + mFlycam.setRollRate(flycam_inputs[LLGameControl::FLYCAM_ROLL]); + mFlycam.setZoomRate(flycam_inputs[LLGameControl::FLYCAM_ZOOM]); + + mFlycam.integrate(delta_time); + + LLVector3d pos_global; + LLQuaternion rot; + mFlycam.getTransform(pos_global, rot); + LLVector3 pos = gAgent.getPosAgentFromGlobal(pos_global); + LLMatrix3 mat(rot); + LLViewerCamera::getInstance()->setOrigin(pos); + LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + + LLViewerCamera::getInstance()->setView(mFlycam.getView()); +} + bool LLAgentCamera::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position) { // disallow pointing at attachments and avatars @@ -2926,6 +3226,8 @@ void LLAgentCamera::clearOrbitKeys() mOrbitDownKey = 0.f; mOrbitInKey = 0.f; mOrbitOutKey = 0.f; + mRollLeftKey = 0.f; + mRollRightKey = 0.f; } void LLAgentCamera::clearPanKeys() diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h index d277fd6158d..b3ece5abace 100644 --- a/indra/newview/llagentcamera.h +++ b/indra/newview/llagentcamera.h @@ -27,7 +27,11 @@ #ifndef LL_LLAGENTCAMERA_H #define LL_LLAGENTCAMERA_H +#include + +#include "llflycam.h" #include "llfollowcam.h" // Ventrella +#include "llgamecontrol.h" // LLGameControl::FLYCAM_NUM_CHANNELS #include "llhudeffectlookat.h" // EPointAtType #include "llhudeffectpointat.h" // ELookAtType @@ -171,6 +175,54 @@ class LLAgentCamera private: LLFollowCam mFollowCam; // Ventrella + //-------------------------------------------------------------------- + // Flycam + //-------------------------------------------------------------------- + // Free-flying camera engine (mFlycam + toggle state below), shared by two + // independent input sources that each feed it differently: + // - game-control/keyboard: updateFlycam() + mFlycamKeyboard* state here. + // - legacy NDOF joystick: applyNdofFlycamFrameDelta() below, called + // from LLViewerJoystick, whose OWN per-axis tuning state + // (mJoystickFlycamDelta/mJoystickFlycamLastDelta) lives over there, + // not on this class. + // Orthogonal to mCameraMode above: while active it preempts + // updateCamera() entirely (driven by updateFlycam()/ + // applyNdofFlycamFrameDelta() instead, called directly from the main + // loop), rather than being a peer state in that mode's own + // animated-transition machinery. +public: + bool isUsingFlycam() const { return mUsingFlycam; } + void toggleFlycam(); + + // Game-control/keyboard-driven update -- see LLViewerJoystick::moveFlycam() + // for the other (legacy NDOF joystick) input source, below. + void updateFlycam(F32 delta_time); + void setFlycamKeyInput(U8 channel, F32 value) { mFlycamKeyboardInput[channel] = value; } + void setFlycamKeyUnroll(bool unroll) { mFlycamKeyboardUnrollRequested = unroll; } + // Snapshots the live LLViewerCamera transform into the flycam. Used both + // when entering flycam (see toggleFlycam()) and whenever an input source + // (e.g. the NDOF joystick regaining focus) needs to recalibrate its own + // axis-delta state against the camera's current position/orientation. + void resetFlycamToCurrentView(); + + // Entry point for input sources (the legacy NDOF joystick) that compute + // their own per-axis dead-zone/scale/feathering deltas and only need this + // class to own the resulting transform update + push to LLViewerCamera -- + // see LLFlycam::applyFrameDelta() for the delta format. + void applyNdofFlycamFrameDelta(const F32 local_delta[7], + bool auto_level, F32 auto_level_fraction, + bool direct_view, F32 direct_view_value); +private: + LLFlycam mFlycam; + bool mUsingFlycam { false }; + + // Keyboard's contribution to the shared/game-control flycam channels + // (blended with LLGameControl::getFlycamInputs() each frame in + // updateFlycam()) -- unrelated to the legacy joystick's own tuning state, + // which lives on LLViewerJoystick (mJoystickFlycamDelta/LastDelta). + std::array mFlycamKeyboardInput {}; + bool mFlycamKeyboardUnrollRequested { false }; + //-------------------------------------------------------------------- // Sit //-------------------------------------------------------------------- @@ -265,8 +317,10 @@ class LLAgentCamera void cameraOrbitAround(const F32 radians); // Rotate camera CCW radians about build focus point void cameraOrbitOver(const F32 radians); // Rotate camera forward radians over build focus point void cameraOrbitIn(const F32 meters); // Move camera in toward build focus point + void cameraRollOver(const F32 radians); // Roll the camera void resetCameraOrbit(); void resetOrbitDiff(); + void resetCameraRoll(); //-------------------------------------------------------------------- // Zoom //-------------------------------------------------------------------- @@ -359,6 +413,8 @@ class LLAgentCamera F32 getOrbitDownKey() const { return mOrbitDownKey; } F32 getOrbitInKey() const { return mOrbitInKey; } F32 getOrbitOutKey() const { return mOrbitOutKey; } + F32 getRollLeftKey() const { return mRollLeftKey; } + F32 getRollRightKey() const { return mRollRightKey; } void setOrbitLeftKey(F32 mag) { mOrbitLeftKey = mag; } void setOrbitRightKey(F32 mag) { mOrbitRightKey = mag; } @@ -366,6 +422,8 @@ class LLAgentCamera void setOrbitDownKey(F32 mag) { mOrbitDownKey = mag; } void setOrbitInKey(F32 mag) { mOrbitInKey = mag; } void setOrbitOutKey(F32 mag) { mOrbitOutKey = mag; } + void setRollLeftKey(F32 mag) { mRollLeftKey = mag; } + void setRollRightKey(F32 mag) { mRollRightKey = mag; } void clearOrbitKeys(); private: @@ -379,6 +437,10 @@ class LLAgentCamera F32 mOrbitAroundRadians; F32 mOrbitOverAngle; + F32 mRollLeftKey; + F32 mRollRightKey; + F32 mRollAngle = 0.f; + //-------------------------------------------------------------------- // Pan //-------------------------------------------------------------------- @@ -411,7 +473,9 @@ class LLAgentCamera /** Keys ** ** *******************************************************************************/ - +public: + void storeCameraPosition(); + void loadCameraPosition(); }; extern LLAgentCamera gAgentCamera; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 6fef9e25125..1e2719ca09f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -46,6 +46,7 @@ #include "llagentwearables.h" #include "lldirpicker.h" #include "llfloaterimcontainer.h" +#include "llfloaterpreference.h" #include "llimprocessing.h" #include "llwindow.h" #include "llviewerstats.h" @@ -55,6 +56,7 @@ #include "llmarketplacenotifications.h" #include "llmd5.h" #include "llmeshrepository.h" +#include "llpanelpreferencegamecontrol.h" #include "llpumpio.h" #include "llmimetypes.h" #include "llslurl.h" @@ -62,6 +64,7 @@ #include "llfocusmgr.h" #include "llurlfloaterdispatchhandler.h" #include "llviewerjoystick.h" +#include "llgamecontrol.h" #include "llcalc.h" #include "llconversationlog.h" #if LL_WINDOWS @@ -194,6 +197,7 @@ #include "lldebugview.h" #include "llconsole.h" #include "llcontainerview.h" +#include "lltoolfocus.h" #include "lltooltip.h" #include "llsdutil.h" @@ -220,6 +224,7 @@ #include "llvosurfacepatch.h" #include "llviewerfloaterreg.h" #include "llcommandlineparser.h" +#include "llfloaterpreference.h" #include "llfloatermemleak.h" #include "llfloaterreg.h" #include "llfloatersimplesnapshot.h" @@ -1143,6 +1148,31 @@ bool LLAppViewer::init() LLViewerJoystick::getInstance()->init(false); } + LLGameControl::init(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "gamecontrollerdb.txt"), + [](const std::vector& keys) -> LLSD + { + LLSD result = LLSD::emptyMap(); + for (const std::string& key : keys) + { + if (gSavedSettings.controlExists(key)) + { + result[key] = gSavedSettings.getLLSD(key); + } + } + return result; + }, + [](const LLSD& key_values) + { + for (auto it = key_values.beginMap(); it != key_values.endMap(); ++it) + { + if (gSavedSettings.controlExists(it->first)) + { + gSavedSettings.setLLSD(it->first, it->second); + } + } + }, + []() { LLPanelPreferenceGameControl::updateDeviceList(); }); + try { initializeSecHandler(); @@ -1252,6 +1282,7 @@ bool LLAppViewer::init() LLWorld::createInstance(); LLViewerStatsRecorder::createInstance(); LLSelectMgr::createInstance(); + LLToolCamera::createInstance(); LLViewerCamera::createInstance(); LL::GLTFSceneManager::createInstance(); @@ -1339,6 +1370,66 @@ bool LLAppViewer::frame() return ret; } +void sendGameControlData() +{ + LLMessageSystem* msg = gMessageSystem; + const LLGameControl::ServerState& state = LLGameControl::getServerState(); + + msg->newMessageFast(_PREHASH_GameControlData); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgentID); + msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + msg->addU8Fast(_PREHASH_Packet, LLGameControl::getNextPacketNum()); + msg->addU8Fast(_PREHASH_ActionMode, state.mActionMode); + + // Non-modal canonical axes/buttons: pack every available value every message, + // whether or not it changed since the last one (no per-field diffing). Trailing + // zero axes are truncated and the Buttons block is skipped entirely when zero -- + // the server assumes anything not packed is zero -- to save bytes. + size_t num_axes = state.mAxes.size(); + while (num_axes > 0 && state.mAxes[num_axes - 1] == 0) + { + --num_axes; + } + for (size_t i = 0; i < num_axes; ++i) + { + msg->nextBlockFast(_PREHASH_Axes); + msg->addS16Fast(_PREHASH_Value, state.mAxes[i]); + } + if (state.mButtons != 0) + { + msg->nextBlockFast(_PREHASH_Buttons); + msg->addU32Fast(_PREHASH_Flags, state.mButtons); + } + + // Modal semantic axes/buttons: same truncation policy, except the number of + // axes considered depends on ActionMode -- see message_template.msg's + // GameControlData doc and LLGameControl::numSemanticAxesForMode() -- since the + // server computes its own expected axis count from the mode and zeros anything + // not packed. + size_t num_mode_axes = LLGameControl::numSemanticAxesForMode((LLGameControl::AgentControlMode)state.mActionMode); + while (num_mode_axes > 0 && state.mSemanticAxes[num_mode_axes - 1] == 0) + { + --num_mode_axes; + } + for (size_t i = 0; i < num_mode_axes; ++i) + { + msg->nextBlockFast(_PREHASH_ModeAxes); + msg->addS16Fast(_PREHASH_Value, state.mSemanticAxes[i]); + } + if (state.mSemanticButtons != 0) + { + msg->nextBlockFast(_PREHASH_ModeButtons); + msg->addU32Fast(_PREHASH_Flags, state.mSemanticButtons); + } + + LLGameControl::updateResendPeriod(); + gAgent.sendMessage(); + + // Mirror the just-sent channel values into the preferences "Data Output" tab. + LLPanelPreferenceGameControl::updateDataOutput(); +} + bool LLAppViewer::doFrame() { resumeMainloopTimeout("Main:doFrameStart"); @@ -1409,7 +1500,7 @@ bool LLAppViewer::doFrame() LL_WARNS() << " Someone took over my signal/exception handler (post messagehandling)!" << LL_ENDL; } - gViewerWindow->getWindow()->gatherInput(); + gViewerWindow->getWindow()->gatherInput(gFocusMgr.getAppHasFocus()); } //memory leaking simulation @@ -2086,6 +2177,8 @@ bool LLAppViewer::cleanup() LLViewerJoystick::getInstance()->terminate(); } + LLGameControl::terminate(); + LL_INFOS() << "Shutting down message system" << LL_ENDL; end_messaging_system(); @@ -4857,6 +4950,7 @@ void LLAppViewer::loadKeyBindings() } } LLUrlRegistry::instance().setKeybindingHandler(&gViewerInput); + LLUrlRegistry::instance().setGameControllerHandler(LLGameControl::getInstance()); } // As per GHI #4498, remove old, stale CEF cache folders from previous sessions @@ -5329,6 +5423,60 @@ void LLAppViewer::idle() gAgent.autoPilot(&yaw); } + // Auto-derive the active game-control mode (Avatar/Captive/FlyCam) from + // current avatar state so the runtime mappings and gating track it. + gAgent.updateGameControlMode(); + + // Read the agent's control flags now, before applyExternalActions() below + // (which drives the avatar from a real game controller) has a chance to + // fold any controller-driven movement into them this frame -- so the + // keyboard-derived GameControl state computed here can never be sourced + // from the controller's own output. See LLGameControllerManager:: + // setExternalInput()/accumulateFinalState() for where the two are + // combined (only as fully-independent final ServerStates). + U32 control_flags = gAgent.getControlFlags(); + + // apply to GameControl + LLGameControl::setExternalInput(control_flags, gAgent.getGameControlButtonsFromKeys(), gAgent.getRunning()); + + // Feed the actual on-screen cursor position to GameControl + // Matches the same whole-window clamp LLViewerWindow::moveCursorTo() + // uses to keep a gamepad-driven cursor on-screen. + if (LLGameControl::getAgentControlMode() == LLGameControl::CONTROL_MODE_CURSOR) + { + LLCoordGL cursor_pos = gViewerWindow->getCurrentMouse(); + S32 rect_width = gViewerWindow->getWindowWidthScaled(); + S32 rect_height = gViewerWindow->getWindowHeightScaled(); + S32 pixel_x = llclamp(cursor_pos.mX, 0, rect_width); + S32 pixel_y = llclamp(rect_height - cursor_pos.mY, 0, rect_height); + LLGameControl::setMouseCursorPosition(pixel_x, pixel_y, rect_width, rect_height); + } + + if (LLPanelPreferenceGameControl::isWaitingForInputChannel()) + { + LLPanelPreferenceGameControl::applyGameControlInput(); + } + else + { + // Recompute canonical controller state (also populates the internal + // state consumed below) and send it to the server when it changed. + if (LLGameControl::computeFinalStateAndCheckForChanges()) + { + sendGameControlData(); + } + + // Drive the local avatar from the controller. In Avatar/Captive + // modes this moves the avatar; in FlyCam mode applyExternalActions + // ignores movement bits (it early-returns while flycam is active) but + // still services the flycam on/off toggle, so we run it either way. + // Flycam *motion* is driven separately below via gAgent.updateFlycam(). + if (LLGameControl::willControlAvatar() || LLGameControl::willControlFlycam()) + { + LLGameControl::AgentActions game_control_actions = LLGameControl::computeAgentActions(); + gAgent.applyExternalActions(game_control_actions); + } + } + send_agent_update(false); // After calling send_agent_update() in the mainloop we always clear @@ -5596,9 +5744,14 @@ void LLAppViewer::idle() { gAgentPilot.moveCamera(); } - else if (LLViewerJoystick::getInstance()->getOverrideCamera()) + else if (gAgent.isUsingFlycam()) { + // Flycam is now a single shared state (LLAgentCamera) driven by + // whichever input sources are actually present; each is a no-op + // when its device/mode isn't active, so it's safe to feed both + // every frame rather than picking one exclusively. LLViewerJoystick::getInstance()->moveFlycam(); + gAgent.updateFlycam(); } else { diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index 2fd0eebd448..7938b333e1f 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -37,6 +37,7 @@ #include "llmd5.h" #include "llfindlocale.h" #include "llversioninfo.h" +#include "llfocusmgr.h" #include @@ -261,7 +262,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { - return LLWindowSDL::handleEvents(*event); + return LLWindowSDL::handleEvents(*event, gFocusMgr.getAppHasFocus()); } void SDL_AppQuit(void *appstate, SDL_AppResult result) diff --git a/indra/newview/llfloaterjoystick.cpp b/indra/newview/llfloaterjoystick.cpp index 871f924316c..00e6aee2347 100644 --- a/indra/newview/llfloaterjoystick.cpp +++ b/indra/newview/llfloaterjoystick.cpp @@ -44,6 +44,7 @@ #include "llwindow.h" #include "llcheckboxctrl.h" #include "llcombobox.h" +#include "llgamecontrol.h" #if LL_WINDOWS && !LL_MESA_HEADLESS // Require DirectInput version 8 @@ -78,15 +79,22 @@ BOOL CALLBACK di8_list_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, // Capable of detecting devices like Oculus Rift if (device_instance_ptr && pvRef) { + // We always include the device if it is from 3DConnexion, otherwise + // if the GameControl feature is enabled then we perfer to route other devices to it. std::string product_name = ll_convert(std::wstring(device_instance_ptr->tszProductName)); - S32 size = sizeof(GUID); - LLSD::Binary data; //just an std::vector - data.resize(size); - memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size); - - LLFloaterJoystick * floater = (LLFloaterJoystick*)pvRef; - LLSD value = data; - floater->addDevice(product_name, value); + bool include_device = LLViewerJoystick::is3DConnexionDevice(product_name); + + if (include_device) + { + S32 size = sizeof(GUID); + LLSD::Binary data; //just an std::vector + data.resize(size); + memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size); + + LLFloaterJoystick * floater = (LLFloaterJoystick*)pvRef; + LLSD value = data; + floater->addDevice(product_name, value); + } } return DIENUM_CONTINUE; } @@ -303,8 +311,12 @@ void LLFloaterJoystick::refreshListOfDevices() #if LL_WINDOWS && !LL_MESA_HEADLESS LL_WARNS() << "NDOF connected to device without using SL provided handle" << LL_ENDL; #endif - std::string desc = joystick->getDescription(); - if (!desc.empty()) + // We always include the device if it is from 3DConnexion, otherwise we only + // support the device in this context when GameControl feature is disabled. + std::string desc = LLViewerJoystick::getInstance()->getDescription(); + bool include_device = LLViewerJoystick::is3DConnexionDevice(desc); + + if (include_device) { LLSD value = LLSD::Integer(1); // value for selection addDevice(desc, value); diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp old mode 100644 new mode 100755 index a0db801dd2a..5d2311d691f --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -54,6 +54,7 @@ #include "llfloaterperformance.h" #include "llfloatersidepanelcontainer.h" #include "llfloaterimsession.h" +#include "llgamecontrol.h" #include "llkeyboard.h" #include "llmodaldialog.h" #include "llnavigationbar.h" @@ -62,6 +63,7 @@ #include "llnotificationsutil.h" #include "llnotificationtemplate.h" #include "llpanellogin.h" +#include "llpanelpreferencegamecontrol.h" #include "llpanelvoicedevicesettings.h" #include "llradiogroup.h" #include "llsearchcombobox.h" @@ -287,6 +289,39 @@ class LLKeybindingHandler: public LLCommandHandler }; LLKeybindingHandler gKeybindHandler; +// TODO: move this out of llfloaterpreference.cpp +class LLGameControlBindingHandler: public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLGameControlBindingHandler(): LLCommandHandler("gamecontroller", UNTRUSTED_CLICK_ONLY) + { + } + + bool handle(const LLSD& params, const LLSD& query_map, + const std::string& grid, LLMediaCtrl* web) + { + if (params.size() < 1) return false; + + LLFloaterPreference* prefsfloater = dynamic_cast + (LLFloaterReg::showInstance("preferences")); + + if (prefsfloater) + { + // find 'controls' panel and bring it the front + LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); + LLPanel* panel = prefsfloater->getChild("game_control"); + if (tabcontainer && panel) + { + tabcontainer->selectTabPanel(panel); + } + } + + return true; + } +}; +LLGameControlBindingHandler gGameControlBindingHandler; + ////////////////////////////////////////////// // LLFloaterPreference @@ -407,6 +442,7 @@ void LLFloaterPreference::saveAvatarProperties( void ) } } +// static void LLFloaterPreference::saveAvatarPropertiesCoro(const std::string cap_url, bool allow_publish) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); @@ -580,21 +616,19 @@ void LLFloaterPreference::draw() void LLFloaterPreference::saveSettings() { LLTabContainer* tabcontainer = getChild("pref core"); - child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - child_list_t::const_iterator end = tabcontainer->getChildList()->end(); - for ( ; iter != end; ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) + if (LLPanelPreference* panel = dynamic_cast(view)) + { panel->saveSettings(); + } } saveIgnoredNotifications(); } void LLFloaterPreference::apply() { - LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); + LLAvatarPropertiesProcessor::getInstance()->addObserver(gAgent.getID(), this); LLTabContainer* tabcontainer = getChild("pref core"); if (sSkin != gSavedSettings.getString("SkinCurrent")) @@ -602,14 +636,14 @@ void LLFloaterPreference::apply() LLNotificationsUtil::add("ChangeSkin"); refreshSkin(this); } + // Call apply() on all panels that derive from LLPanelPreference - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) + if (LLPanelPreference* panel = dynamic_cast(view)) + { panel->apply(); + } } gViewerWindow->requestResolutionUpdate(); // for UIScaleFactor @@ -621,7 +655,9 @@ void LLFloaterPreference::apply() std::string cache_location = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); setCacheLocation(cache_location); - if (hasChild("web_proxy_enabled", true) &&hasChild("web_proxy_editor", true) && hasChild("web_proxy_port", true)) + LLViewerMedia::getInstance()->setCookiesEnabled(getChild("cookies_enabled")->getValue()); + + if (hasChild("web_proxy_enabled", true) && hasChild("web_proxy_editor", true) && hasChild("web_proxy_port", true)) { bool proxy_enable = getChild("web_proxy_enabled")->getValue(); std::string proxy_address = getChild("web_proxy_editor")->getValue(); @@ -658,13 +694,12 @@ void LLFloaterPreference::cancel(const std::vector settings_to_skip { LLTabContainer* tabcontainer = getChild("pref core"); // Call cancel() on all panels that derive from LLPanelPreference - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) + if (LLPanelPreference* panel = dynamic_cast(view)) + { panel->cancel(settings_to_skip); + } } // hide joystick pref floater LLFloaterReg::hideInstance("pref_joystick"); @@ -693,7 +728,7 @@ void LLFloaterPreference::cancel(const std::vector settings_to_skip } //Need to reload the navmesh if the pathing console is up LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); - if ( !pathfindingConsoleHandle.isDead() ) + if (!pathfindingConsoleHandle.isDead()) { LLFloaterPathfindingConsole* pPathfindingConsole = pathfindingConsoleHandle.get(); pPathfindingConsole->onRegionBoundaryCross(); @@ -710,6 +745,15 @@ void LLFloaterPreference::cancel(const std::vector settings_to_skip void LLFloaterPreference::onOpen(const LLSD& key) { + LLTabContainer* tabcontainer = getChild("pref core"); + for (LLView* view : *tabcontainer->getChildList()) + { + if (LLPanelPreference* panel = dynamic_cast(view)) + { + panel->onOpen(key); + } + } + // this variable and if that follows it are used to properly handle do not disturb mode response message static bool initialized = false; // if user is logged in and we haven't initialized do not disturb mode response yet, do it @@ -789,8 +833,7 @@ void LLFloaterPreference::onOpen(const LLSD& key) // while preferences floater was closed. buildPopupLists(); - - //get the options that were checked + // get the options that were checked onNotificationsChange("FriendIMOptions"); onNotificationsChange("NonFriendIMOptions"); onNotificationsChange("ConferenceIMOptions"); @@ -802,8 +845,7 @@ void LLFloaterPreference::onOpen(const LLSD& key) refresh(); // Make sure the current state of prefs are saved away when - // when the floater is opened. That will make cancel do its - // job + // the floater is opened. That will make cancel() do its job saveSettings(); // Make sure there is a default preference file @@ -971,6 +1013,8 @@ void LLFloaterPreference::onClose(bool app_quitting) LLPanelLogin::setAlwaysRefresh(false); if (!app_quitting) { + // is this a BUG? cancel() should only be called in onBtnCancel() + // but not during closing after onBtnOK() cancel(); } } @@ -1003,9 +1047,9 @@ void LLFloaterPreference::onBtnOK(const LLSD& userdata) } //Conversation transcript and log path changed so reload conversations based on new location - if(mPriorInstantMessageLogPath.length()) + if (mPriorInstantMessageLogPath.length()) { - if(moveTranscriptsAndLog()) + if (moveTranscriptsAndLog()) { //When floaters are empty but have a chat history files, reload chat history into them LLFloaterIMSessionTab::reloadEmptyFloaters(); @@ -1022,11 +1066,11 @@ void LLFloaterPreference::onBtnOK(const LLSD& userdata) LLUIColorTable::instance().saveUserSettings(); gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); - //Only save once logged in and loaded per account settings - if(mGotPersonalInfo) + // Only save once logged in and loaded per account settings + if (mGotPersonalInfo) { gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), true); - } + } } else { @@ -1069,26 +1113,33 @@ void LLFloaterPreference::onBtnCancel(const LLSD& userdata) } } +//static +void LLFloaterPreference::refreshInstance() +{ + if (LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences")) + { + instance->refresh(); + } +} + // static void LLFloaterPreference::updateUserInfo(const std::string& visibility) { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) + if (LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences")) { instance->setPersonalInfo(visibility); } } +// static void LLFloaterPreference::refreshEnabledGraphics() { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) + if (LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences")) { instance->refresh(); } - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - if (advanced) + if (LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced")) { advanced->refresh(); } @@ -1130,7 +1181,7 @@ void LLFloaterPreference::onNotificationsChange(const std::string& OptionName) bool show_notifications_alert = true; for (notifications_map::iterator it_notification = mNotificationOptions.begin(); it_notification != mNotificationOptions.end(); it_notification++) { - if(it_notification->second != "No action") + if (it_notification->second != "No action") { show_notifications_alert = false; break; @@ -1142,8 +1193,7 @@ void LLFloaterPreference::onNotificationsChange(const std::string& OptionName) void LLFloaterPreference::onNameTagOpacityChange(const LLSD& newvalue) { - LLColorSwatchCtrl* color_swatch = findChild("background"); - if (color_swatch) + if (LLColorSwatchCtrl* color_swatch = findChild("background")) { LLColor4 new_color = color_swatch->get(); color_swatch->set(new_color.setAlpha((F32)newvalue.asReal())); @@ -1324,7 +1374,16 @@ void LLAvatarComplexityControls::setIndirectMaxArc() void LLFloaterPreference::refresh() { - LLPanel::refresh(); + LLTabContainer* tabcontainer = getChild("pref core"); + for (LLView* view : *tabcontainer->getChildList()) + { + if (LLPanelPreferenceControls* panel = dynamic_cast(view)) + { + panel->refresh(); + break; + } + } + LLFloater::refresh(); setMaxNonImpostorsText( gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), getChild("IndirectMaxNonImpostorsText", true)); @@ -1332,8 +1391,7 @@ void LLFloaterPreference::refresh() gSavedSettings.getU32("RenderAvatarMaxComplexity"), getChild("IndirectMaxComplexityText", true)); refreshEnabledState(); - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - if (advanced) + if (LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced")) { advanced->refresh(); } @@ -1355,7 +1413,7 @@ void LLFloaterPreference::onCommitWindowedMode() void LLFloaterPreference::onChangeQuality(const LLSD& data) { - U32 level = (U32)(data.asReal()); + U32 level = (U32)data.asReal(); constexpr U32 LVL_HIGH = 4; if (level >= LVL_HIGH && mLastQualityLevel < level) { @@ -1463,7 +1521,6 @@ void LLFloaterPreference::onClickLogPath() std::string proposed_name(gSavedPerAccountSettings.getString("InstantMessageLogPath")); mPriorInstantMessageLogPath.clear(); - (new LLDirPickerThread(boost::bind(&LLFloaterPreference::changeLogPath, this, _1, _2), proposed_name))->getFile(); } @@ -1488,10 +1545,10 @@ bool LLFloaterPreference::moveTranscriptsAndLog() bool madeDirectory = false; //Does the directory really exist, if not then make it - if(!LLFile::isdir(chatLogPath)) + if (!LLFile::isdir(chatLogPath)) { //mkdir success is defined as zero - if(LLFile::mkdir(chatLogPath) != 0) + if (LLFile::mkdir(chatLogPath) != 0) { return false; } @@ -1501,10 +1558,10 @@ bool LLFloaterPreference::moveTranscriptsAndLog() std::string originalConversationLogDir = LLConversationLog::instance().getFileName(); std::string targetConversationLogDir = gDirUtilp->add(chatLogPath, "conversation.log"); //Try to move the conversation log - if(!LLConversationLog::instance().moveLog(originalConversationLogDir, targetConversationLogDir)) + if (!LLConversationLog::instance().moveLog(originalConversationLogDir, targetConversationLogDir)) { //Couldn't move the log and created a new directory so remove the new directory - if(madeDirectory) + if (madeDirectory) { LLFile::remove(chatLogPath); } @@ -1517,7 +1574,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() LLLogChat::getListOfTranscriptFiles(listOfTranscripts); - if(!LLLogChat::moveTranscripts(gDirUtilp->getChatLogsDir(), + if (!LLLogChat::moveTranscripts(gDirUtilp->getChatLogsDir(), instantMessageLogPath, listOfTranscripts, listOfFilesMoved)) @@ -1530,7 +1587,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() //Move the conversation log back LLConversationLog::instance().moveLog(targetConversationLogDir, originalConversationLogDir); - if(madeDirectory) + if (madeDirectory) { LLFile::remove(chatLogPath); } @@ -1577,7 +1634,6 @@ void LLFloaterPreference::setPersonalInfo(const std::string& visibility) getChild("voice_call_friends_only_check")->setValue(gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly")); } - void LLFloaterPreference::refreshUI() { refresh(); @@ -1882,8 +1938,7 @@ void LLFloaterPreference::onClickActionChange() void LLFloaterPreference::onAtmosShaderChange() { - LLCheckBoxCtrl* ctrl_alm = getChild("UseLightShaders"); - if(ctrl_alm) + if (LLCheckBoxCtrl* ctrl_alm = getChild("UseLightShaders")) { //Deferred/SSAO/Shadows bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump") && gSavedSettings.getBOOL("RenderObjectBump"); @@ -1944,12 +1999,9 @@ void LLFloaterPreference::updateClickActionControls() // In such case we won't need to do this 'dynamic_cast' nightmare. // updateTable() can also be avoided LLTabContainer* tabcontainer = getChild("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreferenceControls* panel = dynamic_cast(view); - if (panel) + if (LLPanelPreferenceControls* panel = dynamic_cast(view)) { panel->setKeyBind("walk_to", EMouseClickType::CLICK_LEFT, @@ -1983,12 +2035,9 @@ void LLFloaterPreference::updateClickActionViews() // Todo: This is a very ugly way to get access to keybindings. // Reconsider possible options. LLTabContainer* tabcontainer = getChild("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreferenceControls* panel = dynamic_cast(view); - if (panel) + if (LLPanelPreferenceControls* panel = dynamic_cast(view)) { click_to_walk = panel->canKeyBindHandle("walk_to", EMouseClickType::CLICK_LEFT, @@ -2044,6 +2093,16 @@ void LLFloaterPreference::selectPanel(const LLSD& name) } } +void LLFloaterPreference::setPanelVisibility(const LLSD& name, bool visible) +{ + LLTabContainer * tab_containerp = getChild("pref core"); + LLPanel * panel = tab_containerp->getPanelByName(name.asStringRef()); + if (NULL != panel) + { + tab_containerp->setTabVisibility(panel, visible); + } +} + void LLFloaterPreference::selectPrivacyPanel() { selectPanel("im"); @@ -2076,7 +2135,6 @@ void LLFloaterPreference::changed() // set 'enable' property for 'Delete transcripts...' button updateDeleteTranscriptsButton(); - } void LLFloaterPreference::saveGraphicsPreset(std::string& preset) @@ -2184,7 +2242,6 @@ bool LLPanelPreference::postBuild() if (hasChild("media_enabled", true)) { bool media_enabled = gSavedSettings.getBOOL("AudioStreamingMedia"); - getChild("media_enabled")->set(media_enabled); getChild("autoplay_enabled")->setEnabled(media_enabled); } @@ -2230,7 +2287,6 @@ bool LLPanelPreference::postBuild() } #endif - apply(); return true; } @@ -2241,41 +2297,41 @@ LLPanelPreference::~LLPanelPreference() delete mBandWidthUpdater; } } + +// virtual void LLPanelPreference::apply() { // no-op } +// virtual void LLPanelPreference::saveSettings() { - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - // Save the value of all controls in the hierarchy mSavedValues.clear(); std::list view_stack; view_stack.push_back(this); - if (advanced) + // Search for 'Advanced' panel and add it if found + if (LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced")) { view_stack.push_back(advanced); } - while(!view_stack.empty()) + + while (!view_stack.empty()) { // Process view on top of the stack LLView* curview = view_stack.front(); view_stack.pop_front(); - LLColorSwatchCtrl* color_swatch = dynamic_cast(curview); - if (color_swatch) + if (LLColorSwatchCtrl* color_swatch = dynamic_cast(curview)) { mSavedColors[color_swatch->getName()] = color_swatch->get(); } else { - LLUICtrl* ctrl = dynamic_cast(curview); - if (ctrl) + if (LLUICtrl* ctrl = dynamic_cast(curview)) { - LLControlVariable* control = ctrl->getControlVariable(); - if (control) + if (LLControlVariable* control = ctrl->getControlVariable()) { mSavedValues[control] = control->getValue(); } @@ -2283,10 +2339,9 @@ void LLPanelPreference::saveSettings() } // Push children onto the end of the work stack - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) + for (LLView* view : *curview->getChildList()) { - view_stack.push_back(*iter); + view_stack.push_back(view); } } @@ -2428,7 +2483,7 @@ class LLPanelPreferencePrivacy : public LLPanelPreference mAccountIndependentSettings.push_back("AutoDisengageMic"); } - /*virtual*/ void saveSettings() + void saveSettings() override { LLPanelPreference::saveSettings(); @@ -2443,7 +2498,7 @@ class LLPanelPreferencePrivacy : public LLPanelPreference if (find(mAccountIndependentSettings.begin(), mAccountIndependentSettings.end(), setting) == mAccountIndependentSettings.end()) { - mSavedValues.erase(it++); + it = mSavedValues.erase(it); } else { @@ -2607,9 +2662,11 @@ void LLPanelPreferenceGraphics::cancel(const std::vector settings_t { LLPanelPreference::cancel(settings_to_skip); } + void LLPanelPreferenceGraphics::saveSettings() { resetDirtyChilds(); + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); if (preset_graphic_active.empty()) { @@ -2620,8 +2677,10 @@ void LLPanelPreferenceGraphics::saveSettings() instance->saveGraphicsPreset(preset_graphic_active); } } + LLPanelPreference::saveSettings(); } + void LLPanelPreferenceGraphics::setHardwareDefaults() { resetDirtyChilds(); @@ -2646,6 +2705,12 @@ LLPanelPreferenceControls::~LLPanelPreferenceControls() { } +void LLPanelPreferenceControls::refresh() +{ + populateControlTable(); + LLPanelPreference::refresh(); +} + bool LLPanelPreferenceControls::postBuild() { // populate list of controls @@ -2717,12 +2782,9 @@ bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) cell_params.column = ""; cell_params.value = ""; - - for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); - row_it != contents.rows.end(); - ++row_it) + for (LLScrollListItem::Params& row_params : contents.rows) { - std::string control = row_it->value.getValue().asString(); + std::string control = row_params.value.getValue().asString(); if (!control.empty() && control != "menu_separator") { bool show = true; @@ -2741,7 +2803,7 @@ bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) if (show) { // At the moment viewer is hardcoded to assume that columns are named as lst_ctrl%d - LLScrollListItem::Params item_params(*row_it); + LLScrollListItem::Params item_params(row_params); item_params.enabled.setValue(enabled); S32 num_columns = pControlsTable->getNumColumns(); @@ -2766,7 +2828,7 @@ bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) // value = "menu_separator" // column = "lst_action" / > // - pControlsTable->addRow(*row_it, EAddPosition::ADD_BOTTOM); + pControlsTable->addRow(row_params, EAddPosition::ADD_BOTTOM); } } return true; @@ -2788,6 +2850,22 @@ void LLPanelPreferenceControls::addControlTableSeparator() void LLPanelPreferenceControls::populateControlTable() { + // The mode combo is the source of truth for which bindings to display. + // Keep mEditingMode in sync with it and make sure that mode's bindings are + // loaded before we render; otherwise the table shows empty cells on first + // open (refresh() populates before any load has happened, and mEditingMode + // may not match the mode currently selected in the combo). + if (pKeyModeBox) + { + mEditingMode = pKeyModeBox->getValue().asInteger(); + } + if (mEditingMode >= 0 + && mEditingMode < (S32)LLKeyConflictHandler::MODE_COUNT + && mConflictHandler[mEditingMode].empty()) + { + mConflictHandler[mEditingMode].loadFromSettings((LLKeyConflictHandler::ESourceMode)mEditingMode); + } + pControlsTable->clearRows(); pControlsTable->clearColumns(); @@ -2799,6 +2877,7 @@ void LLPanelPreferenceControls::populateControlTable() case LLKeyConflictHandler::MODE_FIRST_PERSON: case LLKeyConflictHandler::MODE_EDIT_AVATAR: case LLKeyConflictHandler::MODE_SITTING: + case LLKeyConflictHandler::MODE_FLYCAM: filename = "control_table_contents_columns_basic.xml"; break; default: @@ -2826,6 +2905,14 @@ void LLPanelPreferenceControls::populateControlTable() addControlTableRows("control_table_contents_movement.xml"); addControlTableSeparator(); addControlTableRows("control_table_contents_media.xml"); + addControlTableSeparator(); + addControlTableRows("control_table_contents_game_control.xml"); + } + else if (mEditingMode == LLKeyConflictHandler::MODE_FLYCAM) + { + // Flycam has its own dedicated set of actions; camera/editing/media + // groups don't apply to it. + addControlTableRows("control_table_contents_flycam.xml"); } // MODE_THIRD_PERSON; MODE_EDIT_AVATAR; MODE_SITTING else if (mEditingMode < LLKeyConflictHandler::MODE_SAVED_SETTINGS) @@ -2842,6 +2929,9 @@ void LLPanelPreferenceControls::populateControlTable() addControlTableSeparator(); addControlTableRows("control_table_contents_media.xml"); + addControlTableSeparator(); + + addControlTableRows("control_table_contents_game_control.xml"); } else { @@ -3217,6 +3307,7 @@ void LLPanelPreferenceControls::onDefaultKeyBind(bool all_modes) mConflictHandler[mEditingMode].saveToSettings(true); } } + updateTable(); if (mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON || all_modes) @@ -3235,6 +3326,8 @@ void LLPanelPreferenceControls::onCancelKeyBind() pControlsTable->deselectAllItems(); } +//------------------------LLFloaterPreferenceProxy-------------------------------- + LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) : LLFloater(key), mSocksSettingsDirty(false) @@ -3278,7 +3371,7 @@ void LLFloaterPreferenceProxy::onOpen(const LLSD& key) void LLFloaterPreferenceProxy::onClose(bool app_quitting) { - if(app_quitting) + if (app_quitting) { cancel(); } @@ -3302,7 +3395,7 @@ void LLFloaterPreferenceProxy::saveSettings() mSavedValues.clear(); std::list view_stack; view_stack.push_back(this); - while(!view_stack.empty()) + while (!view_stack.empty()) { // Process view on top of the stack LLView* curview = view_stack.front(); @@ -3389,13 +3482,9 @@ void LLFloaterPreferenceProxy::onClickCloseBtn(bool app_quitting) void LLFloaterPreferenceProxy::cancel() { - - for (control_values_map_t::iterator iter = mSavedValues.begin(); - iter != mSavedValues.end(); ++iter) + for (const auto& iter : mSavedValues) { - LLControlVariable* control = iter->first; - LLSD ctrl_value = iter->second; - control->set(ctrl_value); + iter.first->set(iter.second); } mSocksSettingsDirty = false; closeFloater(); @@ -3420,21 +3509,20 @@ void LLFloaterPreferenceProxy::onChangeSocksSettings() // Check for invalid states for the other HTTP proxy radio LLRadioGroup* otherHttpProxy = getChild("other_http_proxy_type"); if ((otherHttpProxy->getSelectedValue().asString() == "Socks" && - !getChild("socks_proxy_enabled")->get())||( + getChild("socks_proxy_enabled")->get() == false )||( otherHttpProxy->getSelectedValue().asString() == "Web" && - !getChild("web_proxy_enabled")->get())) + getChild("web_proxy_enabled")->get() == false ) ) { otherHttpProxy->selectFirstItem(); } - } void LLFloaterPreference::onUpdateFilterTerm(bool force) { - LLWString seachValue = utf8str_to_wstring(mFilterEdit->getValue()); - LLWStringUtil::toLower(seachValue); + LLWString seachValue = utf8str_to_wstring( mFilterEdit->getValue() ); + LLWStringUtil::toLower( seachValue ); - if (!mSearchData || (mSearchData->mLastFilter == seachValue && !force)) + if( !mSearchData || (mSearchData->mLastFilter == seachValue && !force)) return; if (mSearchDataDirty) @@ -3445,7 +3533,7 @@ void LLFloaterPreference::onUpdateFilterTerm(bool force) mSearchData->mLastFilter = seachValue; - if (!mSearchData->mRootTab) + if( !mSearchData->mRootTab ) return; mSearchData->mRootTab->hightlightAndHide( seachValue ); @@ -3468,10 +3556,10 @@ void LLFloaterPreference::filterIgnorableNotifications() void collectChildren( LLView const *aView, ll::prefs::PanelDataPtr aParentPanel, ll::prefs::TabContainerDataPtr aParentTabContainer ) { - if (!aView) + if( !aView ) return; - llassert_always(aParentPanel || aParentTabContainer); + llassert_always( aParentPanel || aParentTabContainer ); for (LLView* pView : *aView->getChildList()) { @@ -3481,56 +3569,56 @@ void collectChildren( LLView const *aView, ll::prefs::PanelDataPtr aParentPanel, ll::prefs::PanelDataPtr pCurPanelData = aParentPanel; ll::prefs::TabContainerDataPtr pCurTabContainer = aParentTabContainer; - LLPanel const *pPanel = dynamic_cast(pView); - LLTabContainer const *pTabContainer = dynamic_cast(pView); - ll::ui::SearchableControl const *pSCtrl = dynamic_cast( pView ); + LLPanel const *pPanel = dynamic_cast< LLPanel const *>( pView ); + LLTabContainer const *pTabContainer = dynamic_cast< LLTabContainer const *>( pView ); + ll::ui::SearchableControl const *pSCtrl = dynamic_cast< ll::ui::SearchableControl const *>( pView ); - if (pTabContainer) + if( pTabContainer ) { pCurPanelData.reset(); - pCurTabContainer = ll::prefs::TabContainerDataPtr(new ll::prefs::TabContainerData); - pCurTabContainer->mTabContainer = const_cast< LLTabContainer *>(pTabContainer); + pCurTabContainer = ll::prefs::TabContainerDataPtr( new ll::prefs::TabContainerData ); + pCurTabContainer->mTabContainer = const_cast< LLTabContainer *>( pTabContainer ); pCurTabContainer->mLabel = pTabContainer->getLabel(); pCurTabContainer->mPanel = 0; - if (aParentPanel) - aParentPanel->mChildPanel.push_back(pCurTabContainer); - if (aParentTabContainer) - aParentTabContainer->mChildPanel.push_back(pCurTabContainer); + if( aParentPanel ) + aParentPanel->mChildPanel.push_back( pCurTabContainer ); + if( aParentTabContainer ) + aParentTabContainer->mChildPanel.push_back( pCurTabContainer ); } - else if (pPanel) + else if( pPanel ) { pCurTabContainer.reset(); - pCurPanelData = ll::prefs::PanelDataPtr(new ll::prefs::PanelData); + pCurPanelData = ll::prefs::PanelDataPtr( new ll::prefs::PanelData ); pCurPanelData->mPanel = pPanel; pCurPanelData->mLabel = pPanel->getLabel(); llassert_always( aParentPanel || aParentTabContainer ); - if (aParentTabContainer) - aParentTabContainer->mChildPanel.push_back(pCurPanelData); - else if (aParentPanel) - aParentPanel->mChildPanel.push_back(pCurPanelData); + if( aParentTabContainer ) + aParentTabContainer->mChildPanel.push_back( pCurPanelData ); + else if( aParentPanel ) + aParentPanel->mChildPanel.push_back( pCurPanelData ); } - else if (pSCtrl && pSCtrl->getSearchText().size()) + else if( pSCtrl && pSCtrl->getSearchText().size() ) { - ll::prefs::SearchableItemPtr item = ll::prefs::SearchableItemPtr(new ll::prefs::SearchableItem()); + ll::prefs::SearchableItemPtr item = ll::prefs::SearchableItemPtr( new ll::prefs::SearchableItem() ); item->mView = pView; item->mCtrl = pSCtrl; - item->mLabel = utf8str_to_wstring(pSCtrl->getSearchText()); - LLWStringUtil::toLower(item->mLabel); + item->mLabel = utf8str_to_wstring( pSCtrl->getSearchText() ); + LLWStringUtil::toLower( item->mLabel ); - llassert_always(aParentPanel || aParentTabContainer); + llassert_always( aParentPanel || aParentTabContainer ); - if (aParentPanel) - aParentPanel->mChildren.push_back(item); - if (aParentTabContainer) - aParentTabContainer->mChildren.push_back(item); + if( aParentPanel ) + aParentPanel->mChildren.push_back( item ); + if( aParentTabContainer ) + aParentTabContainer->mChildren.push_back( item ); } - collectChildren(pView, pCurPanelData, pCurTabContainer); + collectChildren( pView, pCurPanelData, pCurTabContainer ); } } diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index aa958459c9b..3b37811f127 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -36,9 +36,13 @@ #include "llfloater.h" #include "llavatarpropertiesprocessor.h" #include "llconversationlog.h" +#include "llgamecontrol.h" +#include "llkeyconflict.h" +#include "llscrolllistcell.h" +#include "llscrolllistctrl.h" #include "llsearcheditor.h" #include "llsetkeybinddialog.h" -#include "llkeyconflict.h" +#include "llspinctrl.h" class LLConversationLogObserver; class LLPanelPreference; @@ -80,12 +84,14 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, void apply(); void cancel(const std::vector settings_to_skip = {}); - /*virtual*/ void draw(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void changed(); - /*virtual*/ void changed(const LLUUID& session_id, U32 mask) {}; + virtual void draw() override; + virtual bool postBuild() override; + virtual void onOpen(const LLSD& key) override; + virtual void onClose(bool app_quitting) override; + virtual void changed() override; + virtual void changed(const LLUUID& session_id, U32 mask) override {}; + + static void refreshInstance(); // static data update, called from message handler static void updateUserInfo(const std::string& visibility); @@ -99,7 +105,7 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, // update Show Favorites checkbox static void updateShowFavoritesCheckbox(bool val); - void processProperties( void* pData, EAvatarProcessorType type ); + void processProperties( void* pData, EAvatarProcessorType type ) override; void saveAvatarProperties( void ); static void saveAvatarPropertiesCoro(const std::string url, bool allow_publish); void selectPrivacyPanel(); @@ -168,7 +174,7 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, void setPersonalInfo(const std::string& visibility); void refreshEnabledState(); void onCommitWindowedMode(); - void refresh(); // Refresh enable/disable + void refresh() override; // Refresh enable/disable // if the quality radio buttons are changed void onChangeQuality(const LLSD& data); @@ -198,6 +204,7 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, void buildPopupLists(); static void refreshSkin(void* data); void selectPanel(const LLSD& name); + void setPanelVisibility(const LLSD& name, bool visible); void saveGraphicsPreset(std::string& preset); void setRecommendedSettings(); @@ -256,7 +263,7 @@ class LLPanelPreference : public LLPanel { public: LLPanelPreference(); - /*virtual*/ bool postBuild(); + virtual bool postBuild() override; virtual ~LLPanelPreference(); @@ -302,12 +309,12 @@ class LLPanelPreference : public LLPanel class LLPanelPreferenceGraphics : public LLPanelPreference { public: - bool postBuild(); - void draw(); - void cancel(const std::vector settings_to_skip = {}); - void saveSettings(); + bool postBuild() override; + void draw() override; + void cancel(const std::vector settings_to_skip = {}) override; + void saveSettings() override; void resetDirtyChilds(); - void setHardwareDefaults(); + void setHardwareDefaults() override; void setPresetText(); protected: @@ -325,11 +332,13 @@ class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResp LLPanelPreferenceControls(); virtual ~LLPanelPreferenceControls(); - bool postBuild(); + bool postBuild() override; - void apply(); - void cancel(const std::vector settings_to_skip = {}); - void saveSettings(); + void refresh() override; + + void apply() override; + void cancel(const std::vector settings_to_skip = {}) override; + void saveSettings() override; void resetDirtyChilds(); void onListCommit(); @@ -345,9 +354,12 @@ class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResp void updateAndApply(); // from interface - /*virtual*/ bool onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes); - /*virtual*/ void onDefaultKeyBind(bool all_modes); - /*virtual*/ void onCancelKeyBind(); + bool onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes) override; + void onDefaultKeyBind(bool all_modes) override; + void onCancelKeyBind() override; + + // Cleans content and then adds content from xml files according to current mEditingMode + void populateControlTable(); private: // reloads settings, discards current changes, updates table @@ -358,9 +370,6 @@ class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResp bool addControlTableRows(const std::string &filename); void addControlTableSeparator(); - // Cleans content and then adds content from xml files according to current mEditingMode - void populateControlTable(); - // Updates keybindings from storage to table void updateTable(); @@ -396,13 +405,13 @@ class LLFloaterPreferenceProxy : public LLFloater void cancel(); protected: - bool postBuild(); - void onOpen(const LLSD& key); - void onClose(bool app_quitting); + bool postBuild() override; + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; void saveSettings(); void onBtnOk(); void onBtnCancel(); - void onClickCloseBtn(bool app_quitting = false); + void onClickCloseBtn(bool app_quitting = false) override; void onChangeSocksSettings(); diff --git a/indra/newview/llflycam.cpp b/indra/newview/llflycam.cpp new file mode 100644 index 00000000000..4ca4aeeee3c --- /dev/null +++ b/indra/newview/llflycam.cpp @@ -0,0 +1,307 @@ +/** + * @file llflycam.cpp + * @brief LLFlycam class implementation + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llflycam.h" + +#include +#include "llcamera.h" +#include "llcoordframe.h" +#include "m3math.h" + +void LLFlycam::setTransform(const LLVector3d& position, const LLQuaternion& rotation) +{ + mPosition = position; + mRotation = rotation; + mRotation.normalize(); +} + +void LLFlycam::getTransform(LLVector3d& position_out, LLQuaternion& rotation_out) +{ + position_out = mPosition; + rotation_out = mRotation; +} + +// 'view' is expected to be in radians +void LLFlycam::setView(F32 view) +{ + mView = std::clamp(view, MIN_FIELD_OF_VIEW, MAX_FIELD_OF_VIEW); +} + + +void LLFlycam::setLinearVelocity(const LLVector3& velocity) +{ + // Note: this math expects velocity components to be in range [-1.0, 1.0] + mLinearVelocity = velocity * mSpeedFactor; +} + + +void LLFlycam::setPitchRate(F32 pitch_rate) +{ + // Note: this math expects pitch_rate to be in range [-1.0, 1.0] + constexpr F32 PITCH_RATE_FACTOR = 0.75f; + mPitchRate = pitch_rate * mSpeedFactor * PITCH_RATE_FACTOR; +} + + +void LLFlycam::setYawRate(F32 yaw_rate) +{ + // Note: this math expects yaw_rate to be in range [-1.0, 1.0] + constexpr F32 YAW_RATE_FACTOR = 0.90f; + mYawRate = yaw_rate * mSpeedFactor * YAW_RATE_FACTOR; +} + + +void LLFlycam::setRollRate(F32 roll_rate) +{ + // Note: this math expects roll_rate to be in range [-1.0, 1.0] + constexpr F32 ROLL_RATE_FACTOR = 0.90f; + mRollRate = mAllowRoll ? roll_rate * mSpeedFactor * ROLL_RATE_FACTOR : 0.0f; +} + + +void LLFlycam::setSpeedFactor(F32 speed_factor) +{ + mSpeedFactor = std::clamp(speed_factor, MIN_FLYCAM_SPEED_FACTOR, MAX_FLYCAM_SPEED_FACTOR); +} + + +void LLFlycam::setZoomRate(F32 zoom_rate) +{ + // Note: this math expects zoom_rate to be in range [-1.0, 1.0] + constexpr F32 FULL_ZOOM_PERIOD = 5.0f; // seconds + constexpr F32 ZOOM_RATE_FACTOR = (MAX_FIELD_OF_VIEW - MIN_FIELD_OF_VIEW) / FULL_ZOOM_PERIOD; + mZoomRate = zoom_rate * ZOOM_RATE_FACTOR; +} + + +void LLFlycam::setOrbitEngaged(bool engaged, F32 focal_distance) +{ + if (engaged && !mOrbitEngaged) + { + // Rising edge: fix the focal point 'focal_distance' meters in front of + // the camera's current forward axis. It stays put in world space for + // as long as orbit stays engaged, even as Pan/Truck/Tilt/Roll rotate + // the camera around it (see integrate()). + LLVector3 forward(LLMatrix3(mRotation).getFwdRow()); + mOrbitFocalPoint = mPosition + LLVector3d(forward) * (F64)focal_distance; + } + mOrbitEngaged = engaged; +} + + +void LLFlycam::setOrbitRadialRate(F32 radial_rate) +{ + // Note: this math expects radial_rate to be in range [-1.0, 1.0]. The + // resulting rate is a FRACTION of the current distance-to-focal-point per + // second (not a fixed speed), so approach slows near the focal point and + // retreat speeds up far away from it. + constexpr F32 ORBIT_RADIAL_RATE_FACTOR = 1.0f; // fraction of distance/sec at full deflection; may be tuned later + mOrbitRadialRate = radial_rate * ORBIT_RADIAL_RATE_FACTOR; +} + + +void LLFlycam::startReset(const LLVector3d& target_position, const LLQuaternion& target_rotation, F32 duration) +{ + mResetStartPosition = mPosition; + mResetStartRotation = mRotation; + mResetTargetPosition = target_position; + mResetTargetRotation = target_rotation; + mResetDuration = std::max(duration, 0.001f); + mResetTimeRemaining = mResetDuration; + + // Drop any live rates so leftover input doesn't fight the lerp, or get + // applied unexpectedly the moment the reset completes. + mLinearVelocity.clear(); + mPitchRate = mYawRate = mRollRate = mZoomRate = 0.0f; +} + + +void LLFlycam::integrate(F32 delta_time) +{ + // cap delta_time to slow camera motion when framerates are low + constexpr F32 MAX_DELTA_TIME = 0.2f; + if (delta_time > MAX_DELTA_TIME) + { + delta_time = MAX_DELTA_TIME; + } + + if (mResetTimeRemaining > 0.0f) + { + // Reset in progress: lerp toward the target transform and ignore + // whatever rates setLinearVelocity()/setPitchRate()/etc. set this + // frame -- flycam input has no effect until the lerp completes. + mResetTimeRemaining = std::max(mResetTimeRemaining - delta_time, 0.0f); + F32 fraction = 1.0f - (mResetTimeRemaining / mResetDuration); + mPosition = lerp(mResetStartPosition, mResetTargetPosition, fraction); + mRotation = nlerp(fraction, mResetStartRotation, mResetTargetRotation); + return; + } + + // Note: we modulate pitch and yaw rates by view ratio + // to make pitch and yaw work better when zoomed in close + F32 angle = delta_time * mPitchRate * (mView / DEFAULT_FIELD_OF_VIEW); + bool needs_renormalization = false; + if (fabsf(angle) > 0.0f) + { + LLQuaternion dQ; + dQ.setAngleAxis(angle, 0.0f, 1.0f, 0.0f); + mRotation = dQ * mRotation; + needs_renormalization = true; + } + + // Roll about the camera's forward (local X) axis. Like pitch this is a + // body-frame rotation, so it pre-multiplies mRotation. + angle = delta_time * mRollRate * (mView / DEFAULT_FIELD_OF_VIEW); + if (fabsf(angle) > 0.0f) + { + LLQuaternion dQ; + dQ.setAngleAxis(angle, 1.0f, 0.0f, 0.0f); + mRotation = dQ * mRotation; + needs_renormalization = true; + } + + angle = delta_time * mYawRate * (mView / DEFAULT_FIELD_OF_VIEW); + if (fabsf(angle) > 0.0f) + { + LLQuaternion dQ; + dQ.setAngleAxis(angle, 0.0f, 0.0f, 1.0f); + if (mAllowRoll) + { + mRotation = dQ * mRotation; + } + else + { + mRotation = dQ * mRotation; + + // Un-roll: keep the just-applied forward axis, but rebuild "up" to + // lie in the plane spanned by forward and world-up, which removes + // any roll the pitch+yaw quaternion multiplications above may have + // introduced (they don't commute). + LLMatrix3 level(mRotation); + LLVector3 forward(level.getFwdRow()); + forward.normVec(); + + LLVector3 world_up = LLVector3::z_axis; + LLVector3 new_up = world_up - (world_up * forward) * forward; + constexpr F32 MIN_UNROLL_SIN = 0.01f; // ~0.6 degrees off vertical + if (new_up.lengthSquared() > MIN_UNROLL_SIN * MIN_UNROLL_SIN) + { + new_up.normVec(); + // Right-handed (forward, left, up) basis, per + // LLMatrix3::orthogonalize()'s up = forward % left: up % forward == left. + LLVector3 new_left = new_up % forward; + level.setRows(forward, new_left, new_up); + mRotation = LLQuaternion(level); + } + } + needs_renormalization = true; + } + + if (mOrbitEngaged) + { + // Re-derive position from the just-rotated forward axis, so the + // camera stays (radially adjusted) 'distance' meters from the fixed + // focal point, always facing it. Pan/Truck (yaw, above) and Tilt + // (pitch, above) therefore sweep/swing the camera around the focal + // point as a side effect of simply rotating the camera; only the + // radius itself is orbit-specific here. + constexpr F32 MIN_ORBIT_DISTANCE = 0.1f; // meters + F32 distance = (F32)(mPosition - mOrbitFocalPoint).length(); + distance = std::max(distance * (1.0f + delta_time * mOrbitRadialRate), MIN_ORBIT_DISTANCE); + // 'forward' points from the camera toward the focal point, so the + // camera itself sits behind the focal point along that axis. + LLVector3 forward(LLMatrix3(mRotation).getFwdRow()); + mPosition = mOrbitFocalPoint - LLVector3d(forward) * (F64)distance; + } + + if (mLinearVelocity.lengthSquared() > 0.0f) + { + // Boom (local Z) still translates the camera directly here, on top of + // the orbit reposition above, regardless of mOrbitEngaged -- Truck/ + // Dolly's contributions are zeroed out by LLAgentCamera::updateFlycam() + // while orbiting, since they're redirected into yaw/radial rate instead. + LLVector3d translation((delta_time * mLinearVelocity) * mRotation); + mPosition += translation; + if (mOrbitEngaged) + { + // Boom doesn't reorient the camera or change its distance from + // the focal point -- drag the focal point along with it instead, + // so the next frame's orbit reposition still measures 'distance' + // from wherever boom just moved it to. + mOrbitFocalPoint += translation; + } + } + + if (mZoomRate != 0.0f) + { + // Note: we subtract the delta because "positive" zoom (e.g. "zoom in") + // produces smaller view angle + mView = std::clamp(mView - delta_time * mZoomRate, MIN_FIELD_OF_VIEW, MAX_FIELD_OF_VIEW); + } + + if (needs_renormalization) + { + mRotation.normalize(); + } +} + + +void LLFlycam::applyFrameDelta(const F32 local_delta[7], + bool auto_level, F32 auto_level_fraction, + bool direct_view, F32 direct_view_value) +{ + mPosition += LLVector3d(local_delta[VX], local_delta[VY], local_delta[VZ]) * mRotation; + + LLMatrix3 rot_mat(local_delta[3], local_delta[4], local_delta[5]); + mRotation = LLQuaternion(rot_mat) * mRotation; + + if (auto_level) + { + LLMatrix3 level(mRotation); + + LLVector3 x = LLVector3(level.mMatrix[0]); + LLVector3 y = LLVector3(level.mMatrix[1]); + LLVector3 z = LLVector3(level.mMatrix[2]); + + y.mV[2] = 0.f; + y.normVec(); + + level.setRows(x, y, z); + level.orthogonalize(); + + LLQuaternion quat(level); + mRotation = nlerp(auto_level_fraction, mRotation, quat); + } + + if (direct_view) + { + setView(direct_view_value); + } + else + { + setView(mView + local_delta[6]); + } +} diff --git a/indra/newview/llflycam.h b/indra/newview/llflycam.h new file mode 100644 index 00000000000..7254a6955f6 --- /dev/null +++ b/indra/newview/llflycam.h @@ -0,0 +1,139 @@ +/** + * @file llflycam.h + * @brief LLFlycam class header file + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "llcamera.h" +#include "llquaternion.h" +#include "v3math.h" +#include "v3dmath.h" + +constexpr F32 MIN_FLYCAM_SPEED_FACTOR = 0.5f; +constexpr F32 MAX_FLYCAM_SPEED_FACTOR = 2.0f; +constexpr F32 DEFAULT_FLYCAM_SPEED_FACTOR = 1.0f; + +class LLFlycam +{ +public: + + LLFlycam() = default; + + // Note: position is in global (region-independent) coordinates, since it + // must remain valid across the region-origin rebases that happen when the + // agent crosses a region boundary while this transform is in use. + void setTransform(const LLVector3d& position, const LLQuaternion& rotation); + void getTransform(LLVector3d& position_out, LLQuaternion& rotation_out); + + void setView(F32 view); + F32 getView() const { return mView; } + + void setLinearVelocity(const LLVector3& velocity); + void setPitchRate(F32 pitch_rate); + void setYawRate(F32 yaw_rate); + void setRollRate(F32 roll_rate); + void setZoomRate(F32 zoom_rate); + + // Whether Roll input is allowed to rotate the flycam about its forward + // axis; when false, setRollRate() discards its input and the flycam + // never rolls. Off by default; driven by the "GameControl" FlyCam + // settings' AllowRoll flag (see LLGameControl::isFlycamRollAllowed()). + void setAllowRoll(bool allow) { mAllowRoll = allow; } + bool getAllowRoll() const { return mAllowRoll; } + + // Multiplier applied to the Pitch/Yaw/Roll RATE_FACTOR coefficients in + // setPitchRate()/setYawRate()/setRollRate(), and to the Truck/Dolly/Boom + // velocity passed to setLinearVelocity() (e.g. "Slow" .. "Fast"). Clamped + // to [MIN_FLYCAM_SPEED_FACTOR, MAX_FLYCAM_SPEED_FACTOR]; defaults to + // DEFAULT_FLYCAM_SPEED_FACTOR. Driven by the "GameControl" FlyCam settings' + // SpeedFactor value (see LLGameControl::getFlycamSpeedFactor()). + void setSpeedFactor(F32 speed_factor); + F32 getSpeedFactor() const { return mSpeedFactor; } + + // Orbit modifier: while engaged, position is re-derived every integrate() + // from the camera's own (pitch/yaw-rotated) forward axis so the camera + // stays 'focal_distance' meters from a focal point fixed in front of it + // the moment orbit engages -- Pan/Truck/Tilt/Roll keep rotating the + // camera exactly as they do outside orbit (see integrate()); only the + // radial (dolly) rate is orbit-specific. Boom still translates the camera + // directly (see integrate()), but doesn't reorient it or change its + // distance from the focal point: the same translation is applied to the + // focal point too, dragging it along with the camera. + void setOrbitEngaged(bool engaged, F32 focal_distance); + bool isOrbitEngaged() const { return mOrbitEngaged; } + void setOrbitRadialRate(F32 radial_rate); + + // Begins a smooth transition from the current transform to + // 'target_position'/'target_rotation', taking 'duration' seconds. While + // the transition is in progress, integrate() ignores the rates set by + // setLinearVelocity()/setPitchRate()/etc. and instead lerps toward the + // target; normal input-driven integration resumes once it completes. + void startReset(const LLVector3d& target_position, const LLQuaternion& target_rotation, F32 duration); + bool isResetting() const { return mResetTimeRemaining > 0.0f; } + + void integrate(F32 delta_time); + + // Applies one frame of already-tuned deltas directly to the transform, + // bypassing the rate-based setPitchRate()/etc. + integrate() path. Used + // by input sources (the legacy NDOF joystick) that compute their own + // per-axis dead-zone/scale/feathering and only need this class to own + // the resulting transform update, so their feel is preserved exactly. + // 'local_delta' holds, in order: [0..2] local-frame translation delta + // (x,y,z); [3..5] rotation delta (roll,pitch,yaw, as fed to + // LLMatrix3(r,p,y)); [6] zoom delta added to the view angle (ignored if + // 'direct_view' is set, in which case 'direct_view_value' replaces the + // view angle outright). + void applyFrameDelta(const F32 local_delta[7], + bool auto_level, F32 auto_level_fraction, + bool direct_view, F32 direct_view_value); + +protected: + LLVector3d mPosition; + LLVector3 mLinearVelocity; + LLQuaternion mRotation; + F32 mPitchRate { 0.0f }; + F32 mYawRate { 0.0f }; + F32 mRollRate { 0.0f }; + F32 mZoomRate { 0.0f }; + F32 mView { DEFAULT_FIELD_OF_VIEW }; + bool mAllowRoll { false }; + F32 mSpeedFactor { DEFAULT_FLYCAM_SPEED_FACTOR }; + + // Orbit-in-progress state: see setOrbitEngaged()/setOrbitRadialRate() and + // the orbit branch in integrate(). + bool mOrbitEngaged { false }; + LLVector3d mOrbitFocalPoint; + F32 mOrbitRadialRate { 0.0f }; + + // Reset-in-progress state: integrate() lerps mPosition/mRotation from + // mResetStart* to mResetTarget* as mResetTimeRemaining counts down from + // mResetDuration to zero. + LLVector3d mResetStartPosition; + LLQuaternion mResetStartRotation; + LLVector3d mResetTargetPosition; + LLQuaternion mResetTargetRotation; + F32 mResetDuration { 0.0f }; + F32 mResetTimeRemaining { 0.0f }; +}; diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp index c082c632f14..edd5f5e20c8 100644 --- a/indra/newview/llkeyconflict.cpp +++ b/indra/newview/llkeyconflict.cpp @@ -34,6 +34,7 @@ #include "llkeyconflict.h" +#include "llgamecontrol.h" #include "llinitparam.h" #include "llkeyboard.h" #include "lltrans.h" @@ -99,29 +100,34 @@ LLKeyConflictHandler::~LLKeyConflictHandler() // Note: does not reset bindings if temporary file was used } -bool LLKeyConflictHandler::canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask) +bool LLKeyConflictHandler::canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask) const { - return mControlsMap[control_name].canHandle(mouse_ind, key, mask); + control_map_t::const_iterator iter = mControlsMap.find(control_name); + if (iter != mControlsMap.end()) + { + return iter->second.canHandle(mouse_ind, key, mask); + } + return false; } -bool LLKeyConflictHandler::canHandleKey(const std::string &control_name, KEY key, MASK mask) +bool LLKeyConflictHandler::canHandleKey(const std::string &control_name, KEY key, MASK mask) const { return canHandleControl(control_name, CLICK_NONE, key, mask); } -bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask) +bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask) const { return canHandleControl(control_name, mouse_ind, KEY_NONE, mask); } -bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask) +bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask) const { return canHandleControl(control_name, (EMouseClickType)mouse_ind, KEY_NONE, mask); } -bool LLKeyConflictHandler::canAssignControl(const std::string &control_name) +bool LLKeyConflictHandler::canAssignControl(const std::string &control_name) const { - control_map_t::iterator iter = mControlsMap.find(control_name); + control_map_t::const_iterator iter = mControlsMap.find(control_name); if (iter != mControlsMap.end()) { return iter->second.mAssignable; @@ -238,6 +244,8 @@ std::string LLKeyConflictHandler::getStringFromKeyData(const LLKeyData& keydata) result += LLKeyboard::stringFromMouse(keydata.mMouse); + result += LLGameControl::controllerInputStringFromAction((LLGameControl::ActionType)keydata.mControllerActionType, keydata.mControllerAction); + return result; } @@ -284,12 +292,25 @@ void LLKeyConflictHandler::loadFromSettings(const LLViewerInput::KeyMode& keymod LLKeyboard::keyFromString(it->key, &key); } LLKeyboard::maskFromString(it->mask, &mask); - // Note: it->command is also the name of UI element, howhever xml we are loading from + // Note: it->command is also the name of UI element, however xml we are loading from // might not know all the commands, so UI will have to know what to fill by its own // Assumes U32_MAX conflict mask, and is assignable by default, // but assignability might have been overriden by generatePlaceholders. LLKeyConflict &type_data = (*destination)[it->command]; - type_data.mKeyBind.addKeyData(mouse, key, mask, true); + + if (it->controller.isProvided() && !it->controller.getValue().empty()) + { + LLGameControl::ActionType actionType = LLGameControl::ActionType::NONE; + U8 action = 0; + if(LLGameControl::actionFromString(it->controller.getValue(),actionType, action)) + { + type_data.mKeyBind.addKeyData(LLKeyData((U8)actionType, action)); + } + } + else + { + type_data.mKeyBind.addKeyData(mouse, key, mask, true); + } } } @@ -338,6 +359,13 @@ bool LLKeyConflictHandler::loadFromSettings(const ESourceMode &load_mode, const res = true; } break; + case MODE_FLYCAM: + if (keys.flycam.isProvided()) + { + loadFromSettings(keys.flycam, destination); + res = true; + } + break; default: LL_ERRS() << "Not implememted mode " << load_mode << LL_ENDL; break; @@ -513,6 +541,14 @@ void LLKeyConflictHandler::saveToSettings(bool temporary) // just copy old keys.xml and rename to key_bindings.xml, it should work binding.mouse.set(LLKeyboard::stringFromMouse(data.mMouse, false), true); } + if(data.mControllerActionType > LLGameControl::ActionType::BUTTON) + { + binding.controller.setProvided(false); + } + else + { + binding.controller.set(LLGameControl::stringFromAction((LLGameControl::ActionType)(data.mControllerActionType), data.mControllerAction)); + } binding.command = iter->first; mode.bindings.add(binding); } @@ -544,6 +580,14 @@ void LLKeyConflictHandler::saveToSettings(bool temporary) keys.sitting.bindings.set(mode.bindings, true); } break; + case MODE_FLYCAM: + // Unlike the long-established modes above, 'flycam' is a new + // block: a key_bindings.xml written before this mode existed + // has no tag at all, so isProvided() would be false + // forever and this mode's bindings would never get written + // back out. Therefore set it unconditionally. + keys.flycam.bindings.set(mode.bindings, true); + break; default: LL_ERRS() << "Not implememted mode " << mLoadMode << LL_ENDL; break; diff --git a/indra/newview/llkeyconflict.h b/indra/newview/llkeyconflict.h index 6c01ddb7a79..c8135444222 100644 --- a/indra/newview/llkeyconflict.h +++ b/indra/newview/llkeyconflict.h @@ -44,7 +44,7 @@ class LLKeyConflict LLKeyData getKeyData(U32 index) { return mKeyBind.getKeyData(index); } void setPrimaryKeyData(const LLKeyData& data) { mKeyBind.replaceKeyData(data, 0); } void setKeyData(const LLKeyData& data, U32 index) { mKeyBind.replaceKeyData(data, index); } - bool canHandle(EMouseClickType mouse, KEY key, MASK mask) { return mKeyBind.canHandle(mouse, key, mask); } + bool canHandle(EMouseClickType mouse, KEY key, MASK mask) const { return mKeyBind.canHandle(mouse, key, mask); } LLKeyBind mKeyBind; bool mAssignable; // whether user can change key or key simply acts as placeholder @@ -61,6 +61,7 @@ class LLKeyConflictHandler MODE_THIRD_PERSON, MODE_EDIT_AVATAR, MODE_SITTING, + MODE_FLYCAM, MODE_SAVED_SETTINGS, // for settings from saved settings MODE_COUNT }; @@ -76,11 +77,11 @@ class LLKeyConflictHandler LLKeyConflictHandler(ESourceMode mode); ~LLKeyConflictHandler(); - bool canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask); - bool canHandleKey(const std::string &control_name, KEY key, MASK mask); - bool canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask); - bool canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask); //Just for convinience - bool canAssignControl(const std::string &control_name); + bool canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask) const; + bool canHandleKey(const std::string &control_name, KEY key, MASK mask) const; + bool canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask) const; + bool canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask) const; //Just for convenience + bool canAssignControl(const std::string &control_name) const; static bool isReservedByMenu(const KEY &key, const MASK &mask); static bool isReservedByMenu(const LLKeyData &data); diff --git a/indra/newview/llpanelpreferencegamecontrol.cpp b/indra/newview/llpanelpreferencegamecontrol.cpp new file mode 100755 index 00000000000..068aa87e749 --- /dev/null +++ b/indra/newview/llpanelpreferencegamecontrol.cpp @@ -0,0 +1,2138 @@ +/** + * @file llfloaterpreference.cpp + * @brief Global preferences with and without persistence. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * App-wide preferences. Note that these are not per-user, + * because we need to load many preferences before we have + * a login name. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelpreferencegamecontrol.h" + +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llsliderctrl.h" +#include "lltabcontainer.h" +#include "llspinctrl.h" +#include "lltextbox.h" + + +namespace +{ + // Singleton instance pointer - only one game control panel exists at a time + static LLPanelPreferenceGameControl* sGameControlPanel { nullptr }; + + // Track current UI selection state for input/output channel assignment. + // When user clicks a cell to change its mapping, these track which cell is being edited. + static LLScrollListCtrl* sSelectedGrid { nullptr }; + static LLScrollListItem* sSelectedItem { nullptr }; + static LLScrollListCell* sSelectedCell { nullptr }; + static S32 sSelectedRow { -1 }; + static S32 sSelectedCol { -1 }; + static LLRect sSelectedOpenRect; + + // The combobox popup currently overlaying sSelectedCell (one of the four + // action-mapping/output selectors below). Stashed so applyGameControlInput() + // can drive it (and reuse onCommitInputChannel()) without having to re-derive + // which selector initCombobox() chose. + static LLComboBox* sSelectedCombobox { nullptr }; + + // The selector combobox overlays the edited cell, but its drop-down button + // texture is semi-transparent, so the cell's own text shows through and + // "doubles" with the selector's label. While a selector is open we blank + // the cell's text and stash its value here, restoring it when the selector + // closes (see initCombobox / clearSelectionState). + static LLSD sSelectedCellValue; + + // Selector items use the label "None" for the unmapped entry. + // Table cells should render that as a blank cell rather than the word. + const std::string NONE_LABEL("None"); + + // Kinds of action mapping, used both as the LLGameControl mapping key and as + // the block discriminator stored in each action-table row's value. + const std::string MODE_INPUT_TYPE_AXES("Axes"); + const std::string MODE_INPUT_TYPE_BUTTONS("Buttons"); + const std::string MODE_INPUT_TYPE_AXES_INVERT("AxesInvert"); + + LLSD blankIfNone(const LLSD& label) + { + return label.asString() == NONE_LABEL ? LLSD() : label; + } + + // SecondLife-game-control glyph(s) for a canonical controller input value. + // Codepoints are from the SecondLife-game-control package's promptfont.h, + // preferring the generic vendor-neutral "gamepad"/"dpad"/"analog" sets and + // falling back to the Xbox set for shoulders/triggers (no generic equivalents + // exist). Rendered with the "SecondLife-game-control" family declared in + // fonts.xml. Inputs with no entry (e.g. BUTTON_21..31, *_NONE) return empty + // and keep their text label. + std::string promptFontGlyph(const std::string& input_value) + { + static const std::map sGlyphs = { + // Face buttons + { "BUTTON_SOUTH", { 0x21A7 } }, // PF_GAMEPAD_A (bottom) + { "BUTTON_EAST", { 0x21A6 } }, // PF_GAMEPAD_B (right) + { "BUTTON_WEST", { 0x21A4 } }, // PF_GAMEPAD_X (left) + { "BUTTON_NORTH", { 0x21A5 } }, // PF_GAMEPAD_Y (top) + // D-pad + { "BUTTON_DPAD_UP", { 0x219F } }, // PF_DPAD_UP + { "BUTTON_DPAD_DOWN", { 0x21A1 } }, // PF_DPAD_DOWN + { "BUTTON_DPAD_LEFT", { 0x219E } }, // PF_DPAD_LEFT + { "BUTTON_DPAD_RIGHT", { 0x21A0 } }, // PF_DPAD_RIGHT + // Shoulders / stick clicks + { "BUTTON_LEFT_SHOULDER", { 0x2198 } }, // PF_XBOX_LEFT_SHOULDER + { "BUTTON_RIGHT_SHOULDER", { 0x2199 } }, // PF_XBOX_RIGHT_SHOULDER + { "BUTTON_LEFT_STICK", { 0x21BA } }, // PF_ANALOG_L_CLICK (L3) + { "BUTTON_RIGHT_STICK", { 0x21BB } }, // PF_ANALOG_R_CLICK (R3) + // Menu cluster + { "BUTTON_SELECT", { 0x21F7 } }, // PF_GAMEPAD_SELECT (menu left) + { "BUTTON_HOME", { 0x21F9 } }, // PF_GAMEPAD_HOME (menu middle) + { "BUTTON_START", { 0x21F8 } }, // PF_GAMEPAD_START (menu right) + // Misc / paddles / touchpad + { "BUTTON_15", { 0x2212 } }, // PF_GAMEPAD_M1 (Misc1) + { "BUTTON_PADDLE1", { 0x2277 } }, // PF_GAMEPAD_R4 + { "BUTTON_PADDLE2", { 0x2276 } }, // PF_GAMEPAD_L4 + { "BUTTON_PADDLE3", { 0x2279 } }, // PF_GAMEPAD_R5 + { "BUTTON_PADDLE4", { 0x2278 } }, // PF_GAMEPAD_L5 + { "BUTTON_TOUCHPAD", { 0x21E7 } }, // PF_SONY_TOUCHPAD + // Axes + { "AXIS_LEFTX", { 0x21C4 } }, // PF_ANALOG_L_LEFT_RIGHT + { "AXIS_LEFTY", { 0x21C5 } }, // PF_ANALOG_L_UP_DOWN + { "AXIS_RIGHTX", { 0x21C6 } }, // PF_ANALOG_R_LEFT_RIGHT + { "AXIS_RIGHTY", { 0x21F5 } }, // PF_ANALOG_R_UP_DOWN + { "AXIS_LEFT_TRIGGER", { 0x2196 } }, // PF_XBOX_LEFT_TRIGGER + { "AXIS_RIGHT_TRIGGER", { 0x2197 } }, // PF_XBOX_RIGHT_TRIGGER + // Combined trigger pair: show both trigger glyphs side by side. + { "AXIS_TRIGGERS", { 0x2196, 0x2197 } }, + }; + auto it = sGlyphs.find(input_value); + if (it == sGlyphs.end()) + { + return LLStringUtil::null; + } + return wstring_to_utf8str(it->second); + } + + // The SecondLife-game-control glyphs live on real Unicode arrow codepoints, + // so they must be drawn with the dedicated SecondLife-game-control family + // rather than the default text font. Sized larger than the SansSerif rows + // so the icons read clearly. + const LLFontGL* promptFontForCell() + { + return LLFontGL::getFont(LLFontDescriptor("SecondLife-game-control", "Huge", 0)); + } +} + + static LLRect computeCellScreenRect(LLScrollListCtrl* grid, LLView* relative_to, S32 row_index, S32 col_index); + +// Static entry point called when device list changes (device connected/disconnected). +// Delegates to the singleton instance if it exists. +void LLPanelPreferenceGameControl::updateDeviceList() +{ + if (sGameControlPanel) + { + sGameControlPanel->updateDeviceListInternal(); + } +} + +// Constructor - registers this instance as the singleton +LLPanelPreferenceGameControl::LLPanelPreferenceGameControl() +{ + sGameControlPanel = this; +} + +// Destructor - clears singleton pointer +LLPanelPreferenceGameControl::~LLPanelPreferenceGameControl() +{ + sGameControlPanel = nullptr; +} + +static LLPanelInjector t_pref_game_control("panel_preference_game_control"); + +// Snapshot the panel's UI state as the single "GameControl" setting. +// The returned map can be persisted (saveSettings) or applied directly to +// LLGameControl runtime state via LLGameControl::applySettingsFromLLSD(). +LLSD LLPanelPreferenceGameControl::getSettingsAsLLSD() +{ + // Fold each device's current options into GameControl/Devices//Config. + // The global per-mode action mappings are already live in LLGameControl (edited + // via its accessors), so snapshotting the whole GameControl structure captures both. + for (auto& [guid, device] : mDeviceOptions) + { + device.settings = device.options.saveToString(device.name); + LLGameControl::setDeviceConfig(guid, device.settings); + } + + LLSD result = LLSD::emptyMap(); + result["GameControl"] = LLGameControl::getGameControlSettings(); + return result; +} + +// Saves current UI state to settings by pushing the "GameControl" snapshot +// (global action mappings + per-device Config) into gSavedSettings. +void LLPanelPreferenceGameControl::saveSettings() +{ + LLPanelPreference::saveSettings(); + + if (mOrigSettings.isEmpty()) + { + rememberOriginalSettings(); + } + + // Push every entry of the snapshot into gSavedSettings, also tracking + // the new value in mSavedValues so the base panel's accounting stays in sync. + LLSD settings = getSettingsAsLLSD(); + for (auto it = settings.beginMap(); it != settings.endMap(); ++it) + { + if (LLControlVariable* control = gSavedSettings.getControl(it->first)) + { + control->set(it->second); + mSavedValues[control] = control->getValue(); + } + } +} + +// Handles row selection in the Actions table. If the user clicked the editable +// Input column of a real (non-spacer) row, shows the appropriate input selector. +void LLPanelPreferenceGameControl::onGridSelect(LLUICtrl* ctrl) +{ + clearSelectionState(); + + LLScrollListCtrl* table = dynamic_cast(ctrl); + if (!table || !table->getEnabled()) + return; + + // Deselect everything in the other scroll_list + LLScrollListCtrl* sibling = (table == mActionMappingsAxes) ? mActionMappingsButtons : mActionMappingsAxes; + sibling->deselectAllItems(true); + + if (LLScrollListItem* item = table->getFirstSelected()) + { + if (table == mActionMappingsAxes) + { + // Always sync the Invert checkbox -- clicking it selects the row (firing + // this same commit callback) but doesn't itself persist the new value. + // Only meaningful when the row has a real axis mapped (the checkbox is + // disabled and inert otherwise, matching populateActionMappings()). + std::string mode = currentEditMode(); + LLSD row_value = item->getValue(); + std::string action = row_value["action"].asString(); + LLSD mapping = LLGameControl::getModeMapping(mode, MODE_INPUT_TYPE_AXES); + std::string input_value = mapping.has(action) ? mapping[action].asString() : LLStringUtil::null; + std::string none_value = mAxisInputSelector->getAllData().back()->getValue().asString(); + if (!input_value.empty() && input_value != none_value) + { + S32 invert_col = mActionMappingsAxes->getColumn("invert")->mIndex; + LLGameControl::setAxisInvert(mode, action, item->getColumn(invert_col)->getValue().asBoolean()); + } + } + + // Try to show combobox for editing; if not applicable, deselect + if (initCombobox(item, table)) + return; + + table->deselectAllItems(); + } +} + +// Initializes and displays the appropriate combobox over the selected cell. +// Chooses the combobox and editable column from the table and (for the Actions +// table) the row's block input_type. Returns true if a combobox was shown, false if the +// click wasn't on an editable cell. +bool LLPanelPreferenceGameControl::initCombobox(LLScrollListItem* item, LLScrollListCtrl* grid) +{ + LLComboBox* combobox = nullptr; + S32 col = -1; + + if (grid == mActionMappingsAxes || grid == mActionMappingsButtons) + { + // Two editable columns map to the same input: the SecondLife-game-control + // icon column (1) opens the glyph selector, the text description column + // (2) opens the text selector. Both commit through onCommitInputChannel, + // which rebuilds the row so the icon and description update together. + LLSD row_value = item->getValue(); + if (!row_value.isMap()) + return false; + bool axes = (grid == mActionMappingsAxes); + S32 sel = item->getSelectedCell(); + if (sel == 1) + { + combobox = axes ? mAxisInputGlyphSelector : mButtonInputGlyphSelector; + } + else if (sel == 2) + { + combobox = axes ? mAxisInputSelector : mButtonInputSelector; + } + else + { + return false; + } + col = sel; + } + else if (grid == mAxisChannels) + { + // Two editable columns map to the same output: the SecondLife-game-control + // icon column ("output") opens the glyph selector, the text column + // ("output_description") opens the text selector. Both commit through + // onCommitInputChannel, which refreshes both cells so the icon and + // description stay in sync. The Input columns are the fixed physical axis + // and are not editable. + S32 sel = item->getSelectedCell(); + S32 desc_col = grid->getColumn("output_description")->mIndex; + S32 glyph_col = grid->getColumn("output")->mIndex; + if (sel == glyph_col) + { + combobox = mAxisOutputGlyphSelector; + } + else if (sel == desc_col) + { + combobox = mAxisOutputSelector; + } + else + { + return false; + } + col = sel; + } + else if (grid == mButtonChannels) + { + // Two editable columns map to the same output: the SecondLife-game-control + // icon column ("output") opens the glyph selector, the text column + // ("output_description") opens the text selector. Both commit through + // onCommitInputChannel, which refreshes both cells so the icon and + // description stay in sync. The Input columns are the fixed physical + // button and are not editable. + S32 sel = item->getSelectedCell(); + S32 desc_col = grid->getColumn("output_description")->mIndex; + S32 glyph_col = grid->getColumn("output")->mIndex; + if (sel == glyph_col) + { + combobox = mButtonInputGlyphSelector; + } + else if (sel == desc_col) + { + combobox = mButtonInputSelector; + } + else + { + return false; + } + col = sel; + } + + if (!combobox) + return false; + + LLScrollListText* cell = dynamic_cast(item->getColumn(col)); + if (!cell) + return false; + + // compute new rect for combobox + S32 row_index = grid->getItemIndex(item); + fitInRect(combobox, grid, row_index, col); + sSelectedRow = row_index; + sSelectedCol = col; + sSelectedOpenRect = computeCellScreenRect(grid, combobox->getParent(), row_index, col); + + + // Pre-select the item matching the cell's current input. + std::string value; + if (grid == mActionMappingsAxes || grid == mActionMappingsButtons) + { + // Use the action's stored mapping value directly, so pre-selection is + // correct even when the icon cell is blank (input has no glyph). + LLSD row_value = item->getValue(); + LLSD mapping = LLGameControl::getModeMapping(currentEditMode(), row_value["input_type"].asString()); + std::string action = row_value["action"].asString(); + value = mapping.has(action) ? mapping[action].asString() : LLStringUtil::null; + } + else + { + // Device tables: match the cell's current text against the selector items + // (works regardless of the value naming scheme). + std::string cell_value = cell->getValue(); + for (const LLScrollListItem* combo_item : combobox->getAllData()) + { + if (combo_item->getColumn(0)->getValue().asString() == cell_value) + { + value = combo_item->getValue().asString(); + break; + } + } + } + if (value.empty()) + { + // No match (e.g. a blank "None" cell): select the last item, which is "None". + value = combobox->getAllData().back()->getValue().asString(); + } + + combobox->setValue(value); + combobox->setVisible(true); + combobox->showList(); + + sSelectedGrid = grid; + sSelectedItem = item; + sSelectedCell = cell; + sSelectedCombobox = combobox; + + // Hide the cell's text under the (translucent) selector so it doesn't + // double with the selector's label; clearSelectionState() restores it. + sSelectedCellValue = cell->getValue(); + cell->setValue(LLSD()); + + return true; +} + +// Called when the user selects a value from an input/output selector combobox. +// The originating grid determines the meaning: Actions table -> action mapping; +// axis/button channels table -> per-device physical-to-canonical remap. +void LLPanelPreferenceGameControl::onCommitInputChannel(LLUICtrl* ctrl) +{ + if (!sSelectedGrid || !sSelectedItem || !sSelectedCell) + return; + + LLComboBox* combobox = dynamic_cast(ctrl); + llassert(combobox); + if (!combobox) + return; + + // Editing an action mapping can change whether the input cell shows a text + // label or a SecondLife-game-control glyph, which means recreating the cell + // with the right font; remember to rebuild the action tables once the commit + // is applied. + bool action_grid_edited = (sSelectedGrid == mActionMappingsAxes || sSelectedGrid == mActionMappingsButtons); + // Editing an axis- or button-channels output likewise changes both its text and + // icon cells, so both are refreshed from the device map once the commit is applied. + bool axis_channels_edited = (sSelectedGrid == mAxisChannels); + bool button_channels_edited = (sSelectedGrid == mButtonChannels); + + if (sSelectedGrid == mActionMappingsAxes || sSelectedGrid == mActionMappingsButtons) + { + // The edited row identifies the action + block; the combobox supplies the input. + LLSD row_value = sSelectedItem->getValue(); + std::string mode = currentEditMode(); + std::string input_type = row_value["input_type"].asString(); + std::string action = row_value["action"].asString(); + const LLComboBox* input_selector = (input_type == MODE_INPUT_TYPE_AXES) ? mAxisInputSelector : mButtonInputSelector; + std::string input_value = combobox->getValue().asString(); + std::string input_label = combobox->getSelectedItemLabel(); + + if (input_label != NONE_LABEL) + { + // An input can drive at most one action within a block: clear it elsewhere. + removeDuplicateActionInput(mode, input_type, action, input_value, input_selector); + } + + // Store the mapping directly in LLGameControl's live GameControl settings. + // gSavedSettings is updated later via saveSettings() when the user clicks OK. + LLGameControl::updateModeMapping(mode, input_type, action, input_value); + sSelectedCell->setValue(blankIfNone(input_label)); + } + else if (sSelectedGrid == mAxisChannels) + { + // The row is a physical axis; the selected item's value names the output it + // maps to: an individual canonical axis, "Triggers left/right" (the fan-out + // pair), or "None". Output codes are resolved by LLGameControl so the panel + // stays agnostic of the axis-map encoding. + S32 axis = mAxisChannels->getItemIndex(sSelectedItem); + std::string output_name = combobox->getValue().asString(); + LLGameControl::Options& options = getSelectedDeviceOptions(); + options.getAxisMap()[axis] = LLGameControl::axisOutputFromName(output_name); + LLGameControl::setDeviceOptions(mSelectedDeviceGUID, options); + // Both output cells (icon + description) are re-rendered from the actual + // map by populateAxisChannelsCells() below. + } + else if (sSelectedGrid == mButtonChannels) + { + // The row is a physical button; the selected item's index is the canonical + // button it maps to. "None" (index >= NUM_BUTTONS) is not a valid output. + S32 button = mButtonChannels->getItemIndex(sSelectedItem); + S32 output = combobox->getCurrentIndex(); + LLGameControl::Options& options = getSelectedDeviceOptions(); + if (output >= 0 && output < (S32)LLGameControl::NUM_BUTTONS) + { + options.getButtonMap()[button] = (U8)output; + LLGameControl::setDeviceOptions(mSelectedDeviceGUID, options); + } + // Both output cells (icon + description) are re-rendered from the actual + // map by populateButtonChannelsCells() below. + } + + // The branches above set the cell to its committed value; keep the + // restore-on-close value in sync so clearSelectionState() doesn't revert + // the cell back to its pre-edit text. + sSelectedCellValue = sSelectedCell->getValue(); + + sSelectedGrid->deselectAllItems(); + clearSelectionState(); + + // Rebuild the action tables so a newly bound (or unbound) button action is + // re-rendered with the correct cell font -- text label vs SecondLife-game-control + // glyph. + if (action_grid_edited) + { + populateActionMappings(); + } + + // Refresh the axis-/button-channels output cells so the committed output's text + // label and SecondLife-game-control glyph are both re-rendered from the device map. + if (axis_channels_edited) + { + populateAxisChannelsCells(); + } + if (button_channels_edited) + { + populateButtonChannelsCells(); + } +} + +// Returns true if a cell is currently selected and waiting for input channel assignment. +// Used to determine whether to capture live controller input. +bool LLPanelPreferenceGameControl::isWaitingForInputChannel() +{ + return sSelectedCell != nullptr; +} + +// Static method called in the mainloop when an Action Mappings input selector is open. +// Refreshes the canonical controller state, and if the user is pressing a button or +// tilting an axis that matches the input_type of selector open (button vs. axis), assigns +// it to the selected cell as if the user had picked it from the dropdown. +void LLPanelPreferenceGameControl::applyGameControlInput() +{ + if (!sGameControlPanel || !sSelectedCombobox) + return; + + bool wants_axis = (sSelectedGrid == sGameControlPanel->mActionMappingsAxes); + bool wants_button = (sSelectedGrid == sGameControlPanel->mActionMappingsButtons); + if (!wants_axis && !wants_button) + return; + + // Recompute the canonical controller state from the live device input without + // triggering the normal "send to server" bookkeeping (see computeFinalStateAndCheckForChanges()), + // since input used to configure a mapping should not drive the avatar or the network. + LLGameControl::computeFinalState(); + + LLGameControl::InputChannel channel = LLGameControl::getActiveInputChannel(); + if (wants_axis && !channel.isAxis()) + return; + if (wants_button && !channel.isButton()) + return; + + std::string value = channel.getRemoteName(); + if (!sSelectedCombobox->setSelectedByValue(value, true)) + { + // The action-mapping axis selector has no entries for the individual + // trigger axes: it collapses them into one virtual "Triggers" item + // (see AXIS_TRIGGERS in panel_preferences_game_control.xml). + if (value != "AXIS_LEFT_TRIGGER" && value != "AXIS_RIGHT_TRIGGER") + return; + if (!sSelectedCombobox->setSelectedByValue(std::string("AXIS_TRIGGERS"), true)) + return; + } + + sGameControlPanel->onCommitInputChannel(sSelectedCombobox); +} + +// Handles selection in the axis-channels table (invert, deadzone, offset, output). +// Syncs the invert flag immediately; shows the numeric editor for deadzone/offset, +// or the axis selector popup for the Output column. +void LLPanelPreferenceGameControl::onAxisChannelsSelect() +{ + clearSelectionState(); + + // Deselect everything in the other scroll_list + mButtonChannels->deselectAllItems(true); + + if (LLScrollListItem* row = mAxisChannels->getFirstSelected()) + { + // Only the Output columns (icon + description) are editable; initCombobox + // ignores the fixed Input columns. Axis tuning (invert/offset/dead zone) + // now lives on the Device State tab. + initCombobox(row, mAxisChannels); + } +} + +// Handles selection in the (now editable) axis-state table on the Device State +// tab. Syncs the Invert checkbox immediately and shows the numeric editor for the +// Offset/Dead Zone columns. Edits target the state tab's selected device. +void LLPanelPreferenceGameControl::onAxisStateSelect() +{ + clearSelectionState(); + + // Deselect everything in the other scroll_list + mButtonState->deselectAllItems(true); + + auto options_it = mDeviceOptions.find(mStateSelectedDeviceGUID); + if (options_it == mDeviceOptions.end()) + { + return; + } + LLGameControl::Options& deviceOptions = options_it->second.options; + + if (LLScrollListItem* row = mAxisState->getFirstSelected()) + { + S32 row_index = mAxisState->getItemIndex(row); + + { + // Always sync invert checkbox - clicking the checkbox selects the row + // but doesn't automatically update the underlying option. + S32 invert_checkbox_column = axisStateColumn("invert"); + bool invert = row->getColumn(invert_checkbox_column)->getValue().asBoolean(); + S32 multiplier = invert ? -1 : 1; + if (multiplier != deviceOptions.getAxisOptions()[row_index].mMultiplier) + { + deviceOptions.getAxisOptions()[row_index].mMultiplier = multiplier; + + if (row_index == LLGameControl::AXIS_LEFT_TRIGGER + || row_index == LLGameControl::AXIS_RIGHT_TRIGGER) + { + // The two trigger axes act as one bidirectional axis under the hood, + // so their inversion must stay in sync -- update the sibling axis's + // option and reflect the new state in its checkbox cell. + S32 other_row_index = row_index == LLGameControl::AXIS_LEFT_TRIGGER + ? LLGameControl::AXIS_RIGHT_TRIGGER + : LLGameControl::AXIS_LEFT_TRIGGER; + deviceOptions.getAxisOptions()[other_row_index].mMultiplier = multiplier; + + if (LLScrollListItem* other_row = mAxisState->getItemByIndex(other_row_index)) + { + other_row->getColumn(invert_checkbox_column)->setValue(invert); + } + } + + LLGameControl::setDeviceOptions(mStateSelectedDeviceGUID, deviceOptions); + } + } + + S32 offset_col = axisStateColumn("offset"); + S32 dead_zone_col = axisStateColumn("dead_zone"); + S32 column_index = row->getSelectedCell(); + if (column_index == offset_col || column_index == dead_zone_col) + { + fitInRect(mNumericValueEditor, mAxisState, row_index, column_index); + if (column_index == dead_zone_col) // Dead Zone + { + mNumericValueEditor->setMinValue(0); + mNumericValueEditor->setMaxValue(LLGameControl::MAX_AXIS_DEAD_ZONE); + mNumericValueEditor->setValue(deviceOptions.getAxisOptions()[row_index].mDeadZone); + } + else // Offset + { + mNumericValueEditor->setMinValue(-LLGameControl::MAX_AXIS_OFFSET); + mNumericValueEditor->setMaxValue(LLGameControl::MAX_AXIS_OFFSET); + mNumericValueEditor->setValue(deviceOptions.getAxisOptions()[row_index].mOffset); + } + mNumericValueEditor->setVisible(true); + } + } +} + +// Handles selection in the button-channels table. Only the Output columns (icon + +// description) use a popup selector; the Input columns are fixed (the physical button). +void LLPanelPreferenceGameControl::onButtonChannelsSelect() +{ + clearSelectionState(); + + // Deselect everything in the other scroll_list + mAxisChannels->deselectAllItems(true); + + if (LLScrollListItem* row = mButtonChannels->getFirstSelected()) + { + initCombobox(row, mButtonChannels); + } +} + +// Called when user commits a numeric value (offset or dead zone) in the spin +// control over the Device State tab's axis table. Validates and clamps the value, +// then updates both the UI and the state device's options. +void LLPanelPreferenceGameControl::onCommitNumericValue() +{ + auto options_it = mDeviceOptions.find(mStateSelectedDeviceGUID); + if (options_it == mDeviceOptions.end()) + { + return; + } + LLGameControl::Options& deviceOptions = options_it->second.options; + + if (LLScrollListItem* row = mAxisState->getFirstSelected()) + { + S32 value = mNumericValueEditor->getValue().asInteger(); + S32 row_index = mAxisState->getItemIndex(row); + S32 offset_col = axisStateColumn("offset"); + S32 dead_zone_col = axisStateColumn("dead_zone"); + S32 column_index = row->getSelectedCell(); + llassert(column_index == offset_col || column_index == dead_zone_col); + if (column_index != offset_col && column_index != dead_zone_col) + return; + + if (column_index == dead_zone_col) // Dead Zone + { + value = std::clamp(value, 0, LLGameControl::MAX_AXIS_DEAD_ZONE); + deviceOptions.getAxisOptions()[row_index].mDeadZone = (U16)value; + } + else // Offset + { + value = std::clamp(value, -LLGameControl::MAX_AXIS_OFFSET, LLGameControl::MAX_AXIS_OFFSET); + deviceOptions.getAxisOptions()[row_index].mOffset = (S16)value; + } + setNumericLabel(row->getColumn(column_index), value); + LLGameControl::setDeviceOptions(mStateSelectedDeviceGUID, deviceOptions); + } +} + +// Initializes all UI controls and sets up callbacks. +// Called once when the panel is first built from XML. +bool LLPanelPreferenceGameControl::postBuild() +{ + // Send-to-server checkbox (on the Server Data sub-tab) + mCheckGameControlToServer = getChild("game_control_to_server"); + mCheckGameControlToServer->setCommitCallback([this](LLUICtrl*, const LLSD&) + { + LLGameControl::setSendToServer(mCheckGameControlToServer->getValue()); + }); + + getChild("game_control_tabs")->setCommitCallback([this](LLUICtrl*, const LLSD&) { clearSelectionState(); }); + + mTabActions = getChild("tab_action_mappings"); + mTabDevices = getChild("tab_device_mappings"); + + // Actions tab (global, per-mode). The mode items (Avatar/FlyCam/Captive) are + // defined in the XML; only one mode is visible at a time, with a single + // Enabled checkbox that tracks whichever mode is currently selected. + mActionMode = getChild("action_mode"); + mActionMode->setCommitCallback([this](LLUICtrl*, const LLSD&) { onActionModeChanged(); }); + + mCheckActionModeEnabled = getChild("action_mode_enabled"); + mCheckActionModeEnabled->setCommitCallback([this](LLUICtrl*, const LLSD&) + { onModeEnabledToggled(mCheckActionModeEnabled->getValue()); }); + + mCheckFlycamAllowRoll = getChild("flycam_allow_roll"); + mCheckFlycamAllowRoll->setCommitCallback([this](LLUICtrl*, const LLSD&) + { LLGameControl::setFlycamRollAllowed(mCheckFlycamAllowRoll->getValue()); }); + + mSliderFlycamSpeedFactor = getChild("flycam_speed_factor"); + mSliderFlycamSpeedFactor->setCommitCallback([this](LLUICtrl*, const LLSD&) + { LLGameControl::setFlycamSpeedFactor((F32)mSliderFlycamSpeedFactor->getValue().asReal()); }); + + mCheckFlycamCrosshair = getChild("flycam_crosshair"); + mCheckFlycamCrosshair->setCommitCallback([this](LLUICtrl*, const LLSD&) + { LLGameControl::setFlycamCrosshairEnabled(mCheckFlycamCrosshair->getValue()); }); + + mRestoreActionsDefaults = getChild("restore_actions_defaults"); + mRestoreActionsDefaults->setCommitCallback([this](LLUICtrl*, const LLSD&) { onResetActionsToDefaults(); }); + + mCurrentModeIndicator = getChild("current_mode_indicator"); + + mActionMappingsAxes = getChild("action_mappings_axes"); + mActionMappingsAxes->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onGridSelect(ctrl); }); + + mActionMappingsButtons = getChild("action_mappings_buttons"); + mActionMappingsButtons->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onGridSelect(ctrl); }); + + // Inputs tab (per-device) + mNoDeviceMessage = getChild("nodevice_message"); + mDevicePrompt = getChild("device_prompt"); + mDeviceList = getChild("device_list"); + mCheckShowAllDevices = getChild("show_all_known_devices"); + mPanelDeviceSettings = getChild("device_settings"); + + mCheckShowAllDevices->setCommitCallback([this](LLUICtrl*, const LLSD&) { populateDeviceTitle(); }); + mDeviceList->setCommitCallback([this](LLUICtrl*, const LLSD& value) { populateDeviceSettings(value); }); + + mRestoreDeviceDefaults = getChild("restore_device_defaults"); + mRestoreDeviceDefaults->setCommitCallback([this](LLUICtrl*, const LLSD&) { onResetDeviceToDefaults(); }); + + mAxisChannels = getChild("axis_channels"); + mAxisChannels->setCommitCallback([this](LLUICtrl*, const LLSD&) { onAxisChannelsSelect(); }); + + mButtonChannels = getChild("button_channels"); + mButtonChannels->setCommitCallback([this](LLUICtrl*, const LLSD&) { onButtonChannelsSelect(); }); + + // Outputs tab (per device) + mTabDeviceState = getChild("tab_device_options"); + mStateNoDeviceMessage = getChild("state_nodevice_message"); + mStateDevicePrompt = getChild("state_device_prompt"); + mStateRemapNote = getChild("state_remap_note"); + mStateDeviceList = getChild("options_device_list"); + mPanelDeviceState = getChild("device_state_settings"); + mStateDeviceList->setCommitCallback([this](LLUICtrl*, const LLSD& value) + { + mStateSelectedDeviceGUID = value.asString(); + clearSelectionState(); + mCalibrating = false; // switching devices invalidates any in-progress calibration + populateAxisStateOptionCells(); // refresh invert/offset/dead-zone for the new device + }); + + mRestoreDeviceOptionsDefaults = getChild("restore_device_options_defaults"); + mRestoreDeviceOptionsDefaults->setCommitCallback([this](LLUICtrl*, const LLSD&) { onResetDeviceOptionsToDefaults(); }); + + mAutoCalibrate = getChild("auto_calibrate"); + mAutoCalibrate->setCommitCallback([this](LLUICtrl*, const LLSD&) { onAutoCalibrate(); }); + + mAxisState = getChild("axis_state"); + // The axis-state table is now editable: its Invert/Offset/Dead Zone columns + // tune the state device's axis options while its Raw/Adjusted columns update live. + mAxisState->setCommitCallback([this](LLUICtrl*, const LLSD&) { onAxisStateSelect(); }); + mButtonState = getChild("button_state"); + + // Data Output tab (live, read-only view of the last outgoing GameControlInput) + mTabDataOutput = getChild("tab_server_data"); + mDataOutput = getChild("data_output"); + + // Spin control for editing deadzone/offset values inline + mNumericValueEditor = getChild("numeric_value_editor"); + mNumericValueEditor->setCommitCallback([this](LLUICtrl*, const LLSD&) { onCommitNumericValue(); }); + + // Action selectors: provide the "Action" column rows, chosen by edit mode. + mAnalogActionSelector = getChild("analog_action_selector"); + mBinaryActionSelector = getChild("binary_action_selector"); + mMouselookAnalogActionSelector = getChild("mouselook_analog_action_selector"); + mMouselookBinaryActionSelector = getChild("mouselook_binary_action_selector"); + mFlycamAnalogActionSelector = getChild("flycam_analog_action_selector"); + mFlycamBinaryActionSelector = getChild("flycam_binary_action_selector"); + mCaptiveBinaryActionSelector = getChild("sit_binary_action_selector"); + mCursorAnalogActionSelector = getChild("cursor_analog_action_selector"); + mCursorBinaryActionSelector = getChild("cursor_binary_action_selector"); + + // Canonical input selectors, shown inline when editing an Input/Output cell. + // The Actions tab binds an axis action to a canonical input (sticks or the + // "Triggers left/right" pair) via mAxisInputSelector; the Devices tab maps a + // physical axis to an individual canonical axis via mAxisOutputSelector. + mAxisInputSelector = getChild("axis_input_selector"); + mAxisInputSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + + mAxisOutputSelector = getChild("axis_output_selector"); + mAxisOutputSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + + // Glyph counterpart of mAxisOutputSelector, shown when editing the + // SecondLife-game-control "output" (icon) column of the axis-channels table. + // Same values, rendered as SecondLife-game-control glyphs, so build it from + // the text selector's values. + mAxisOutputGlyphSelector = getChild("axis_output_glyph_selector"); + mAxisOutputGlyphSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + buildInputGlyphSelector(mAxisOutputSelector, mAxisOutputGlyphSelector); + + mButtonInputSelector = getChild("button_input_selector"); + mButtonInputSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + + // Glyph selectors shown when editing the SecondLife-game-control + // "Axis"/"Button" column. Their items mirror the text selectors' values, so + // build them from those. + mAxisInputGlyphSelector = getChild("axis_input_glyph_selector"); + mAxisInputGlyphSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + buildInputGlyphSelector(mAxisInputSelector, mAxisInputGlyphSelector); + + mButtonInputGlyphSelector = getChild("button_input_glyph_selector"); + mButtonInputGlyphSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + buildInputGlyphSelector(mButtonInputSelector, mButtonInputGlyphSelector); + + // The device tables' rows are static (one per physical axis/button); their + // cells are filled per-device. The action table is mode-specific and built + // in populateActionMappings() on open / mode change. + populateAxisChannelsRows(); + populateButtonChannelsRows(); + + // The Device State tab's rows are static (one per physical axis/button); their + // Value cells are refreshed every frame in draw() from the selected device. + populateAxisStateRows(); + populateButtonStateRows(); + + // The Data Output tab's rows are static (AXIS_0..5, a blank separator, then + // BUTTON_0..31); their Value cells are refreshed whenever a fresh + // GameControlInput message is sent (see updateDataOutput()). + populateDataOutputRows(); + + // Workaround for the common bug: + // LLScrollListCtrl with draw_heading="true" initially has incorrect mTop (17 px higher). + // Each scroll list fills a dedicated wrapper panel, so the corrected top is its + // parent's height - 1. + auto fixHeadingTop = [](LLScrollListCtrl* grid) + { + LLRect rect = grid->getRect(); + rect.mTop = grid->getParent()->getRect().getHeight() - 1; + grid->setRect(rect); + grid->updateLayout(); + }; + fixHeadingTop(mActionMappingsAxes); + fixHeadingTop(mActionMappingsButtons); + fixHeadingTop(mAxisChannels); + fixHeadingTop(mButtonChannels); + fixHeadingTop(mAxisState); + fixHeadingTop(mButtonState); + fixHeadingTop(mDataOutput); + + return true; +} + +// Called when the preferences floater is opened. +// Loads current LLGameControl state into UI controls and refreshes all tables. +void LLPanelPreferenceGameControl::onOpen(const LLSD& key) +{ + // Sync checkbox with current LLGameControl state + mCheckGameControlToServer->setValue(LLGameControl::sendToServer()); + + clearSelectionState(); + + // Default to editing whichever mode is currently active in-world, so the + // panel opens already showing the mappings the user is presently driving + // with; fall back to Avatar (the first action_mode item) when no mode is + // active (CONTROL_MODE_NONE, e.g. not yet logged in). + LLGameControl::AgentControlMode live_mode = LLGameControl::getAgentControlMode(); + if (live_mode == LLGameControl::CONTROL_MODE_NONE || !mActionMode->selectByValue((S32)live_mode)) + { + mActionMode->selectFirstItem(); + } + populateActionMappings(); + + // Refresh device list and settings + updateDeviceListInternal(); + + mCheckGameControlToServer->setEnabled(true); + mActionMappingsAxes->setEnabled(true); + mActionMappingsButtons->setEnabled(true); + mAxisChannels->setEnabled(true); + mButtonChannels->setEnabled(true); + mDeviceList->setEnabled(true); + mStateDeviceList->setEnabled(true); + + // Apply the current mode's enable flag (must run after the blanket enables + // above): syncs the checkbox and locks the tables if the mode is disabled. + updateActionModeEnabledUI(); + + // Clear original settings - will be populated on first saveSettings() call + mOrigSettings = LLSD::emptyMap(); +} + + static LLRect computeCellScreenRect(LLScrollListCtrl* grid, LLView* relative_to, S32 row_index, S32 col_index) + { + LLRect rect(grid->getCellRect(row_index, col_index)); + LLView* parent = grid->getParent(); + while (parent && parent != relative_to) + { + rect.translate(parent->getRect().mLeft, parent->getRect().mBottom); + parent = parent->getParent(); + } + return rect; + } + +// Per-frame refresh of the Device State tab. Only touches the tables while that +// tab is actually the visible one, so it costs nothing on the other tabs. +void LLPanelPreferenceGameControl::draw() +{ + updateCurrentModeIndicator(); + if (mTabDeviceState && mTabDeviceState->getVisible()) + { + populateDeviceStateValues(); + if (mCalibrating) + { + updateAutoCalibration(); + } + } + if (sSelectedCombobox && sSelectedGrid && sSelectedRow >= 0 && sSelectedCol >= 0) + { + LLRect current_rect = computeCellScreenRect(sSelectedGrid, sSelectedCombobox->getParent(), sSelectedRow, sSelectedCol); + if (current_rect != sSelectedOpenRect) + { + clearSelectionState(); + } + } + + LLPanelPreference::draw(); +} + +// Called when user clicks OK. Clears selections and prepares for onClose() +void LLPanelPreferenceGameControl::apply() +{ + LLPanelPreference::apply(); + clearSelectionState(); + + // Clear mOrigSettings to prevent cancel() from reverting changes. + // Note: cancel() is called in onClose() even after OK, so we must clear this. + mOrigSettings = LLSD::emptyMap(); + + // Note: any settings changes have already been written to global-settings + // via the LLControlVariables assigned to the UI elements. LLGameControl has + // already loaded from global-settings in saveSettings(). +} + +// Called when user clicks Cancel. Restores all settings to their original values. +void LLPanelPreferenceGameControl::cancel(const std::vector settings_to_skip) +{ + LLPanelPreference::cancel(settings_to_skip); + clearSelectionState(); + + // Skip restore if original settings were cleared in apply(). + if (mOrigSettings.isEmpty()) + { + return; + } + + // Restore in-memory state from the snapshot, then push back to gSavedSettings + // via the bulk save path so persistence stays consistent. + LLGameControl::applySettingsFromLLSD(mOrigSettings); + LLGameControl::saveToSettings(); +} + +// Rebuilds the internal device options map from LLGameControl state. +// Merges saved options with currently connected devices. +void LLPanelPreferenceGameControl::updateDeviceListInternal() +{ + mDeviceOptions.clear(); + + // Load saved device options from settings. Each device's saved Config is + // parsed independently -- one entry with a corrupted/hand-edited Config + // (loadFromString() returns false) falls back to default Options rather + // than being dropped or affecting any other device's entry in this loop. + for (const auto& [guid, options] : LLGameControl::getDeviceOptions()) + { + DeviceOptions deviceOptions = { LLStringUtil::null, options, LLGameControl::Options() }; + if (!deviceOptions.options.loadFromString(deviceOptions.name, deviceOptions.settings)) + { + LL_WARNS("SDL3") << "Discarding invalid saved device options for '" << guid + << "'; using defaults instead" << LL_ENDL; + } + if (deviceOptions.name.empty()) + { + // Parsing failed before reaching the saved name, or the saved + // Config simply never recorded one -- fall back to the guid so + // the device list never shows a blank title. + deviceOptions.name = guid; + } + mDeviceOptions.emplace(guid, deviceOptions); + } + + // Add currently connected devices that don't have saved settings yet + for (const auto& device : LLGameControl::getDevices()) + { + if (mDeviceOptions.find(device.getGUID()) == mDeviceOptions.end()) + { + mDeviceOptions[device.getGUID()] = { device.getName(), device.saveOptionsToString(true), device.getOptions() }; + } + } + + mCheckShowAllDevices->setValue(false); + populateDeviceTitle(); + + // The Device State tab tracks the live set of connected devices too. + updateDeviceStateList(); +} + +// Updates the device selection UI based on available devices. +// Shows single device name, dropdown list, or "no device" message as appropriate. +void LLPanelPreferenceGameControl::populateDeviceTitle() +{ + mSelectedDeviceGUID.clear(); + + // "Show all devices" includes saved settings for disconnected devices + bool showAllDevices = mCheckShowAllDevices->getValue().asBoolean(); + std::size_t deviceCount = showAllDevices ? mDeviceOptions.size() : LLGameControl::getDevices().size(); + + mNoDeviceMessage->setVisible(!deviceCount); + mDevicePrompt->setVisible(deviceCount); + mDeviceList->setVisible(deviceCount); // always use the combo box, even for one device + mPanelDeviceSettings->setVisible(deviceCount); + mRestoreDeviceDefaults->setVisible(deviceCount); + + if (!deviceCount) + { + return; + } + + auto makeTitle = [](const std::string& guid, const std::string& name) -> std::string + { + return guid + ", " + name; + }; + + auto makeListItem = [](const std::string& guid, const std::string& title) + { + return LLSD().with("value", guid).with("columns", LLSD().with("label", title)); + }; + + mDeviceList->clear(); + mDeviceList->clearRows(); + + if (showAllDevices) + { + for (const auto& [guid, device] : mDeviceOptions) + { + mDeviceList->addElement(makeListItem(guid, makeTitle(guid, device.name))); + } + } + else + { + for (const LLGameControl::Device& device : LLGameControl::getDevices()) + { + mDeviceList->addElement(makeListItem(device.getGUID(), makeTitle(device.getGUID(), device.getName()))); + } + } + + mDeviceList->selectNthItem(0); + populateDeviceSettings(mDeviceList->getValue()); +} + +// Loads settings for the specified device into the device-channel tables. +void LLPanelPreferenceGameControl::populateDeviceSettings(const std::string& guid) +{ + mSelectedDeviceGUID = guid; + llassert_always(mDeviceOptions.find(guid) != mDeviceOptions.end()); + + // Device tables are per-device (mode-independent). + populateAxisChannelsCells(); + populateButtonChannelsCells(); +} + +// The mode string ("When moving Avatar"/"When moving FlyCam"/"When sitting"/ +// "When in mouselook") currently selected for editing. +std::string LLPanelPreferenceGameControl::currentEditMode() +{ + S32 ordinal = mActionMode->getValue().asInteger(); + return LLGameControl::getModeName((LLGameControl::AgentControlMode)ordinal); +} + +// Picks the action selector supplying the "Action" rows for a block + mode. +LLComboBox* LLPanelPreferenceGameControl::actionSelectorForMode(bool axis, const std::string& mode) const +{ + bool flycam = (mode == "FlyCam"); + bool mouselook = (mode == "Mouselook"); + bool cursor = (mode == "Cursor"); + // Avatar and Captive share the same axis actions (see llgamecontrol.cpp); + // Mouselook and Cursor each have their own axis-action selector too, since + // Cursor swaps in "Mouse left/right"/"Mouse up/down" for Strafe/Advance. + // Each mode has its own button-action selector so their button sets can + // diverge (e.g. Captive swaps in "Stand" for "Jump", Cursor adds "Mouse + // click left/right"). + if (axis) + return flycam ? mFlycamAnalogActionSelector + : mouselook ? mMouselookAnalogActionSelector + : cursor ? mCursorAnalogActionSelector + : mAnalogActionSelector; + if (mouselook) + return mMouselookBinaryActionSelector; + if (flycam) + return mFlycamBinaryActionSelector; + if (cursor) + return mCursorBinaryActionSelector; + return (mode == "Captive") ? mCaptiveBinaryActionSelector : mBinaryActionSelector; +} + +// Rebuilds the two Actions tables for the current mode: axis actions in the top +// table, button actions in the bottom table. Each row stores {input_type, action} as its +// value and shows the action label + its currently-mapped input. +void LLPanelPreferenceGameControl::populateActionMappings() +{ + mActionMappingsAxes->clearRows(); + mActionMappingsButtons->clearRows(); + + std::string mode = currentEditMode(); + + auto addBlock = [&](LLScrollListCtrl* grid, const std::string& input_type, + const LLComboBox* action_selector, const LLComboBox* input_selector) + { + LLSD mapping = LLGameControl::getModeMapping(mode, input_type); + // Axes-only Invert column: -1 for the Buttons block (no such column there). + // The selector's last item is the "None" value (e.g. AXIS_NONE), used below + // to tell an unmapped row from a mapped one. + S32 invert_col = (input_type == MODE_INPUT_TYPE_AXES) ? grid->getColumn("invert")->mIndex : -1; + std::string none_value = input_selector->getAllData().back()->getValue().asString(); + + for (LLScrollListItem* item : action_selector->getAllData()) + { + std::string action = item->getValue().asString(); + if (action == NONE_LABEL) // skip the "None" sentinel; not an action row + continue; + + // Current input bound to this action, and the SecondLife-game-control + // glyph to show instead of the text label, if this input has one. + std::string input_value = mapping.has(action) ? mapping[action].asString() : LLStringUtil::null; + std::string glyph = promptFontGlyph(input_value); + + LLScrollListItem::Params row_params; + for (S32 c = 0; c < grid->getNumColumns(); ++c) + { + LLScrollListCell::Params cell_params; + const std::string& col_name = grid->getColumn(c)->mName; + cell_params.column = col_name; + // The icon column (1) renders the controller glyph in + // SecondLife-game-control font when this input has one; the action + // (0) and description (2) columns match the "Controls" preferences + // panel font. + cell_params.font = (c == 1 && !glyph.empty()) + ? promptFontForCell() + : LLFontGL::getFontSansSerif(); + if (c == invert_col) + { + cell_params.type = "checkbox"; + cell_params.font_halign = "right"; // right-align the checkbox within the cell + } + row_params.columns.add(cell_params); + } + + // Row value carries the block input_type and action key (used on commit/lookup). + LLSD row_value; + row_value["input_type"] = input_type; + row_value["action"] = action; + row_params.value = row_value; + + LLScrollListItem* row = grid->addRow(row_params); + row->getColumn(0)->setValue(item->getColumn(0)->getValue()); // action label + // Columns 1 (icon) and 2 (description) both derive from input_value, so + // they always refer to the same controller input. + row->getColumn(1)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + row->getColumn(2)->setValue(blankIfNone(inputLabel(input_selector, input_value))); + + if (invert_col >= 0) + { + // Disabled (and left at its last value) whenever no axis is mapped, + // since there is nothing to invert. + bool mapped = !input_value.empty() && input_value != none_value; + row->getColumn(invert_col)->setValue(LLGameControl::getAxisInvert(mode, action)); + row->getColumn(invert_col)->setEnabled(mapped); + } + } + }; + + addBlock(mActionMappingsAxes, MODE_INPUT_TYPE_AXES, actionSelectorForMode(true, mode), mAxisInputSelector); + addBlock(mActionMappingsButtons, MODE_INPUT_TYPE_BUTTONS, actionSelectorForMode(false, mode), mButtonInputSelector); +} + +// Handles a change in the edit-mode selector: rebuild the action table. +void LLPanelPreferenceGameControl::onActionModeChanged() +{ + clearSelectionState(); + populateActionMappings(); + updateActionModeEnabledUI(); +} + +// Toggle whether game-control input is converted to the currently-selected +// mode's actions. +void LLPanelPreferenceGameControl::onModeEnabledToggled(bool enabled) +{ + clearSelectionState(); + LLGameControl::setModeEnabled(currentEditMode(), enabled); + updateActionModeEnabledUI(); +} + +// Sync the Enabled checkbox with the selected mode's stored flag (keeps it +// correct after apply/cancel/reset and after switching modes) and lock/unlock +// the action tables (and Restore Defaults) so a disabled mode's mappings can't change. +void LLPanelPreferenceGameControl::updateActionModeEnabledUI() +{ + std::string mode = currentEditMode(); + bool enabled = LLGameControl::isModeEnabled(mode); + mCheckActionModeEnabled->set(enabled); + // A disabled mode's tables grey out; onGridSelect() also refuses edits on a + // disabled table, so this both signals and enforces the lock. + mActionMappingsAxes->setEnabled(enabled); + mActionMappingsButtons->setEnabled(enabled); + mRestoreActionsDefaults->setEnabled(enabled); + + // Roll, move-speed, and the crosshair are only meaningful for FlyCam: hide + // all three controls for every other mode. + bool is_flycam = (mode == "FlyCam"); + mCheckFlycamAllowRoll->setVisible(is_flycam); + mSliderFlycamSpeedFactor->setVisible(is_flycam); + mCheckFlycamCrosshair->setVisible(is_flycam); + if (is_flycam) + { + mCheckFlycamAllowRoll->set(LLGameControl::isFlycamRollAllowed()); + mCheckFlycamAllowRoll->setEnabled(enabled); + mSliderFlycamSpeedFactor->setValue(LLGameControl::getFlycamSpeedFactor()); + mSliderFlycamSpeedFactor->setEnabled(enabled); + mCheckFlycamCrosshair->set(LLGameControl::isFlycamCrosshairEnabled()); + mCheckFlycamCrosshair->setEnabled(enabled); + } +} + +// Shows the live AgentControlMode independent of the mode being edited (mActionMode) +void LLPanelPreferenceGameControl::updateCurrentModeIndicator() +{ + LLGameControl::AgentControlMode mode = LLGameControl::getAgentControlMode(); + if (mode == mLastDrawnControlMode) + { + return; // no change, skip the string work + } + mLastDrawnControlMode = mode; + + if (mCurrentModeIndicator) + { + static const std::map sDisplayNames = { + { LLGameControl::CONTROL_MODE_AVATAR, "Moving Avatar" }, + { LLGameControl::CONTROL_MODE_MOUSELOOK, "Mouselook" }, + { LLGameControl::CONTROL_MODE_FLYCAM, "FlyCam" }, + { LLGameControl::CONTROL_MODE_CAPTIVE, "Sitting" }, + { LLGameControl::CONTROL_MODE_CURSOR, "Mouse Cursor" }, + }; + auto it = sDisplayNames.find(mode); + std::string name = (it != sDisplayNames.end()) ? it->second : "None"; + mCurrentModeIndicator->setText(std::string("Active mode: ") + name); + } +} + +// Enforces that an input drives at most one action within a mode's block. When a +// new (non-None) input is assigned to an action, any *other* action in the same +// mode+input_type mapping that currently uses that same input is reset to "None" +// (its Input cell blanked and its mapping set to the selector's None value). +void LLPanelPreferenceGameControl::removeDuplicateActionInput(const std::string& mode, + const std::string& input_type, const std::string& keep_action, const std::string& input_value, + const LLComboBox* input_selector) +{ + // The selector's last item is the "None" value (e.g. AXIS_NONE / BUTTON_NONE). + std::string none_value = input_selector->getAllData().back()->getValue().asString(); + LLSD mapping = LLGameControl::getModeMapping(mode, input_type); + + LLScrollListCtrl* grid = (input_type == MODE_INPUT_TYPE_AXES) ? mActionMappingsAxes : mActionMappingsButtons; + for (LLScrollListItem* row : grid->getAllData()) + { + LLSD row_value = row->getValue(); + if (!row_value.isMap()) + continue; + + std::string action = row_value["action"].asString(); + if (action == keep_action) + continue; + + if (mapping.has(action) && mapping[action].asString() == input_value) + { + LLGameControl::updateModeMapping(mode, input_type, action, none_value); + row->getColumn(1)->setValue(LLSD()); // blank the Input cell + } + } +} + +// Creates one row per physical axis in the axis-channels table. Columns: +// input(glyph) | input_description | output_description | output(glyph). The two +// Input columns are the fixed physical axis (icon + text); the two Output columns +// are the canonical axis it maps to (icon + text), edited via popup selectors. +// Axis tuning (invert/offset/dead zone) lives on the Device State tab. +void LLPanelPreferenceGameControl::populateAxisChannelsRows() +{ + mAxisChannels->clearRows(); + + // The physical controller axes are the output selector's first NUM_AXES items, + // in order (item N == canonical axis N). + std::vector items = mAxisOutputSelector->getAllData(); + + S32 input_glyph_col = mAxisChannels->getColumn("input")->mIndex; + S32 input_desc_col = mAxisChannels->getColumn("input_description")->mIndex; + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + for (S32 i = 0; i < (S32)(mAxisChannels->getNumColumns()); ++i) + { + const std::string& name = mAxisChannels->getColumn(i)->mName; + // The icon columns ("input"/"output") render the controller glyph in + // SecondLife-game-control; the description columns match the "Controls" + // panel font. + cell_params.font = (name == "input" || name == "output") + ? promptFontForCell() + : LLFontGL::getFontSansSerif(); + cell_params.column = name; + row_params.columns.add(cell_params); + } + + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) + { + LLScrollListItem* row = mAxisChannels->addRow(row_params); + // The physical axis is fixed: fill both its text label and its glyph. + std::string glyph = promptFontGlyph(items[i]->getValue().asString()); + row->getColumn(input_desc_col)->setValue(items[i]->getColumn(0)->getValue()); + row->getColumn(input_glyph_col)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + } +} + +// Fills the axis-channel Output cells (text description + SecondLife-game-control +// glyph) from the current device's axis map. +void LLPanelPreferenceGameControl::populateAxisChannelsCells() +{ + std::vector rows = mAxisChannels->getAllData(); + const LLGameControl::Options& options = getSelectedDeviceOptions(); + const auto& axis_map = options.getAxisMap(); + + S32 output_desc_col = mAxisChannels->getColumn("output_description")->mIndex; + S32 output_glyph_col = mAxisChannels->getColumn("output")->mIndex; + + for (size_t i = 0; i < rows.size() && i < axis_map.size(); ++i) + { + std::string output_name = LLGameControl::axisOutputName(axis_map[i]); + std::string glyph = promptFontGlyph(output_name); + rows[i]->getColumn(output_desc_col)->setValue(inputLabel(mAxisOutputSelector, output_name)); + rows[i]->getColumn(output_glyph_col)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + } +} + +// Creates one row per physical button in the button-channels table. Columns: +// input(glyph) | input_description | output_description | output(glyph). The two +// Input columns are the fixed physical button (icon + text); the two Output columns +// are the canonical button it maps to (icon + text), edited via popup selectors. +void LLPanelPreferenceGameControl::populateButtonChannelsRows() +{ + mButtonChannels->clearRows(); + + std::vector items = mButtonInputSelector->getAllData(); + + S32 input_glyph_col = mButtonChannels->getColumn("input")->mIndex; + S32 input_desc_col = mButtonChannels->getColumn("input_description")->mIndex; + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + for (S32 i = 0; i < (S32)(mButtonChannels->getNumColumns()); ++i) + { + const std::string& name = mButtonChannels->getColumn(i)->mName; + // The icon columns ("input"/"output") render the controller glyph in + // SecondLife-game-control; the description columns match the + // "Controls" panel font. + cell_params.font = (name == "input" || name == "output") + ? promptFontForCell() + : LLFontGL::getFontSansSerif(); + cell_params.column = name; + row_params.columns.add(cell_params); + } + + for (size_t i = 0; i < LLGameControl::NUM_BUTTONS; ++i) + { + LLScrollListItem* row = mButtonChannels->addRow(row_params); + // The physical button is fixed: fill both its text label and its glyph + // (buttons without a glyph, e.g. spares, keep an empty icon cell). + std::string glyph = promptFontGlyph(items[i]->getValue().asString()); + row->getColumn(input_desc_col)->setValue(items[i]->getColumn(0)->getValue()); + row->getColumn(input_glyph_col)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + } +} + +// Fills the button-channel Output cells (text description + +// SecondLife-game-control glyph) from the current device's button map. +void LLPanelPreferenceGameControl::populateButtonChannelsCells() +{ + std::vector rows = mButtonChannels->getAllData(); + const auto& button_map = getSelectedDeviceOptions().getButtonMap(); + std::vector items = mButtonInputSelector->getAllData(); + llassert(rows.size() == button_map.size()); + + S32 output_desc_col = mButtonChannels->getColumn("output_description")->mIndex; + S32 output_glyph_col = mButtonChannels->getColumn("output")->mIndex; + + for (size_t i = 0; i < rows.size(); ++i) + { + U8 output = button_map[i]; + rows[i]->getColumn(output_desc_col)->setValue(selectorLabelAt(mButtonInputSelector, output)); + std::string value = (output < items.size()) ? items[output]->getValue().asString() : LLStringUtil::null; + std::string glyph = promptFontGlyph(value); + rows[i]->getColumn(output_glyph_col)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + } +} + +// Rebuilds the Device State selector from the currently connected devices. +// Only connected devices are listed here, since the tab shows live input and a +// disconnected device has no live state. Shows the "No device" message and hides +// the tables when nothing is connected. +void LLPanelPreferenceGameControl::updateDeviceStateList() +{ + mStateDeviceList->clear(); + mStateDeviceList->clearRows(); + + const std::list& devices = LLGameControl::getDevices(); + bool hasDevice = !devices.empty(); + + mStateNoDeviceMessage->setVisible(!hasDevice); + mStateDevicePrompt->setVisible(hasDevice); + mStateRemapNote->setVisible(hasDevice); + mStateDeviceList->setVisible(hasDevice); + mRestoreDeviceOptionsDefaults->setVisible(hasDevice); + mPanelDeviceState->setVisible(hasDevice); + + if (!hasDevice) + { + mStateSelectedDeviceGUID.clear(); + return; + } + + for (const LLGameControl::Device& device : devices) + { + std::string title = device.getGUID() + ", " + device.getName(); + mStateDeviceList->addElement(LLSD().with("value", device.getGUID()) + .with("columns", LLSD().with("label", title))); + } + + // Keep the current selection if that device is still connected; otherwise + // fall back to the first device. + if (mStateSelectedDeviceGUID.empty() || !mStateDeviceList->setSelectedByValue(mStateSelectedDeviceGUID, true)) + { + mStateDeviceList->selectNthItem(0); + } + mStateSelectedDeviceGUID = mStateDeviceList->getValue().asString(); + + // Refresh the editable axis option cells for the (re)selected state device. + populateAxisStateOptionCells(); +} + +// Creates one row per physical axis in the axis-state table. Columns: +// Axis-state table columns (input, raw_value, invert, offset, dead_zone, +// fixed_value); their display order is defined entirely by the XML. Raw/Adjusted +// are filled live in draw(); Invert/Offset/Dead Zone are editable axis options. +// All cells are addressed by column name (see axisStateColumn), so reordering the +// columns in the XML requires no change here. +void LLPanelPreferenceGameControl::populateAxisStateRows() +{ + mAxisState->clearRows(); + + // One row per physical axis, labelled from the output selector's canonical-axis items. + std::vector items = mAxisOutputSelector->getAllData(); + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + for (S32 i = 0; i < (S32)(mAxisState->getNumColumns()); ++i) + { + const std::string& name = mAxisState->getColumn(i)->mName; + // The icon column ("input_glyph") renders the controller glyph in + // SecondLife-game-control font; every other column uses the default text font. + cell_params.font = (name == "input_glyph") + ? promptFontForCell() + : LLFontGL::getFontSansSerif(); + cell_params.column = name; + row_params.columns.add(cell_params); + } + // Configure each cell by column name so the XML column order stays the single + // source of truth (reordering columns in the XML needs no change here). + row_params.columns(axisStateColumn("raw_value")).font_halign = "right"; + row_params.columns(axisStateColumn("invert")).type = "checkbox"; + row_params.columns(axisStateColumn("invert")).font_halign = "right"; // right-align the checkbox within the cell + row_params.columns(axisStateColumn("offset")).font_halign = "right"; + row_params.columns(axisStateColumn("dead_zone")).font_halign = "right"; + row_params.columns(axisStateColumn("fixed_value")).font_halign = "right"; + + S32 input_glyph_col = axisStateColumn("input_glyph"); + S32 input_col = axisStateColumn("input"); + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) + { + LLScrollListItem* row = mAxisState->addRow(row_params); + // The physical axis is fixed: fill both its SecondLife-game-control glyph + // and its text label. + std::string glyph = promptFontGlyph(items[i]->getValue().asString()); + row->getColumn(input_glyph_col)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + row->getColumn(input_col)->setValue(items[i]->getColumn(0)->getValue()); // physical axis label + } + + // Fill the editable option columns for the currently selected state device. + populateAxisStateOptionCells(); +} + +// Fills the Device State axis table's editable option cells (Invert / Offset / +// Dead Zone) from the state tab's selected device. Called when rows are built and +// whenever the selected state device changes; the per-frame draw() only refreshes +// the Raw/Adjusted columns, so it never clobbers these while the user edits. +void LLPanelPreferenceGameControl::populateAxisStateOptionCells() +{ + auto options_it = mDeviceOptions.find(mStateSelectedDeviceGUID); + if (options_it == mDeviceOptions.end()) + { + return; + } + const auto& all_axis_options = options_it->second.options.getAxisOptions(); + std::vector rows = mAxisState->getAllData(); + S32 invert_col = axisStateColumn("invert"); + S32 offset_col = axisStateColumn("offset"); + S32 dead_zone_col = axisStateColumn("dead_zone"); + for (size_t i = 0; i < rows.size() && i < all_axis_options.size(); ++i) + { + const LLGameControl::Options::AxisOptions& axis_options = all_axis_options[i]; + rows[i]->getColumn(invert_col)->setValue(axis_options.mMultiplier == -1); // Invert checkbox + setNumericLabel(rows[i]->getColumn(offset_col), axis_options.mOffset); // Offset + setNumericLabel(rows[i]->getColumn(dead_zone_col), axis_options.mDeadZone); // Dead Zone + } +} + +// Creates one row per physical button in the button-state table. +// Columns: Button (physical button label) | Value (filled live in draw()). +void LLPanelPreferenceGameControl::populateButtonStateRows() +{ + mButtonState->clearRows(); + + std::vector items = mButtonInputSelector->getAllData(); + + S32 input_glyph_col = mButtonState->getColumn("input_glyph")->mIndex; + S32 input_col = mButtonState->getColumn("input")->mIndex; + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + for (S32 i = 0; i < (S32)(mButtonState->getNumColumns()); ++i) + { + const std::string& name = mButtonState->getColumn(i)->mName; + // The icon column ("input_glyph") renders the controller glyph in + // SecondLife-game-control; every other column uses the default text font. + cell_params.font = (name == "input_glyph") + ? promptFontForCell() + : LLFontGL::getFontSansSerif(); + cell_params.column = name; + row_params.columns.add(cell_params); + } + row_params.columns(mButtonState->getColumn("value")->mIndex).font_halign = "right"; // Value + + for (size_t i = 0; i < LLGameControl::NUM_BUTTONS; ++i) + { + LLScrollListItem* row = mButtonState->addRow(row_params); + // The physical button is fixed: fill both its SecondLife-game-control + // glyph and its text label. + std::string glyph = promptFontGlyph(items[i]->getValue().asString()); + row->getColumn(input_glyph_col)->setValue(glyph.empty() ? LLSD() : LLSD(glyph)); + row->getColumn(input_col)->setValue(items[i]->getColumn(0)->getValue()); // physical button label + } +} + +// Fills the Value columns of both state tables from the selected device's live +// input state. Axis values are the raw [0, 32767] readings; button values are +// 1 (pressed) or 0 (released). Called each frame while the tab is visible. +void LLPanelPreferenceGameControl::populateDeviceStateValues() +{ + if (mStateSelectedDeviceGUID.empty()) + { + return; + } + + // Locate the live state for the selected device by GUID. + const LLGameControl::State* state = nullptr; + for (const LLGameControl::Device& device : LLGameControl::getDevices()) + { + if (device.getGUID() == mStateSelectedDeviceGUID) + { + state = &device.getState(); + break; + } + } + if (!state) + { + return; + } + + // Each row is a physical axis, and its Invert/Offset/Dead-Zone options are keyed by + // physical axis, so Raw/Adjusted must be too. State::mAxes/mRawAxes are keyed by + // canonical output axis (post-mapping), which would land on the wrong row; instead + // read mPhysicalRawAxes (pre-fix) / mPhysicalFixedAxes (post-fix), the pre-map values + // onAxis() preserves by physical index specifically for this table. + std::vector axisRows = mAxisState->getAllData(); + S32 raw_col = axisStateColumn("raw_value"); + S32 fixed_col = axisStateColumn("fixed_value"); + for (size_t i = 0; i < axisRows.size(); ++i) + { + if (i >= state->mPhysicalRawAxes.size()) + { + break; + } + S32 raw = (S32)state->mPhysicalRawAxes[i]; // Raw Value (pre-fix) + S32 fixed = (S32)state->mPhysicalFixedAxes[i]; // Adjusted Value (post-fix) + axisRows[i]->getColumn(raw_col)->setValue(llformat("%d ", raw)); + axisRows[i]->getColumn(fixed_col)->setValue(llformat("%d ", fixed)); + } + + // Each row is a physical button, so read the pre-map mPhysicalButtons; mButtons is + // keyed by canonical button (post-mapping) and would show state on the wrong row. + std::vector buttonRows = mButtonState->getAllData(); + S32 button_value_col = mButtonState->getColumn("value")->mIndex; + for (size_t i = 0; i < buttonRows.size(); ++i) + { + S32 value = (state->mPhysicalButtons >> i) & 0x1; + buttonRows[i]->getColumn(button_value_col)->setValue(llformat("%d ", value)); + } +} + +// Static hook, called from the send path each time a fresh GameControlInput +// message goes out. Refreshes the Data Output tab's Value column from the +// values just packed into that message (the current server state). +// sGameControlPanel exists for the lifetime of the panel instance, not just while +// it's on-screen (e.g. Preferences closed, or another tab selected), and this is +// called unconditionally on every outgoing message -- so skip the refresh (and the +// mode-change/row-rebuild check inside populateDataOutputValues()) unless the tab is +// actually visible; it will simply pick up the current values on the next send after +// the user switches back to it. +void LLPanelPreferenceGameControl::updateDataOutput() +{ + if (sGameControlPanel && sGameControlPanel->mTabDataOutput->isInVisibleChain()) + { + sGameControlPanel->populateDataOutputValues(); + } +} + +// Creates the Data Output rows: one per canonical axis (AXIS_0..AXIS_5), a single +// empty separator row, then one per canonical button (BUTTON_0..BUTTON_31), then +// the semantic (mode-dependent) block built by populateDataOutputSemanticRows(). +// The Value column is filled later in populateDataOutputValues(). +void LLPanelPreferenceGameControl::populateDataOutputRows() +{ + mDataOutput->clearRows(); + mDataOutputSemanticMode = -1; // force the semantic block below to build fresh + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + cell_params.font = LLFontGL::getFontSansSerif(); + for (S32 i = 0; i < (S32)(mDataOutput->getNumColumns()); ++i) + { + cell_params.column = mDataOutput->getColumn(i)->mName; + row_params.columns.add(cell_params); + } + row_params.columns(2).font_halign = "right"; // Value + + // Column 0 is a per-block 0-based index; it restarts at 0 for the button + // block below. Column 1 is the canonical output name, column 2 the Value. + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) + { + LLScrollListItem* row = mDataOutput->addRow(row_params); + row->getColumn(0)->setValue(llformat("%d", (S32)i)); + row->getColumn(1)->setValue(LLGameControl::axisOutputName((U8)i)); + } + + // One empty row separates the axis block from the button block. + mDataOutput->addRow(row_params); + + for (size_t i = 0; i < LLGameControl::NUM_BUTTONS; ++i) + { + LLScrollListItem* row = mDataOutput->addRow(row_params); + row->getColumn(0)->setValue(llformat("%d", (S32)i)); + // getRemoteName() gives the Button enum's symbolic name for the named + // buttons (0..BUTTON_TOUCHPAD) but blanks the unnamed tail; fall back to + // the indexed BUTTON_N form there, which is exactly the enum name for those. + std::string name = LLGameControl::InputChannel(LLGameControl::InputChannel::TYPE_BUTTON, (U8)i).getRemoteName(); + LLStringUtil::trim(name); + if (name.empty()) + { + name = llformat("BUTTON_%d", (S32)i); + } + row->getColumn(1)->setValue(name); + } + + populateDataOutputSemanticRows((LLGameControl::AgentControlMode)LLGameControl::getServerState().mActionMode); +} + +// Rebuilds the semantic (mode-dependent) block of Data Output rows -- a blank +// separator, one row per semantic axis slot for 'mode' (LLGameControl:: +// numSemanticAxesForMode()/semanticAxisName()), another blank separator, then one +// row per semantic button slot (numSemanticButtonsForMode()/semanticButtonName()). +// Called whenever populateDataOutputValues() finds the live ServerState::mActionMode +// no longer matches mDataOutputSemanticMode: unlike the canonical block above, both +// the semantic block's row count and its labels depend on the mode, so simply +// truncating/re-adding rows here (rather than reusing stale ones) is what keeps a +// mode switch from leaving behind a stale label or a Value from a slot that no +// longer exists for the new mode. +void LLPanelPreferenceGameControl::populateDataOutputSemanticRows(LLGameControl::AgentControlMode mode) +{ + static const S32 CANONICAL_ROW_COUNT = (S32)LLGameControl::NUM_AXES + 1 + (S32)LLGameControl::NUM_BUTTONS; + while (mDataOutput->getItemCount() > CANONICAL_ROW_COUNT) + { + mDataOutput->deleteSingleItem(mDataOutput->getItemCount() - 1); + } + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + cell_params.font = LLFontGL::getFontSansSerif(); + for (S32 i = 0; i < (S32)(mDataOutput->getNumColumns()); ++i) + { + cell_params.column = mDataOutput->getColumn(i)->mName; + row_params.columns.add(cell_params); + } + row_params.columns(2).font_halign = "right"; // Value + + // One empty row separates the canonical button block from the semantic axes. + mDataOutput->addRow(row_params); + + U8 num_semantic_axes = LLGameControl::numSemanticAxesForMode(mode); + for (U8 i = 0; i < num_semantic_axes; ++i) + { + LLScrollListItem* row = mDataOutput->addRow(row_params); + row->getColumn(0)->setValue(llformat("%d", (S32)i)); + row->getColumn(1)->setValue(LLGameControl::semanticAxisName(mode, i)); + } + + // One empty row separates the semantic axes from the semantic buttons. + mDataOutput->addRow(row_params); + + U8 num_semantic_buttons = LLGameControl::numSemanticButtonsForMode(mode); + for (U8 i = 0; i < num_semantic_buttons; ++i) + { + LLScrollListItem* row = mDataOutput->addRow(row_params); + row->getColumn(0)->setValue(llformat("%d", (S32)i)); + row->getColumn(1)->setValue(LLGameControl::semanticButtonName(mode, i)); + } + + mDataOutputSemanticMode = (S32)mode; +} + +// Fills the Data Output Value column from the last outgoing server state: each +// axis shows its signed [-32768, 32767] value; each button shows 1 (pressed) or +// 0 (released). The row layout matches populateDataOutputRows()/ +// populateDataOutputSemanticRows(): canonical axes, a blank separator, canonical +// buttons, a blank separator, semantic axes, a blank separator, semantic buttons. +void LLPanelPreferenceGameControl::populateDataOutputValues() +{ + const LLGameControl::ServerState& state = LLGameControl::getServerState(); + LLGameControl::AgentControlMode mode = (LLGameControl::AgentControlMode)state.mActionMode; + if ((S32)mode != mDataOutputSemanticMode) + { + populateDataOutputSemanticRows(mode); + } + + std::vector rows = mDataOutput->getAllData(); + size_t row = 0; + + for (size_t i = 0; i < LLGameControl::NUM_AXES && i < state.mAxes.size() && row < rows.size(); ++i, ++row) + { + rows[row]->getColumn(2)->setValue(llformat("%d ", (S32)state.mAxes[i])); + } + + ++row; // skip the empty separator row + + for (size_t i = 0; i < LLGameControl::NUM_BUTTONS && row < rows.size(); ++i, ++row) + { + S32 value = (state.mButtons >> i) & 0x1; + rows[row]->getColumn(2)->setValue(llformat("%d ", value)); + } + + ++row; // skip the empty separator row between canonical buttons and semantic axes + + U8 num_semantic_axes = LLGameControl::numSemanticAxesForMode(mode); + for (size_t i = 0; i < num_semantic_axes && i < state.mSemanticAxes.size() && row < rows.size(); ++i, ++row) + { + rows[row]->getColumn(2)->setValue(llformat("%d ", (S32)state.mSemanticAxes[i])); + } + + ++row; // skip the empty separator row between semantic axes and semantic buttons + + U8 num_semantic_buttons = LLGameControl::numSemanticButtonsForMode(mode); + for (size_t i = 0; i < num_semantic_buttons && row < rows.size(); ++i, ++row) + { + S32 value = (state.mSemanticButtons >> i) & 0x1; + rows[row]->getColumn(2)->setValue(llformat("%d ", value)); + } +} + +// Returns the display label of the input selector item with the given value, +// or empty if there is no such item. +std::string LLPanelPreferenceGameControl::inputLabel(const LLComboBox* input_selector, const std::string& input_value) +{ + for (const LLScrollListItem* item : input_selector->getAllData()) + { + if (item->getValue().asString() == input_value) + { + return item->getColumn(0)->getValue().asString(); + } + } + return LLStringUtil::null; +} + +// Returns the display label of the selector item at the given index (the item +// position matches the canonical axis/button index), or empty if out of range. +std::string LLPanelPreferenceGameControl::selectorLabelAt(const LLComboBox* selector, S32 index) +{ + std::vector items = selector->getAllData(); + if (index < 0 || index >= (S32)items.size()) + { + return LLStringUtil::null; + } + return items[index]->getColumn(0)->getValue().asString(); +} + +// Populates glyph_selector with one item per value in text_selector. Each item +// keeps the canonical value but is displayed as its SecondLife-game-control +// glyph, so the dropdown shown over the icon column matches the icons in the +// table. Inputs with no glyph (e.g. "None", spare buttons) keep their text +// label in the normal font so they remain readable and selectable. +void LLPanelPreferenceGameControl::buildInputGlyphSelector(const LLComboBox* text_selector, LLComboBox* glyph_selector) +{ + glyph_selector->clearRows(); + for (const LLScrollListItem* item : text_selector->getAllData()) + { + std::string value = item->getValue().asString(); + std::string glyph = promptFontGlyph(value); + + LLSD element; + element["value"] = value; + LLSD& column = element["columns"][0]; + column["column"] = "input"; + if (glyph.empty()) + { + column["value"] = item->getColumn(0)->getValue().asString(); // text label + column["font"]["name"] = "SansSerif"; + column["font"]["size"] = "Small"; + } + else + { + column["value"] = glyph; + column["font"]["name"] = "SecondLife-game-control"; + column["font"]["size"] = "Huge"; + } + glyph_selector->addElement(element); + } +} + +// Returns the options struct for the currently selected device. +LLGameControl::Options& LLPanelPreferenceGameControl::getSelectedDeviceOptions() +{ + auto options_it = mDeviceOptions.find(mSelectedDeviceGUID); + llassert_always(options_it != mDeviceOptions.end()); + return options_it->second.options; +} + +// Formats a numeric value for display in a table cell. +// Always shows the value, including the zero (default) case. +S32 LLPanelPreferenceGameControl::axisStateColumn(const std::string& name) const +{ + LLScrollListColumn* column = mAxisState->getColumn(name); + return column ? column->mIndex : -1; +} + +void LLPanelPreferenceGameControl::setNumericLabel(LLScrollListCell* cell, S32 value) +{ + cell->setValue(llformat("%d ", value)); +} + +// Positions a UI control (combobox or spin control) to overlay a specific table cell. +// Used to show editors inline within the scroll list tables. +void LLPanelPreferenceGameControl::fitInRect(LLUICtrl* ctrl, LLScrollListCtrl* grid, S32 row_index, S32 col_index) +{ + LLRect rect(grid->getCellRect(row_index, col_index)); + LLView* parent = grid->getParent(); + while (parent && parent != ctrl->getParent()) + { + rect.translate(parent->getRect().mLeft, parent->getRect().mBottom); + parent = parent->getParent(); + } + + ctrl->setRect(rect); + rect.translate(-rect.mLeft, -rect.mBottom); + for (LLView* child : *ctrl->getChildList()) + { + LLRect childRect(child->getRect()); + childRect.intersectWith(rect); + if (childRect.mRight < rect.mRight && + childRect.mRight > (rect.mLeft + rect.mRight) / 2) + { + childRect.mRight = rect.mRight; + } + child->setRect(childRect); + } +} + +// Clears the current cell selection state and hides all popup editors. +void LLPanelPreferenceGameControl::clearSelectionState() +{ + // Restore the text we blanked when the selector opened (initCombobox). + // On commit this is the freshly committed value; otherwise it's the + // original, so a dismissed selector leaves the cell unchanged. + if (sSelectedCell) + { + sSelectedCell->setValue(sSelectedCellValue); + } + sSelectedCellValue = LLSD(); + + if (sSelectedGrid) + { + sSelectedGrid->deselectAllItems(true); + } + + sSelectedGrid = nullptr; + sSelectedItem = nullptr; + sSelectedCell = nullptr; + sSelectedCombobox = nullptr; + sSelectedRow = -1; + sSelectedCol = -1; + mNumericValueEditor->setVisible(false); + mAxisInputSelector->setVisible(false); + mAxisOutputSelector->setVisible(false); + mButtonInputSelector->setVisible(false); + mAxisInputGlyphSelector->setVisible(false); + mButtonInputGlyphSelector->setVisible(false); + mAxisOutputGlyphSelector->setVisible(false); +} + +// Resets the current mode's axis + button action mappings to the built-in defaults. +void LLPanelPreferenceGameControl::onResetActionsToDefaults() +{ + clearSelectionState(); + + std::string mode = currentEditMode(); + LLSD defaults = LLGameControl::getDefaultModeMappings(); + LLGameControl::setModeMapping(mode, MODE_INPUT_TYPE_AXES, defaults[mode][MODE_INPUT_TYPE_AXES]); + LLGameControl::setModeMapping(mode, MODE_INPUT_TYPE_BUTTONS, defaults[mode][MODE_INPUT_TYPE_BUTTONS]); + LLGameControl::setModeMapping(mode, MODE_INPUT_TYPE_AXES_INVERT, defaults[mode][MODE_INPUT_TYPE_AXES_INVERT]); + if (mode == "FlyCam") + { + // AllowRoll, SpeedFactor, and ShowCrosshair live outside the + // Axes/Buttons/AxesInvert mapping this function otherwise restores, so + // they need to be reset explicitly. + LLGameControl::setFlycamRollAllowed(false); + LLGameControl::setFlycamSpeedFactor(1.0f); + LLGameControl::setFlycamCrosshairEnabled(false); + } + populateActionMappings(); + updateActionModeEnabledUI(); // syncs mCheckFlycamAllowRoll with the reset value above + + // Push the pending UI state into LLGameControl's runtime so the effect is + // immediate. gSavedSettings is updated later via saveSettings() on OK. + LLGameControl::applySettingsFromLLSD(getSettingsAsLLSD()); +} + +// Resets the selected device's options (axis/button remap, invert, deadzone, offset) +// to defaults. +void LLPanelPreferenceGameControl::onResetDeviceToDefaults() +{ + clearSelectionState(); + if (mSelectedDeviceGUID.empty()) + { + return; + } + + LLGameControl::Options& options = getSelectedDeviceOptions(); + options.resetToDefaults(); + LLGameControl::setDeviceOptions(mSelectedDeviceGUID, options); + + populateAxisChannelsCells(); + populateButtonChannelsCells(); + // Invert/offset/dead-zone now live on the Device State tab; refresh them too + // (a no-op unless the reset device is the one shown there). + populateAxisStateOptionCells(); + + LLGameControl::applySettingsFromLLSD(getSettingsAsLLSD()); +} + +// Resets the state-tab device's per-axis tuning (Invert / Offset / Dead Zone) to +// defaults for every axis, leaving the axis/button remaps untouched. This backs +// the Device Options tab's "Restore Defaults" button. +void LLPanelPreferenceGameControl::onResetDeviceOptionsToDefaults() +{ + clearSelectionState(); + auto options_it = mDeviceOptions.find(mStateSelectedDeviceGUID); + if (options_it == mDeviceOptions.end()) + { + return; + } + + LLGameControl::Options& options = options_it->second.options; + for (LLGameControl::Options::AxisOptions& axis_options : options.getAxisOptions()) + { + axis_options.resetToDefaults(); + } + LLGameControl::setDeviceOptions(mStateSelectedDeviceGUID, options); + + populateAxisStateOptionCells(); + + LLGameControl::applySettingsFromLLSD(getSettingsAsLLSD()); +} + +// Starts a new auto-calibration pass for the state tab's selected device: clears any +// previously-collected samples and lets updateAutoCalibration() (called from draw()) +// gather CALIBRATION_SAMPLE_COUNT raw-axis readings, one per frame while the Device +// Options tab is visible. +void LLPanelPreferenceGameControl::onAutoCalibrate() +{ + clearSelectionState(); + if (mStateSelectedDeviceGUID.empty()) + { + return; + } + + mCalibrationSamples.assign(LLGameControl::NUM_AXES, {}); + mCalibrationSamplesCollected = 0; + mCalibrating = true; +} + +// Advances an in-progress auto-calibration pass by one frame: records the state +// device's current raw axis readings, and once CALIBRATION_SAMPLE_COUNT frames have +// been sampled, derives Offset/Dead Zone for every axis whose readings never changed +// across all samples (i.e. the axis wasn't being manipulated, so its readings are its +// natural "zero input" state). Axes that changed are left untouched. +void LLPanelPreferenceGameControl::updateAutoCalibration() +{ + // Locate the live state for the selected device by GUID (same lookup as + // populateDeviceStateValues()). + const LLGameControl::State* state = nullptr; + for (const LLGameControl::Device& device : LLGameControl::getDevices()) + { + if (device.getGUID() == mStateSelectedDeviceGUID) + { + state = &device.getState(); + break; + } + } + if (!state) + { + mCalibrating = false; + return; + } + + for (size_t i = 0; i < mCalibrationSamples.size() && i < state->mPhysicalRawAxes.size(); ++i) + { + mCalibrationSamples[i].push_back(state->mPhysicalRawAxes[i]); + } + ++mCalibrationSamplesCollected; + if (mCalibrationSamplesCollected < CALIBRATION_SAMPLE_COUNT) + { + return; + } + + mCalibrating = false; + + auto options_it = mDeviceOptions.find(mStateSelectedDeviceGUID); + if (options_it != mDeviceOptions.end()) + { + std::vector& all_axis_options = options_it->second.options.getAxisOptions(); + for (size_t i = 0; i < mCalibrationSamples.size() && i < all_axis_options.size(); ++i) + { + const std::vector& samples = mCalibrationSamples[i]; + bool steady = !samples.empty(); + for (size_t s = 1; s < samples.size() && steady; ++s) + { + steady = (samples[s] == samples[0]); + } + if (!steady) + { + // Axis moved during the measurement window: the user is manipulating + // it, so leave its Offset/Dead Zone untouched. + continue; + } + + S32 offset = std::clamp(-(S32)samples[0], -(S32)(LLGameControl::MAX_AXIS_OFFSET), (S32)(LLGameControl::MAX_AXIS_OFFSET)); + S32 dead_zone = std::clamp(2 * std::abs(offset), (S32)(LLGameControl::DEFAULT_AXIS_DEAD_ZONE), (S32)(LLGameControl::MAX_AXIS_DEAD_ZONE)); + + LLGameControl::Options::AxisOptions& axis_options = all_axis_options[i]; + axis_options.mOffset = (S16)offset; + axis_options.mDeadZone = (U16)dead_zone; + } + LLGameControl::setDeviceOptions(mStateSelectedDeviceGUID, options_it->second.options); + + populateAxisStateOptionCells(); + + LLGameControl::applySettingsFromLLSD(getSettingsAsLLSD()); + } + + mCalibrationSamples.clear(); + mCalibrationSamplesCollected = 0; +} + +// Captures current settings values into mOrigSettings for later restoration upon cancel(). +void LLPanelPreferenceGameControl::rememberOriginalSettings() +{ + mOrigSettings = LLGameControl::getSettingsAsLLSD(); +} diff --git a/indra/newview/llpanelpreferencegamecontrol.h b/indra/newview/llpanelpreferencegamecontrol.h new file mode 100644 index 00000000000..9fba6bbb3d6 --- /dev/null +++ b/indra/newview/llpanelpreferencegamecontrol.h @@ -0,0 +1,271 @@ +/** + * @file llpanelpreferencegamecontrol.h + * @brief LLPanelPreferenceGameControl class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "llfloaterpreference.h" +#include "llgamecontrol.h" +#include "llscrolllistcell.h" +#include "llscrolllistctrl.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLPanel; +class LLSliderCtrl; +class LLSpinCtrl; +class LLTextBox; + +//------------------------LLPanelPreferenceGameControl-------------------------------- +// +// Preference panel for configuring game controller (gamepad) input. +// +// Two visible sub-tabs: +// - Actions (global, per mode Avatar/Mouselook/FlyCam/Captive/Cursor): one table binding +// each mode's axis and button actions to a canonical input. Independent of device. +// - Devices (per device): normalizes physical hardware to canonical inputs -- +// an axis-channels table and a button-channels table, each mapping the physical +// input (icon + text) to the canonical output (icon + text). Axis tuning +// (invert/offset/dead zone) lives on the Device State tab. +// +// All settings are stored under a single "GameControl" key: +// GameControl/ModeMappings//{Axes,Buttons} - GLOBAL action -> input +// GameControl/Devices//Config - serialized per-device options +// +class LLPanelPreferenceGameControl : public LLPanelPreference +{ + LOG_CLASS(LLPanelPreferenceGameControl); +public: + + // Called when a device is connected/disconnected + static void updateDeviceList(); + + LLPanelPreferenceGameControl(); + ~LLPanelPreferenceGameControl(); + + void onOpen(const LLSD& key) override; + void draw() override; + + void apply() override; + void cancel(const std::vector settings_to_skip = {}) override; + void saveSettings() override; + + // Snapshots the panel's UI state as the single "GameControl" setting map. + // The result can be fed to LLGameControl::applySettingsFromLLSD() + // to push pending changes into the runtime without touching gSavedSettings. + LLSD getSettingsAsLLSD(); + + void updateDeviceListInternal(); // Refresh device list and settings + + // UI event handlers + void onGridSelect(LLUICtrl* ctrl); // Handle table row selection + void onCommitInputChannel(LLUICtrl* ctrl); // Handle input/output combobox selection + void onCommitNumericValue(); // Handle deadzone/offset value changes + + // Live input capture support + static bool isWaitingForInputChannel(); // True if a cell is waiting for input + static void applyGameControlInput(); // Assign detected input to selected cell + + // Data Output tab: refresh the Value column with the channel values packed into + // the most recent outgoing GameControlInput message. Called from the send path. + static void updateDataOutput(); + +protected: + bool postBuild() override; + + // Actions tab (global, per-mode) population. + void populateActionMappings(); // Rebuild the action table for the current mode + LLGameControl::Options& getSelectedDeviceOptions(); // Get options for selected device + + // Devices tab (per-device) population. + void populateDeviceTitle(); // Update device selector UI + void populateDeviceSettings(const std::string& guid); // Load settings for device + void populateAxisChannelsRows(); // Create axis-channel rows (one per physical axis) + void populateAxisChannelsCells(); // Fill axis-channel invert/deadzone/offset/output + void populateButtonChannelsRows(); // Create button-channel rows (one per physical button) + void populateButtonChannelsCells(); // Fill button-channel output column + + // Device State tab (per-device, live read-only) population. + void updateDeviceStateList(); // Rebuild the state device selector from connected devices + void populateAxisStateRows(); // Create axis-state rows (one per physical axis) + void populateAxisStateOptionCells();// Fill the state-tab axis option cells (invert/offset/dead zone) from the state device + void populateButtonStateRows(); // Create button-state rows (one per physical button) + void populateDeviceStateValues(); // Fill the Value columns from the selected device's live state + + // Data Output tab (live, read-only view of the last outgoing GameControlInput). + void populateDataOutputRows(); // Create the AXIS_/BUTTON_ label rows (with a blank separator) + void populateDataOutputSemanticRows(LLGameControl::AgentControlMode mode); // Rebuild the mode-dependent semantic block + void populateDataOutputValues(); // Fill the Value column from the last outgoing server state + + // Utility methods + // Column index of a named axis-state column. Lets the code address the + // Device State axis table by column name, so the XML column order is the + // single source of truth: columns can be reordered in the XML with no code + // change here. Returns -1 if no such column exists. + S32 axisStateColumn(const std::string& name) const; + static void setNumericLabel(LLScrollListCell* cell, S32 value); // Format numeric cell + static void fitInRect(LLUICtrl* ctrl, LLScrollListCtrl* grid, S32 row_index, S32 col_index); // Position editor over cell + +private: + // Inline editing support + bool initCombobox(LLScrollListItem* item, LLScrollListCtrl* grid); // Show combobox over cell + void clearSelectionState(); // Hide editors and clear selection tracking + + // Actions-tab helpers + std::string currentEditMode(); // mode string ("Avatar"/"Mouselook"/"FlyCam"/"Captive"/"Cursor") from mActionMode + void onActionModeChanged(); // rebuild the action table when the edit-mode selector changes + void onModeEnabledToggled(bool enabled); // toggle conversion for the currently-selected mode + void updateActionModeEnabledUI(); // sync the Enabled checkbox and lock/unlock the tables + void updateCurrentModeIndicator(); //sync mode indicator + LLComboBox* actionSelectorForMode(bool axis, const std::string& mode) const; // default vs flycam selector + void removeDuplicateActionInput(const std::string& mode, const std::string& kind, + const std::string& keep_action, const std::string& input_value, const LLComboBox* input_selector); + static std::string inputLabel(const LLComboBox* input_selector, const std::string& input_value); // value -> label + static std::string selectorLabelAt(const LLComboBox* selector, S32 index); // label of item at index + // Fill glyph_selector with one item per value in text_selector, each labelled + // with that input's PromptFont glyph (or its text label where no glyph exists). + static void buildInputGlyphSelector(const LLComboBox* text_selector, LLComboBox* glyph_selector); + + // Handlers for individual table selections/commits + void onAxisChannelsSelect(); // output popup (Device Options tab) + void onButtonChannelsSelect(); // output popup + void onAxisStateSelect(); // invert checkbox / offset-deadzone editor (Device State tab) + + // Reset to defaults handlers + void onResetActionsToDefaults(); // Reset current mode's action mappings + void onResetDeviceToDefaults(); // Reset selected device's options (remap/invert/deadzone/offset) + void onResetDeviceOptionsToDefaults(); // Reset state device's per-axis tuning (invert/offset/dead zone) + + // Auto-calibration: samples the state device's raw axes over several frames and + // derives Offset/Dead Zone for axes that don't move. See updateAutoCalibration(). + void onAutoCalibrate(); // Kick off a new calibration pass + void updateAutoCalibration(); // Advance the in-progress pass; called once per frame from draw() + + void rememberOriginalSettings(); // Capture settings for cancel restoration + + // Sends game_control data to server + LLCheckBoxCtrl* mCheckGameControlToServer { nullptr }; + + // Sub-tab panels + LLPanel* mTabActions { nullptr }; + LLPanel* mTabDevices { nullptr }; + + // Actions tab + LLComboBox* mActionMode { nullptr }; // selects which mode's mappings are edited + LLCheckBoxCtrl* mCheckActionModeEnabled { nullptr }; // enables/disables the selected mode + LLCheckBoxCtrl* mCheckFlycamAllowRoll { nullptr }; // FlyCam-only: allows Roll input; hidden for other modes + LLSliderCtrl* mSliderFlycamSpeedFactor { nullptr }; // FlyCam-only: "Slow".."Fast" move-speed multiplier; hidden for other modes + LLCheckBoxCtrl* mCheckFlycamCrosshair { nullptr }; // FlyCam-only: draws a centered crosshair while flying; hidden for other modes + LLButton* mRestoreActionsDefaults { nullptr }; + LLTextBox* mCurrentModeIndicator { nullptr }; //mode indicator + LLGameControl::AgentControlMode mLastDrawnControlMode { LLGameControl::CONTROL_MODE_NONE }; + LLScrollListCtrl* mActionMappingsAxes { nullptr }; // Action | Axis Input (axis action block) + LLScrollListCtrl* mActionMappingsButtons { nullptr }; // Action | Buttons Input (button action block) + + // Devices tab + LLTextBox* mNoDeviceMessage { nullptr }; // Shown when no devices connected + LLTextBox* mDevicePrompt { nullptr }; // "Device:" label + LLComboBox* mDeviceList { nullptr }; // Dropdown listing available devices + LLCheckBoxCtrl* mCheckShowAllDevices { nullptr }; // Include disconnected devices + LLPanel* mPanelDeviceSettings { nullptr }; + LLButton* mRestoreDeviceDefaults { nullptr }; + LLScrollListCtrl* mAxisChannels { nullptr }; // input(glyph) | input_description | output_description | output(glyph) + LLScrollListCtrl* mButtonChannels { nullptr }; // input(glyph) | input_description | output_description | output(glyph) + + // Device State tab (per-device, live read-only) + LLPanel* mTabDeviceState { nullptr }; + LLTextBox* mStateNoDeviceMessage { nullptr }; // Shown when no devices connected + LLTextBox* mStateDevicePrompt { nullptr }; // "Device:" label + LLTextBox* mStateRemapNote { nullptr }; // Note that values are post-remap + LLComboBox* mStateDeviceList { nullptr }; // Dropdown listing connected devices + LLButton* mRestoreDeviceOptionsDefaults { nullptr }; // Restore per-axis tuning to defaults + LLButton* mAutoCalibrate { nullptr }; // Kicks off automatic offset/dead-zone calibration + LLPanel* mPanelDeviceState { nullptr }; // Wrapper holding the two state tables + LLScrollListCtrl* mAxisState { nullptr }; // Input(glyph) | Axis | Raw/Adjusted values | invert/offset/dead-zone + LLScrollListCtrl* mButtonState { nullptr }; // Input(glyph) | Button | Value (live pressed state) + std::string mStateSelectedDeviceGUID; // GUID of the device shown in the state tab + + // Auto-calibration: in-progress sample capture kicked off by mAutoCalibrate. + // updateAutoCalibration() (called from draw()) appends one raw-axis sample per + // physical axis each frame; once CALIBRATION_SAMPLE_COUNT samples are collected, + // any axis whose samples were all equal is treated as being at rest, and its + // Offset/Dead Zone are derived from that reading. + static constexpr S32 CALIBRATION_SAMPLE_COUNT = 5; + bool mCalibrating { false }; + S32 mCalibrationSamplesCollected { 0 }; + std::vector> mCalibrationSamples; // per physical axis, up to CALIBRATION_SAMPLE_COUNT raw samples + + // Data Output tab (live, read-only view of the last outgoing GameControlInput) + LLPanel* mTabDataOutput { nullptr }; + LLScrollListCtrl* mDataOutput { nullptr }; // Output | Value (last sent axis/button values) + + // AgentControlMode (as an S32; -1 == none yet) the semantic block of mDataOutput + // was last built for. populateDataOutputValues() rebuilds that block via + // populateDataOutputSemanticRows() whenever the live ServerState::mActionMode no + // longer matches, since both row count and labels are mode-dependent. + S32 mDataOutputSemanticMode { -1 }; + + // Inline editors - positioned over table cells when editing + LLSpinCtrl* mNumericValueEditor { nullptr }; // For deadzone/offset values + LLComboBox* mAxisInputSelector { nullptr }; // Axis-action Input popup (sticks + "Triggers left/right" pair) + LLComboBox* mAxisOutputSelector { nullptr }; // Device axis Output popup (individual canonical axes, index-based) + LLComboBox* mButtonInputSelector { nullptr }; // Canonical buttons (action Input + button Output popups) + + // Glyph counterparts of the two action-Input selectors above, shown when the + // user edits the PromptFont icon ("Axis"/"Button") column instead of the text + // "input_description" column. Same values, but each item is rendered as its + // PromptFont glyph. Built in postBuild() from the text selectors' values. + LLComboBox* mAxisInputGlyphSelector { nullptr }; + LLComboBox* mButtonInputGlyphSelector { nullptr }; + + // Glyph counterpart of mAxisOutputSelector, shown when the user edits the + // PromptFont icon ("output") column of the Devices-tab axis-channels table + // instead of the text "output_description" column. Built in postBuild(). + LLComboBox* mAxisOutputGlyphSelector { nullptr }; + + // Action selectors: source of the "Action" column rows, chosen by edit mode. + LLComboBox* mAnalogActionSelector { nullptr }; // Avatar axis actions + LLComboBox* mBinaryActionSelector { nullptr }; // Avatar button actions + LLComboBox* mMouselookAnalogActionSelector { nullptr }; // Mouselook axis actions + LLComboBox* mMouselookBinaryActionSelector { nullptr }; // Mouselook button actions + LLComboBox* mFlycamAnalogActionSelector { nullptr }; // FlyCam axis actions + LLComboBox* mFlycamBinaryActionSelector { nullptr }; // FlyCam button actions + LLComboBox* mCaptiveBinaryActionSelector { nullptr }; // Captive (sitting) button actions + LLComboBox* mCursorAnalogActionSelector { nullptr }; // Cursor-mode axis actions + LLComboBox* mCursorBinaryActionSelector { nullptr }; // Cursor-mode button actions + + // Per-device options storage + struct DeviceOptions + { + std::string name; // Device display name + std::string settings; // Serialized settings string + LLGameControl::Options options; // Parsed options (axis settings, mappings) + }; + std::map mDeviceOptions; // Keyed by device GUID + std::string mSelectedDeviceGUID; // Currently selected device + + LLSD mOrigSettings; // Captured settings for cancel restoration +}; diff --git a/indra/newview/llscriptruntimeperms.h b/indra/newview/llscriptruntimeperms.h index 6350b61d082..3175dabcd8d 100644 --- a/indra/newview/llscriptruntimeperms.h +++ b/indra/newview/llscriptruntimeperms.h @@ -34,7 +34,7 @@ typedef struct _script_perm { question(q), permbit(b), caution(c) {} } script_perm_t; -const U32 NUM_SCRIPT_PERMISSIONS = 19; +const U32 NUM_SCRIPT_PERMISSIONS = 20; const S32 SCRIPT_PERMISSION_DEBIT = 0; const S32 SCRIPT_PERMISSION_TRIGGER_ANIMATION = 3; const S32 SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS = 14; @@ -58,6 +58,7 @@ static const std::array SCRIPT_PERMISSION _script_perm("ScriptReturnObjects", (0x1 << 16), false), _script_perm("ForceSitAvatar", (0x1 << 17), false), _script_perm("ChangeEnvSettings", (0x1 << 18), false), - _script_perm("PrivilegedLandAccess",(0x1 << 19), true) + _script_perm("PrivilegedLandAccess",(0x1 << 19), true), + _script_perm("GameControl", (0x1 << 20), false) } }; diff --git a/indra/newview/lltool.cpp b/indra/newview/lltool.cpp index c595fc42ca8..49c5bf27cdf 100644 --- a/indra/newview/lltool.cpp +++ b/indra/newview/lltool.cpp @@ -193,7 +193,7 @@ bool LLTool::handleKey(KEY key, MASK mask) LLTool* LLTool::getOverrideTool(MASK mask) { // NOTE: if in flycam mode, ALT-ZOOM camera should be disabled - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return NULL; } diff --git a/indra/newview/lltoolfocus.cpp b/indra/newview/lltoolfocus.cpp index b8878b1e6fc..a93f37e09a1 100644 --- a/indra/newview/lltoolfocus.cpp +++ b/indra/newview/lltoolfocus.cpp @@ -62,6 +62,8 @@ bool gCameraBtnPan = false; const S32 SLOP_RANGE = 4; +extern bool agent_push_forward(const LLKeyPressState& s); + // // Camera - shared functionality // @@ -80,8 +82,10 @@ LLToolCamera::LLToolCamera() mMouseSteering(false), mMouseUpX(0), mMouseUpY(0), - mMouseUpMask(MASK_NONE) -{ } + mMouseUpMask(MASK_NONE), + mKeyState(LLKeyPressState(false,false,false,1.0)) +{ +} LLToolCamera::~LLToolCamera() @@ -337,6 +341,12 @@ bool LLToolCamera::handleMouseUp(S32 x, S32 y, MASK mask) bool LLToolCamera::handleHover(S32 x, S32 y, MASK mask) { + if (gViewerWindow->getRightMouseDown()) + { + mKeyState.mState = KEYSTATE_LEVEL; + agent_push_forward(mKeyState); + } + S32 dx = gViewerWindow->getCurrentMouseDX(); S32 dy = gViewerWindow->getCurrentMouseDY(); @@ -471,8 +481,36 @@ bool LLToolCamera::handleHover(S32 x, S32 y, MASK mask) return true; } +bool LLToolCamera::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if(mMouseSteering) + { + mKeyState.mState = KEYSTATE_DOWN; + agent_push_forward(mKeyState); + return true; + } + else + { + return false; + } +} + +bool LLToolCamera::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + if (mMouseSteering) + { + mKeyState.mState = KEYSTATE_UP; + agent_push_forward(mKeyState); + return true; + } + else + { + return false; + } +} void LLToolCamera::onMouseCaptureLost() { releaseMouse(); + handleRightMouseUp(0, 0, 0); } diff --git a/indra/newview/lltoolfocus.h b/indra/newview/lltoolfocus.h index c2460e7aa42..56a7501f509 100644 --- a/indra/newview/lltoolfocus.h +++ b/indra/newview/lltoolfocus.h @@ -27,20 +27,23 @@ #ifndef LL_LLTOOLFOCUS_H #define LL_LLTOOLFOCUS_H +#include "llkeyboard.h" #include "lltool.h" class LLPickInfo; class LLToolCamera -: public LLTool, public LLSingleton +: public LLTool, public LLSimpleton { - LLSINGLETON(LLToolCamera); - virtual ~LLToolCamera(); public: + LLToolCamera(); + virtual ~LLToolCamera(); virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; virtual void onMouseCaptureLost() override; @@ -72,6 +75,7 @@ class LLToolCamera S32 mMouseUpX; // needed for releaseMouse() S32 mMouseUpY; MASK mMouseUpMask; + LLKeyPressState mKeyState; }; diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index 5741d7f5fb5..5989637ce9d 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -35,6 +35,7 @@ #include "llagentcamera.h" #include "llfloaterimnearbychat.h" #include "llfocusmgr.h" +#include "llgamecontrol.h" #include "llkeybind.h" // LLKeyData #include "llmorphview.h" #include "llmoveview.h" @@ -47,6 +48,7 @@ #include "llfloatercamera.h" #include "llinitparam.h" #include "llselectmgr.h" +#include "llviewercamera.h" // // Constants @@ -61,15 +63,18 @@ const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed const LLKeyData agent_control_lbutton(CLICK_LEFT, KEY_NONE, MASK_NONE, true); +static U64 g_lastUpdateTime { 0 }; +static F32 g_deltaTime { 0.0f }; + struct LLKeybindFunctionData { - LLKeybindFunctionData(std::function function, bool global) + LLKeybindFunctionData(LLKeyFunc function, bool global) : mFunction(function), mIsGlobal(global) { } - std::function mFunction; + LLKeyFunc mFunction; // todo: might be good idea to make this into enum, like: global/inworld/menu bool mIsGlobal; }; @@ -82,16 +87,16 @@ struct LLKeyboardActionRegistry LLViewerInput gViewerInput; -bool agent_jump( EKeystate s ) +bool agent_jump( const LLKeyPressState& s ) { static bool first_fly_attempt(true); - if (KEYSTATE_UP == s) + if (KEYSTATE_UP == s.mState) { first_fly_attempt = true; return true; } - F32 time = gKeyboard->getCurKeyElapsedTime(); - S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); + F32 time = s.mElapsedTime; + S32 frame_count = s.mElapsedFrames; if( time < FLY_TIME || frame_count <= FLY_FRAMES @@ -109,9 +114,22 @@ bool agent_jump( EKeystate s ) return true; } -bool agent_push_down( EKeystate s ) +bool agent_push_down( const LLKeyPressState& s ) +{ + if( KEYSTATE_UP == s.mState ) return true; + gAgent.moveUp(-1); + return true; +} + +bool agent_toggle_down( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; + + if (KEYSTATE_DOWN == s.mState + && !gAgent.getFlying()) + { + gAgent.toggleCrouch(); + } gAgent.moveUp(-1); return true; } @@ -128,16 +146,16 @@ static void agent_check_temporary_run(LLAgent::EDoubleTapRunMode mode) } } -static void agent_handle_doubletap_run(EKeystate s, LLAgent::EDoubleTapRunMode mode) +static void agent_handle_doubletap_run(const LLKeyPressState& s, LLAgent::EDoubleTapRunMode mode) { - if (KEYSTATE_UP == s) + if (KEYSTATE_UP == s.mState) { // Note: in case shift is already released, slide left/right run // will be released in agent_turn_left()/agent_turn_right() agent_check_temporary_run(mode); } else if (gSavedSettings.getBOOL("AllowTapTapHoldRun") && - KEYSTATE_DOWN == s && + KEYSTATE_DOWN == s.mState && !gAgent.getRunning()) { if (gAgent.mDoubleTapRunMode == mode && @@ -155,13 +173,13 @@ static void agent_handle_doubletap_run(EKeystate s, LLAgent::EDoubleTapRunMode m } } -static void agent_push_forwardbackward( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode ) +static void agent_push_forwardbackward( const LLKeyPressState& s, S32 direction, LLAgent::EDoubleTapRunMode mode ) { agent_handle_doubletap_run(s, mode); - if (KEYSTATE_UP == s) return; + if (KEYSTATE_UP == s.mState) return; - F32 time = gKeyboard->getCurKeyElapsedTime(); - S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); + F32 time = s.mElapsedTime; + S32 frame_count = s.mElapsedFrames; if( time < NUDGE_TIME || frame_count <= NUDGE_FRAMES) { @@ -173,9 +191,9 @@ static void agent_push_forwardbackward( EKeystate s, S32 direction, LLAgent::EDo } } -bool camera_move_forward( EKeystate s ); +bool camera_move_forward( const LLKeyPressState& s ); -bool agent_push_forward( EKeystate s ) +bool agent_push_forward( const LLKeyPressState& s ) { if(gAgent.isMovementLocked()) return true; @@ -191,9 +209,9 @@ bool agent_push_forward( EKeystate s ) return true; } -bool camera_move_backward( EKeystate s ); +bool camera_move_backward( const LLKeyPressState& s ); -bool agent_push_backward( EKeystate s ) +bool agent_push_backward( const LLKeyPressState& s ) { if(gAgent.isMovementLocked()) return true; @@ -213,12 +231,12 @@ bool agent_push_backward( EKeystate s ) return true; } -static void agent_slide_leftright( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode ) +static void agent_slide_leftright( const LLKeyPressState& s, S32 direction, LLAgent::EDoubleTapRunMode mode ) { agent_handle_doubletap_run(s, mode); - if( KEYSTATE_UP == s ) return; - F32 time = gKeyboard->getCurKeyElapsedTime(); - S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); + if( KEYSTATE_UP == s.mState ) return; + F32 time = s.mElapsedTime; + S32 frame_count = s.mElapsedFrames; if( time < NUDGE_TIME || frame_count <= NUDGE_FRAMES) { @@ -231,7 +249,7 @@ static void agent_slide_leftright( EKeystate s, S32 direction, LLAgent::EDoubleT } -bool agent_slide_left( EKeystate s ) +bool agent_slide_left( const LLKeyPressState& s ) { if(gAgent.isMovementLocked()) return true; agent_slide_leftright(s, 1, LLAgent::DOUBLETAP_SLIDELEFT); @@ -239,16 +257,16 @@ bool agent_slide_left( EKeystate s ) } -bool agent_slide_right( EKeystate s ) +bool agent_slide_right( const LLKeyPressState& s ) { if(gAgent.isMovementLocked()) return true; agent_slide_leftright(s, -1, LLAgent::DOUBLETAP_SLIDERIGHT); return true; } -bool camera_spin_around_cw( EKeystate s ); +bool camera_spin_around_cw( const LLKeyPressState& s ); -bool agent_turn_left(EKeystate s) +bool agent_turn_left(const LLKeyPressState& s) { //in free camera control mode we need to intercept keyboard events for avatar movements if (LLFloaterCamera::inFreeCameraMode()) @@ -265,21 +283,21 @@ bool agent_turn_left(EKeystate s) } else { - if (KEYSTATE_UP == s) + if (KEYSTATE_UP == s.mState) { // Check temporary running. In case user released 'left' key with shift already released. agent_check_temporary_run(LLAgent::DOUBLETAP_SLIDELEFT); return true; } - F32 time = gKeyboard->getCurKeyElapsedTime(); - gAgent.moveYaw( LLFloaterMove::getYawRate( time ) ); + F32 time = s.mElapsedTime; + gAgent.moveYaw( LLFloaterMove::getYawRate( time ) * s.mValue ); } return true; } -bool camera_spin_around_ccw( EKeystate s ); +bool camera_spin_around_ccw( const LLKeyPressState& s ); -bool agent_turn_right( EKeystate s ) +bool agent_turn_right( const LLKeyPressState& s ) { //in free camera control mode we need to intercept keyboard events for avatar movements if (LLFloaterCamera::inFreeCameraMode()) @@ -296,48 +314,58 @@ bool agent_turn_right( EKeystate s ) } else { - if (KEYSTATE_UP == s) + if (KEYSTATE_UP == s.mState) { // Check temporary running. In case user released 'right' key with shift already released. agent_check_temporary_run(LLAgent::DOUBLETAP_SLIDERIGHT); return true; } - F32 time = gKeyboard->getCurKeyElapsedTime(); - gAgent.moveYaw( -LLFloaterMove::getYawRate( time ) ); + F32 time = s.mElapsedTime; + gAgent.moveYaw( -LLFloaterMove::getYawRate( time ) * s.mValue ); } return true; } -bool agent_look_up( EKeystate s ) +bool agent_look_up( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgent.movePitch(-1); //gAgent.rotate(-2.f * DEG_TO_RAD, gAgent.getFrame().getLeftAxis() ); return true; } -bool agent_look_down( EKeystate s ) +bool agent_look_down( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgent.movePitch(1); //gAgent.rotate(2.f * DEG_TO_RAD, gAgent.getFrame().getLeftAxis() ); return true; } -bool agent_toggle_fly( EKeystate s ) +bool agent_toggle_fly( const LLKeyPressState& s ) { // Only catch the edge - if (KEYSTATE_DOWN == s ) + if (KEYSTATE_DOWN == s.mState ) { LLAgent::toggleFlying(); } return true; } -F32 get_orbit_rate() +bool agent_toggle_flycam( const LLKeyPressState& s ) +{ + // Only catch the edge + if (KEYSTATE_DOWN == s.mState ) + { + gAgent.toggleFlycam(); + } + return true; +} + +F32 get_orbit_rate( const LLKeyPressState& s ) { - F32 time = gKeyboard->getCurKeyElapsedTime(); + F32 time = s.mElapsedTime; if( time < NUDGE_TIME ) { F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; @@ -350,26 +378,46 @@ F32 get_orbit_rate() } } -bool camera_spin_around_ccw( EKeystate s ) +// Keyboard-driven flycam translate/rotate: sets a per-channel rate that +// LLAgent::updateFlycam() blends in and clears every frame (same +// "set while held, cleared once consumed" idiom as mOrbitInKey/mPanUpKey). +template +bool flycam_axis_key(const LLKeyPressState& s) +{ + if (KEYSTATE_UP == s.mState) return true; + gAgent.setFlycamKeyInput(CHANNEL, SIGN * get_orbit_rate(s)); + return true; +} + +bool flycam_unroll_key(const LLKeyPressState& s) { - if( KEYSTATE_UP == s ) return true; + if (KEYSTATE_DOWN == s.mState) + { + gAgent.setFlycamKeyUnroll(true); + } + return true; +} + +bool camera_spin_around_ccw( const LLKeyPressState& s ) +{ + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); + gAgentCamera.setOrbitLeftKey( get_orbit_rate(s) ); return true; } -bool camera_spin_around_cw( EKeystate s ) +bool camera_spin_around_cw( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setOrbitRightKey( get_orbit_rate() ); + gAgentCamera.setOrbitRightKey( get_orbit_rate(s) ); return true; } -bool camera_spin_around_ccw_sitting( EKeystate s ) +bool camera_spin_around_ccw_sitting( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT ) return true; + if( KEYSTATE_UP == s.mState && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT ) return true; if (gAgent.rotateGrabbed() || gAgentCamera.sitCameraEnabled() || gAgent.getRunning()) { //send keystrokes, but do not change camera @@ -379,15 +427,15 @@ bool camera_spin_around_ccw_sitting( EKeystate s ) { //change camera but do not send keystrokes gAgentCamera.unlockView(); - gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); + gAgentCamera.setOrbitLeftKey( get_orbit_rate(s) ); } return true; } -bool camera_spin_around_cw_sitting( EKeystate s ) +bool camera_spin_around_cw_sitting( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT ) return true; + if( KEYSTATE_UP == s.mState && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT ) return true; if (gAgent.rotateGrabbed() || gAgentCamera.sitCameraEnabled() || gAgent.getRunning()) { //send keystrokes, but do not change camera @@ -397,32 +445,32 @@ bool camera_spin_around_cw_sitting( EKeystate s ) { //change camera but do not send keystrokes gAgentCamera.unlockView(); - gAgentCamera.setOrbitRightKey( get_orbit_rate() ); + gAgentCamera.setOrbitRightKey( get_orbit_rate(s) ); } return true; } -bool camera_spin_over( EKeystate s ) +bool camera_spin_over( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setOrbitUpKey( get_orbit_rate() ); + gAgentCamera.setOrbitUpKey( get_orbit_rate(s) ); return true; } -bool camera_spin_under( EKeystate s ) +bool camera_spin_under( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setOrbitDownKey( get_orbit_rate() ); + gAgentCamera.setOrbitDownKey( get_orbit_rate(s) ); return true; } -bool camera_spin_over_sitting( EKeystate s ) +bool camera_spin_over_sitting( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; if (gAgent.upGrabbed() || gAgentCamera.sitCameraEnabled()) { //send keystrokes, but do not change camera @@ -431,15 +479,15 @@ bool camera_spin_over_sitting( EKeystate s ) else { //change camera but do not send keystrokes - gAgentCamera.setOrbitUpKey( get_orbit_rate() ); + gAgentCamera.setOrbitUpKey( get_orbit_rate(s) ); } return true; } -bool camera_spin_under_sitting( EKeystate s ) +bool camera_spin_under_sitting( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; if (gAgent.downGrabbed() || gAgentCamera.sitCameraEnabled()) { //send keystrokes, but do not change camera @@ -448,45 +496,68 @@ bool camera_spin_under_sitting( EKeystate s ) else { //change camera but do not send keystrokes - gAgentCamera.setOrbitDownKey( get_orbit_rate() ); + gAgentCamera.setOrbitDownKey( get_orbit_rate(s) ); } return true; } -bool camera_move_forward( EKeystate s ) +bool camera_roll_left(const LLKeyPressState& s) +{ + if (KEYSTATE_UP == s.mState) return true; + gAgentCamera.unlockView(); + gAgentCamera.setRollLeftKey(get_orbit_rate(s)); + return true; +} + +bool camera_roll_right(const LLKeyPressState& s) { - if( KEYSTATE_UP == s ) return true; + if (KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setOrbitInKey( get_orbit_rate() ); + gAgentCamera.setRollRightKey(get_orbit_rate(s)); return true; } +bool camera_roll_reset(const LLKeyPressState& s) +{ + if (KEYSTATE_UP == s.mState ) return true; + gAgentCamera.resetCameraRoll(); + return true; +} -bool camera_move_backward( EKeystate s ) +bool camera_move_forward( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setOrbitOutKey( get_orbit_rate() ); + gAgentCamera.setOrbitInKey( get_orbit_rate(s) ); return true; } -bool camera_move_forward_sitting( EKeystate s ) + +bool camera_move_backward( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD ) return true; + if( KEYSTATE_UP == s.mState ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitOutKey( get_orbit_rate(s) ); + return true; +} + +bool camera_move_forward_sitting( const LLKeyPressState& s ) +{ + if( KEYSTATE_UP == s.mState && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD ) return true; if (gAgent.forwardGrabbed() || gAgentCamera.sitCameraEnabled() || (gAgent.getRunning() && !gAgent.getAlwaysRun())) { agent_push_forward(s); } else { - gAgentCamera.setOrbitInKey( get_orbit_rate() ); + gAgentCamera.setOrbitInKey( get_orbit_rate(s) ); } return true; } -bool camera_move_backward_sitting( EKeystate s ) +bool camera_move_backward_sitting( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD ) return true; + if( KEYSTATE_UP == s.mState && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD ) return true; if (gAgent.backwardGrabbed() || gAgentCamera.sitCameraEnabled() || (gAgent.getRunning() && !gAgent.getAlwaysRun())) { @@ -494,137 +565,137 @@ bool camera_move_backward_sitting( EKeystate s ) } else { - gAgentCamera.setOrbitOutKey( get_orbit_rate() ); + gAgentCamera.setOrbitOutKey( get_orbit_rate(s) ); } return true; } -bool camera_pan_up( EKeystate s ) +bool camera_pan_up( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setPanUpKey( get_orbit_rate() ); + gAgentCamera.setPanUpKey( get_orbit_rate(s) ); return true; } -bool camera_pan_down( EKeystate s ) +bool camera_pan_down( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setPanDownKey( get_orbit_rate() ); + gAgentCamera.setPanDownKey( get_orbit_rate(s) ); return true; } -bool camera_pan_left( EKeystate s ) +bool camera_pan_left( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setPanLeftKey( get_orbit_rate() ); + gAgentCamera.setPanLeftKey( get_orbit_rate(s) ); return true; } -bool camera_pan_right( EKeystate s ) +bool camera_pan_right( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setPanRightKey( get_orbit_rate() ); + gAgentCamera.setPanRightKey( get_orbit_rate(s) ); return true; } -bool camera_pan_in( EKeystate s ) +bool camera_pan_in( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setPanInKey( get_orbit_rate() ); + gAgentCamera.setPanInKey( get_orbit_rate(s) ); return true; } -bool camera_pan_out( EKeystate s ) +bool camera_pan_out( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); - gAgentCamera.setPanOutKey( get_orbit_rate() ); + gAgentCamera.setPanOutKey( get_orbit_rate(s) ); return true; } -bool camera_move_forward_fast( EKeystate s ) +bool camera_move_forward_fast( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); gAgentCamera.setOrbitInKey(2.5f); return true; } -bool camera_move_backward_fast( EKeystate s ) +bool camera_move_backward_fast( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gAgentCamera.unlockView(); gAgentCamera.setOrbitOutKey(2.5f); return true; } -bool edit_avatar_spin_ccw( EKeystate s ) +bool edit_avatar_spin_ccw( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); - //gMorphView->orbitLeft( get_orbit_rate() ); + gAgentCamera.setOrbitLeftKey( get_orbit_rate(s) ); + //gMorphView->orbitLeft( get_orbit_rate(s) ); return true; } -bool edit_avatar_spin_cw( EKeystate s ) +bool edit_avatar_spin_cw( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitRightKey( get_orbit_rate() ); - //gMorphView->orbitRight( get_orbit_rate() ); + gAgentCamera.setOrbitRightKey( get_orbit_rate(s) ); + //gMorphView->orbitRight( get_orbit_rate(s) ); return true; } -bool edit_avatar_spin_over( EKeystate s ) +bool edit_avatar_spin_over( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitUpKey( get_orbit_rate() ); - //gMorphView->orbitUp( get_orbit_rate() ); + gAgentCamera.setOrbitUpKey( get_orbit_rate(s) ); + //gMorphView->orbitUp( get_orbit_rate(s) ); return true; } -bool edit_avatar_spin_under( EKeystate s ) +bool edit_avatar_spin_under( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitDownKey( get_orbit_rate() ); - //gMorphView->orbitDown( get_orbit_rate() ); + gAgentCamera.setOrbitDownKey( get_orbit_rate(s) ); + //gMorphView->orbitDown( get_orbit_rate(s) ); return true; } -bool edit_avatar_move_forward( EKeystate s ) +bool edit_avatar_move_forward( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitInKey( get_orbit_rate() ); + gAgentCamera.setOrbitInKey( get_orbit_rate(s) ); //gMorphView->orbitIn(); return true; } -bool edit_avatar_move_backward( EKeystate s ) +bool edit_avatar_move_backward( const LLKeyPressState& s ) { - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitOutKey( get_orbit_rate() ); + gAgentCamera.setOrbitOutKey( get_orbit_rate(s) ); //gMorphView->orbitOut(); return true; } -bool stop_moving( EKeystate s ) +bool stop_moving( const LLKeyPressState& s ) { //it's supposed that 'stop moving' key will be held down for some time - if( KEYSTATE_UP == s ) return true; + if( KEYSTATE_UP == s.mState ) return true; // stop agent gAgent.setControlFlags(AGENT_CONTROL_STOP); @@ -633,23 +704,23 @@ bool stop_moving( EKeystate s ) return true; } -bool start_chat( EKeystate s ) +bool start_chat( const LLKeyPressState& s ) { if (LLAppViewer::instance()->quitRequested()) { return true; // can't talk, gotta go, kthxbye! } - if (KEYSTATE_DOWN != s) return true; + if (KEYSTATE_DOWN != s.mState) return true; // start chat LLFloaterIMNearbyChat::startChat(NULL); return true; } -bool start_gesture( EKeystate s ) +bool start_gesture( const LLKeyPressState& s ) { LLUICtrl* focus_ctrlp = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (KEYSTATE_UP == s && + if (KEYSTATE_UP == s.mState && ! (focus_ctrlp && focus_ctrlp->acceptsTextInput())) { if ((LLFloaterReg::getTypedInstance("nearby_chat"))->getCurrentChat().empty()) @@ -666,9 +737,9 @@ bool start_gesture( EKeystate s ) return true; } -bool run_forward(EKeystate s) +bool run_forward(const LLKeyPressState& s) { - if (KEYSTATE_UP != s) + if (KEYSTATE_UP != s.mState) { if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD) { @@ -680,7 +751,7 @@ bool run_forward(EKeystate s) gAgent.sendWalkRun(true); } } - else if(KEYSTATE_UP == s) + else if(KEYSTATE_UP == s.mState) { if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_FORWARD) gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; @@ -691,9 +762,9 @@ bool run_forward(EKeystate s) return true; } -bool run_backward(EKeystate s) +bool run_backward(const LLKeyPressState& s) { - if (KEYSTATE_UP != s) + if (KEYSTATE_UP != s.mState) { if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD) { @@ -705,7 +776,7 @@ bool run_backward(EKeystate s) gAgent.sendWalkRun(true); } } - else if (KEYSTATE_UP == s) + else if (KEYSTATE_UP == s.mState) { if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_BACKWARD) gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; @@ -716,9 +787,9 @@ bool run_backward(EKeystate s) return true; } -bool run_left(EKeystate s) +bool run_left(const LLKeyPressState& s) { - if (KEYSTATE_UP != s) + if (KEYSTATE_UP != s.mState) { if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT) { @@ -730,7 +801,7 @@ bool run_left(EKeystate s) gAgent.sendWalkRun(true); } } - else if (KEYSTATE_UP == s) + else if (KEYSTATE_UP == s.mState) { if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_SLIDELEFT) gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; @@ -741,9 +812,9 @@ bool run_left(EKeystate s) return true; } -bool run_right(EKeystate s) +bool run_right(const LLKeyPressState& s) { - if (KEYSTATE_UP != s) + if (KEYSTATE_UP != s.mState) { if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT) { @@ -755,7 +826,7 @@ bool run_right(EKeystate s) gAgent.sendWalkRun(true); } } - else if (KEYSTATE_UP == s) + else if (KEYSTATE_UP == s.mState) { if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_SLIDERIGHT) gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; @@ -766,9 +837,9 @@ bool run_right(EKeystate s) return true; } -bool toggle_run(EKeystate s) +bool toggle_run(const LLKeyPressState& s) { - if (KEYSTATE_DOWN != s) return true; + if (KEYSTATE_DOWN != s.mState) return true; bool run = gAgent.getAlwaysRun(); if (run) { @@ -784,9 +855,9 @@ bool toggle_run(EKeystate s) return true; } -bool toggle_sit(EKeystate s) +bool toggle_sit(const LLKeyPressState& s) { - if (KEYSTATE_DOWN != s) return true; + if (KEYSTATE_DOWN != s.mState) return true; if (gAgent.isSitting()) { gAgent.standUp(); @@ -798,25 +869,25 @@ bool toggle_sit(EKeystate s) return true; } -bool toggle_pause_media(EKeystate s) // analogue of play/pause button in top bar +bool toggle_pause_media(const LLKeyPressState& s) // analogue of play/pause button in top bar { - if (KEYSTATE_DOWN != s) return true; + if (KEYSTATE_DOWN != s.mState) return true; bool pause = LLViewerMedia::getInstance()->isAnyMediaPlaying(); LLViewerMedia::getInstance()->setAllMediaPaused(pause); return true; } -bool toggle_enable_media(EKeystate s) +bool toggle_enable_media(const LLKeyPressState& s) { - if (KEYSTATE_DOWN != s) return true; + if (KEYSTATE_DOWN != s.mState) return true; bool pause = LLViewerMedia::getInstance()->isAnyMediaPlaying() || LLViewerMedia::getInstance()->isAnyMediaShowing(); LLViewerMedia::getInstance()->setAllMediaEnabled(!pause); return true; } -bool walk_to(EKeystate s) +bool walk_to(const LLKeyPressState& s) { - if (KEYSTATE_DOWN != s) + if (KEYSTATE_DOWN != s.mState) { // teleport/walk is usually on mouseclick, mouseclick needs // to let AGENT_CONTROL_LBUTTON_UP happen if teleport didn't, @@ -827,29 +898,29 @@ bool walk_to(EKeystate s) return LLToolPie::getInstance()->walkToClickedLocation(); } -bool teleport_to(EKeystate s) +bool teleport_to(const LLKeyPressState& s) { - if (KEYSTATE_DOWN != s) return false; + if (KEYSTATE_DOWN != s.mState) return false; return LLToolPie::getInstance()->teleportToClickedLocation(); } -bool toggle_voice(EKeystate s) +bool toggle_voice(const LLKeyPressState& s) { - if (KEYSTATE_DOWN != s) return true; + if (KEYSTATE_DOWN != s.mState) return true; if (!LLAgent::isActionAllowed("speak")) return false; LLVoiceClient::getInstance()->toggleUserPTTState(); return true; } -bool voice_follow_key(EKeystate s) +bool voice_follow_key(const LLKeyPressState& s) { - if (KEYSTATE_DOWN == s) + if (KEYSTATE_DOWN == s.mState) { if (!LLAgent::isActionAllowed("speak")) return false; LLVoiceClient::getInstance()->setUserPTTState(true); return true; } - else if (KEYSTATE_UP == s && LLVoiceClient::getInstance()->getUserPTTState()) + else if (KEYSTATE_UP == s.mState && LLVoiceClient::getInstance()->getUserPTTState()) { LLVoiceClient::getInstance()->setUserPTTState(false); return true; @@ -857,7 +928,7 @@ bool voice_follow_key(EKeystate s) return false; } -bool script_trigger_lbutton(EKeystate s) +bool script_trigger_lbutton(const LLKeyPressState& s) { // Check for script overriding/expecting left mouse button. // Note that this does not pass event further and depends onto mouselook. @@ -866,7 +937,7 @@ bool script_trigger_lbutton(EKeystate s) if (gAgent.leftButtonGrabbed()) { bool mouselook = gAgentCamera.cameraMouselook(); - switch (s) + switch (s.mState) { case KEYSTATE_DOWN: if (mouselook) @@ -895,11 +966,56 @@ bool script_trigger_lbutton(EKeystate s) return false; } +bool agent_pitch(const LLKeyPressState& s, F32 direction) +{ + if(!gAgentCamera.cameraMouselook() || KEYSTATE_UP == s.mState) + { + return false; + } + // const F32 NOMINAL_MOUSE_SENSITIVITY = 0.0025f; + + // static LLCachedControl mouse_sensitivity_setting(gSavedSettings, "MouseSensitivity"); + // F32 mouse_sensitivity = clamp_rescale(mouse_sensitivity_setting, 0.f, 15.f, 0.5f, 2.75f) * NOMINAL_MOUSE_SENSITIVITY; + + const F32 fov = LLViewerCamera::getInstance()->getView() / DEFAULT_FIELD_OF_VIEW; + // pitch (vertical) + gAgent.pitch(s.mValue * fov * direction * g_deltaTime); // or -dy if InvertMouse + return true; +} + +bool agent_pitch_forward(const LLKeyPressState& s) +{ + return agent_pitch(s,1); +} + +bool agent_pitch_backward(const LLKeyPressState& s) +{ + return agent_pitch(s,-1); +} + +bool agent_spin_over( const LLKeyPressState& s ) +{ + if( KEYSTATE_UP == s.mState ) return true; + //change camera but do not send keystrokes + gAgentCamera.setOrbitUpKey( get_orbit_rate(s) ); + return true; +} + + +bool agent_spin_under( const LLKeyPressState& s ) +{ + if( KEYSTATE_UP == s.mState ) return true; + //change camera but do not send keystrokes + gAgentCamera.setOrbitDownKey( get_orbit_rate(s) ); + return true; +} + + // Used by scripts, for overriding/handling left mouse button // see mControlsTakenCount -bool agent_control_lbutton_handle(EKeystate s) +bool agent_control_lbutton_handle(const LLKeyPressState& s) { - switch (s) + switch (s.mState) { case KEYSTATE_DOWN: gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); @@ -913,6 +1029,20 @@ bool agent_control_lbutton_handle(EKeystate s) return true; } +template +bool game_control_button(const LLKeyPressState& s) +{ + if ( KEYSTATE_DOWN == s.mState ) + { + gAgent.pressGameControlButton(N); + } + else if ( KEYSTATE_UP == s.mState ) + { + gAgent.releaseGameControlButton(N); + } + return true; +} + // In-world keybindings, like walking or camera #define REGISTER_KEYBOARD_ACTION(KEY, ACTION) LLREGISTER_STATIC(LLKeyboardActionRegistry, KEY, LLKeybindFunctionData(ACTION, false)); // Global keybindings that should work even with floaters focused, like voice @@ -936,6 +1066,10 @@ REGISTER_KEYBOARD_ACTION("spin_over", camera_spin_over); REGISTER_KEYBOARD_ACTION("spin_under", camera_spin_under); REGISTER_KEYBOARD_ACTION("spin_over_sitting", camera_spin_over_sitting); REGISTER_KEYBOARD_ACTION("spin_under_sitting", camera_spin_under_sitting); +REGISTER_KEYBOARD_ACTION("pitch_forward", agent_pitch_forward); +REGISTER_KEYBOARD_ACTION("pitch_backward", agent_pitch_backward); +REGISTER_KEYBOARD_ACTION("spin_over_agent", agent_spin_over); +REGISTER_KEYBOARD_ACTION("spin_under_agent", agent_spin_under); REGISTER_KEYBOARD_ACTION("move_forward", camera_move_forward); REGISTER_KEYBOARD_ACTION("move_backward", camera_move_backward); REGISTER_KEYBOARD_ACTION("move_forward_sitting", camera_move_forward_sitting); @@ -962,6 +1096,7 @@ REGISTER_KEYBOARD_ACTION("run_backward", run_backward); REGISTER_KEYBOARD_ACTION("run_left", run_left); REGISTER_KEYBOARD_ACTION("run_right", run_right); REGISTER_KEYBOARD_ACTION("toggle_run", toggle_run); +REGISTER_KEYBOARD_ACTION("toggle_down", agent_toggle_down); REGISTER_KEYBOARD_ACTION("toggle_sit", toggle_sit); REGISTER_KEYBOARD_ACTION("toggle_pause_media", toggle_pause_media); REGISTER_KEYBOARD_ACTION("toggle_enable_media", toggle_enable_media); @@ -970,6 +1105,55 @@ REGISTER_KEYBOARD_ACTION("walk_to", walk_to); REGISTER_KEYBOARD_GLOBAL_ACTION("toggle_voice", toggle_voice); REGISTER_KEYBOARD_GLOBAL_ACTION("voice_follow_key", voice_follow_key); REGISTER_KEYBOARD_ACTION(script_mouse_handler_name, script_trigger_lbutton); +REGISTER_KEYBOARD_ACTION("roll_left", camera_roll_left); +REGISTER_KEYBOARD_ACTION("roll_right", camera_roll_right); +REGISTER_KEYBOARD_ACTION("roll_reset", camera_roll_reset); +REGISTER_KEYBOARD_ACTION("toggle_flycam", agent_toggle_flycam); +REGISTER_KEYBOARD_ACTION("flycam_move_forward", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_move_backward", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_truck_left", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_truck_right", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_move_up", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_move_down", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_yaw_left", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_yaw_right", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_pitch_up", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_pitch_down", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_roll_left", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_roll_right", (flycam_axis_key)); +REGISTER_KEYBOARD_ACTION("flycam_unroll", flycam_unroll_key); +REGISTER_KEYBOARD_ACTION("game_control_button_0", game_control_button<0>); +REGISTER_KEYBOARD_ACTION("game_control_button_1", game_control_button<1>); +REGISTER_KEYBOARD_ACTION("game_control_button_2", game_control_button<2>); +REGISTER_KEYBOARD_ACTION("game_control_button_3", game_control_button<3>); +REGISTER_KEYBOARD_ACTION("game_control_button_4", game_control_button<4>); +REGISTER_KEYBOARD_ACTION("game_control_button_5", game_control_button<5>); +REGISTER_KEYBOARD_ACTION("game_control_button_6", game_control_button<6>); +REGISTER_KEYBOARD_ACTION("game_control_button_7", game_control_button<7>); +REGISTER_KEYBOARD_ACTION("game_control_button_8", game_control_button<8>); +REGISTER_KEYBOARD_ACTION("game_control_button_9", game_control_button<9>); +REGISTER_KEYBOARD_ACTION("game_control_button_10", game_control_button<10>); +REGISTER_KEYBOARD_ACTION("game_control_button_11", game_control_button<11>); +REGISTER_KEYBOARD_ACTION("game_control_button_12", game_control_button<12>); +REGISTER_KEYBOARD_ACTION("game_control_button_13", game_control_button<13>); +REGISTER_KEYBOARD_ACTION("game_control_button_14", game_control_button<14>); +REGISTER_KEYBOARD_ACTION("game_control_button_15", game_control_button<15>); +REGISTER_KEYBOARD_ACTION("game_control_button_16", game_control_button<16>); +REGISTER_KEYBOARD_ACTION("game_control_button_17", game_control_button<17>); +REGISTER_KEYBOARD_ACTION("game_control_button_18", game_control_button<18>); +REGISTER_KEYBOARD_ACTION("game_control_button_19", game_control_button<19>); +REGISTER_KEYBOARD_ACTION("game_control_button_20", game_control_button<20>); +REGISTER_KEYBOARD_ACTION("game_control_button_21", game_control_button<21>); +REGISTER_KEYBOARD_ACTION("game_control_button_22", game_control_button<22>); +REGISTER_KEYBOARD_ACTION("game_control_button_23", game_control_button<23>); +REGISTER_KEYBOARD_ACTION("game_control_button_24", game_control_button<24>); +REGISTER_KEYBOARD_ACTION("game_control_button_25", game_control_button<25>); +REGISTER_KEYBOARD_ACTION("game_control_button_26", game_control_button<26>); +REGISTER_KEYBOARD_ACTION("game_control_button_27", game_control_button<27>); +REGISTER_KEYBOARD_ACTION("game_control_button_28", game_control_button<28>); +REGISTER_KEYBOARD_ACTION("game_control_button_29", game_control_button<29>); +REGISTER_KEYBOARD_ACTION("game_control_button_30", game_control_button<30>); +REGISTER_KEYBOARD_ACTION("game_control_button_31", game_control_button<31>); #undef REGISTER_KEYBOARD_ACTION LLViewerInput::LLViewerInput() @@ -1026,6 +1210,11 @@ bool LLViewerInput::modeFromString(const std::string& string, S32 *mode) *mode = MODE_SITTING; return true; } + else if (cmp_string == "flycam") + { + *mode = MODE_FLYCAM; + return true; + } S32 val = atoi(string.c_str()); if (val >= 0 && val < MODE_COUNT) @@ -1165,8 +1354,7 @@ bool LLViewerInput::handleGlobalBindsMouse(EMouseClickType clicktype, MASK mask, bool LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, const std::string& function_name) { size_t index; - typedef std::function function_t; - function_t function = nullptr; + LLKeyFunc function = nullptr; std::string name; // Allow remapping of F2-F12 @@ -1255,8 +1443,7 @@ bool LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, cons bool LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const MASK mask, const std::string& function_name) { size_t index; - typedef std::function function_t; - function_t function = nullptr; + LLKeyFunc function = nullptr; if (mouse == CLICK_LEFT && mask == MASK_NONE @@ -1334,11 +1521,79 @@ bool LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const return true; } +bool LLViewerInput::bindController(const S32 mode, const LLGameControl::ActionType actionType, U8 action, const std::string& function_name) +{ + size_t index; + LLKeyFunc function = nullptr; + + LLKeybindFunctionData* result = LLKeyboardActionRegistry::getValue(function_name); + if (result) + { + function = result->mFunction; + } + + if (!function) + { + LL_WARNS_ONCE() << "Can't bind mouse key to function " << function_name << ", no function with this name found" << LL_ENDL; + return false; + } + + if (mode >= MODE_COUNT) + { + LL_ERRS() << "LLKeyboard::bindKey() - unknown mode passed" << mode << LL_ENDL; + return false; + } + + // check for duplicate first and overwrite + if (result->mIsGlobal) + { + auto size = mGlobalControllerBindings[mode].size(); + for (index = 0; index < size; index++) + { + if (actionType == mGlobalControllerBindings[mode][index].mActionType && action == mGlobalControllerBindings[mode][index].mAction) + { + mGlobalControllerBindings[mode][index].mFunction = function; + return true; + } + } + } + else + { + auto size = mControllerBindings[mode].size(); + for (index = 0; index < size; index++) + { + if (actionType == mControllerBindings[mode][index].mActionType && action == mControllerBindings[mode][index].mAction) + { + mControllerBindings[mode][index].mFunction = function; + return true; + } + } + } + + LLControllerBinding bind; + bind.mActionType = actionType; + bind.mAction = action; + bind.mFunction = function; + bind.mFunctionName = function_name; + + if (result->mIsGlobal) + { + mGlobalControllerBindings[mode].push_back(bind); + } + else + { + mControllerBindings[mode].push_back(bind); + } + + return true; +} + LLViewerInput::KeyBinding::KeyBinding() : key("key"), mouse("mouse"), mask("mask"), - command("command") + command("command"), + controller("controller") {} LLViewerInput::KeyMode::KeyMode() @@ -1350,6 +1605,7 @@ LLViewerInput::Keys::Keys() third_person("third_person"), sitting("sitting"), edit_avatar("edit_avatar"), + flycam("flycam"), xml_version("xml_version", 0) {} @@ -1359,8 +1615,10 @@ void LLViewerInput::resetBindings() { mGlobalKeyBindings[i].clear(); mGlobalMouseBindings[i].clear(); + mGlobalControllerBindings[i].clear(); mKeyBindings[i].clear(); mMouseBindings[i].clear(); + mControllerBindings[i].clear(); mLMouseDefaultHandling[i] = false; } } @@ -1380,8 +1638,11 @@ S32 LLViewerInput::loadBindingsXML(const std::string& filename) binding_count += loadBindingMode(keys.third_person, MODE_THIRD_PERSON); binding_count += loadBindingMode(keys.sitting, MODE_SITTING); binding_count += loadBindingMode(keys.edit_avatar, MODE_EDIT_AVATAR); + binding_count += loadBindingMode(keys.flycam, MODE_FLYCAM); // verify version + bool needs_rewrite = false; + if (keys.xml_version < 1) { // updating from a version that was not aware of LMouse bindings @@ -1417,6 +1678,35 @@ S32 LLViewerInput::loadBindingsXML(const std::string& filename) keys.edit_avatar.bindings.add(mouse_binding); } + needs_rewrite = true; + } + + if (keys.xml_version < 2 && !keys.flycam.isProvided()) + { + // Updating from a version that predates the flycam mode: an old + // customized key_bindings.xml has no block at all, so + // without this fixup the user would get zero flycam keybindings + // (no LLKeyConflictHandler-style fallback exists in this runtime + // loader). Pull the authoritative default flycam bindings from + // the shipped app_settings file and merge them in. + std::string default_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "key_bindings.xml"); + if (default_filename != filename) + { + Keys default_keys; + LLSimpleXUIParser default_parser; + if (default_parser.readXUI(default_filename, default_keys) + && default_keys.validateBlock() + && default_keys.flycam.isProvided()) + { + keys.flycam.set(default_keys.flycam.getValue(), true); + binding_count += loadBindingMode(keys.flycam, MODE_FLYCAM); + needs_rewrite = true; + } + } + } + + if (needs_rewrite) + { // fix version keys.xml_version.set(keybindings_xml_version, true); @@ -1471,6 +1761,16 @@ bool compare_mouse_by_mask(LLMouseBinding i1, LLMouseBinding i2) return (count_masks(i1.mMask) > count_masks(i2.mMask)); } +void LLViewerInput::storeDeltaTime() +{ + // measure delta time and frame + // Note: it is possible for the deltas to be very large + // and it is the duty of the code that uses them to clamp as necessary + U64 now = LLFrameTimer::getTotalTime(); + g_deltaTime = F32(now - g_lastUpdateTime) / (F32)(USEC_PER_SEC); + g_lastUpdateTime = now; +} + S32 LLViewerInput::loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mode) { S32 binding_count = 0; @@ -1513,6 +1813,20 @@ S32 LLViewerInput::loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mo LL_WARNS_ONCE() << "There might be issues in keybindings' file" << LL_ENDL; } } + if(!processed && it->controller.isProvided() && !it->controller.getValue().empty()) + { + LLGameControl::ActionType actionType; + U8 action; + if(LLGameControl::actionFromString(it->controller.getValue(), actionType, action)) + { + bindController(mode, actionType, action, it->command); + processed = true; + } + else + { + LL_WARNS_ONCE() << "There might be issues in keybindings' file" << LL_ENDL; + } + } if (processed) { // total @@ -1529,7 +1843,11 @@ S32 LLViewerInput::loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mo EKeyboardMode LLViewerInput::getMode() const { - if ( gAgentCamera.cameraMouselook() ) + if ( gAgent.isUsingFlycam() ) + { + return MODE_FLYCAM; + } + else if ( gAgentCamera.cameraMouselook() ) { return MODE_FIRST_PERSON; } @@ -1556,29 +1874,38 @@ bool LLViewerInput::scanKey(const std::vector &binding, bool key_level, bool repeat) const { + // TODO implement time + LLKeyPressState state = LLKeyPressState(key_up, key_down, key_level, 1.0); + state.mRepeat = repeat; + for (S32 i = 0; i < binding_count; i++) { if (binding[i].mKey == key) { if ((binding[i].mMask & mask) == binding[i].mMask) { + state.mElapsedTime = gKeyboard->getCurKeyElapsedTime(); + state.mElapsedFrames = gKeyboard->getCurKeyElapsedFrameCount(); bool res = false; if (key_down && !repeat) { // ...key went down this frame, call function - res = binding[i].mFunction( KEYSTATE_DOWN ); + state.mState = KEYSTATE_DOWN; + res = binding[i].mFunction( state ); return true; } else if (key_up) { // ...key went down this frame, call function - res = binding[i].mFunction( KEYSTATE_UP ); + state.mState = KEYSTATE_UP; + res = binding[i].mFunction( state ); } else if (key_level) { // ...key held down from previous frame // Not windows, just call the function. - res = binding[i].mFunction( KEYSTATE_LEVEL ); + state.mState = KEYSTATE_LEVEL; + res = binding[i].mFunction( state ); }//if // Key+Mask combinations are supposed to be unique, so we won't find anything else return res; @@ -1613,6 +1940,82 @@ bool LLViewerInput::scanKey(KEY key, bool key_down, bool key_up, bool key_level) return res; } +bool LLViewerInput::scanControllerInput(const LLGameControl::ActionType actionType, const U8 action, const LLKeyPressState& state) const +{ + S32 mode = getMode(); + + for(auto binding : mControllerBindings[mode]) + { + if (binding.mActionType != actionType || binding.mAction != action) + { + continue; + } + + LL_WARNS() << "Controller Val " << LLGameControl::stringFromAction(actionType, action) << state.mValue << LL_ENDL; + + bool res = binding.mFunction(state); + if (state.mDown) + { + return true; + } + return res; + } + return false; +} + +void LLViewerInput::scanController() +{ + LLGameControl::computeFinalState(); + const LLGameControl::State& state = LLGameControl::getState(); + // HACK: GameControl: supply hard-coded threshold for ON/OFF zones + constexpr U16 AXIS_THRESHOLD = 32768 / 8; + + // TODO: GameControl: This is kind of inificient, it would be more efficient to + // loop the bindings and check if the keys/axes they want are active + // rather than looping all keys/axes then all bindings for each one + // But copying scanMouse pattern for now. + + LLKeyPressState keyState = LLKeyPressState(true, false, false, 0.0); + keyState.mAnalog = true; + + for(U8 i = 0;i < state.mAxes.size();i++) + { + U16 current = state.mAxes[i]; + U16 prev = state.mPrevAxes[i]; + keyState.mDown = prev < AXIS_THRESHOLD && current >= AXIS_THRESHOLD; + keyState.mLevel = prev >= AXIS_THRESHOLD && current >= AXIS_THRESHOLD; + keyState.mUp = prev >= AXIS_THRESHOLD && current < AXIS_THRESHOLD; + + if(keyState.mDown || keyState.mLevel || keyState.mUp) + { + keyState.mElapsedTime = LLGameControl::getControllerHeldTime(LLGameControl::ActionType::DOF, i); + keyState.mElapsedFrames = LLGameControl::getControllerHeldFrames(LLGameControl::ActionType::DOF, i); + keyState.mValue = std::clamp(((F32)current) / 32767.0f, 0.0f, 1.0f); + keyState.mState = keyState.mDown ? KEYSTATE_DOWN : (keyState.mLevel ? KEYSTATE_LEVEL : KEYSTATE_UP); + scanControllerInput(LLGameControl::ActionType::DOF, i, keyState); + } + } + + keyState.mAnalog = false; + for(U8 i = 0; i < LLGameControl::Button::NUM_BUTTONS; i++) + { + U32 flag = 1 << i; + bool current = (state.mButtons & flag) == flag; + bool prev = (state.mPrevButtons & flag) == flag; + if(current || prev) + { + keyState.mDown = current && !prev; + keyState.mLevel = current && prev; + keyState.mUp = !current && prev; + keyState.mElapsedTime = LLGameControl::getControllerHeldTime(LLGameControl::ActionType::BUTTON, i); + keyState.mElapsedFrames = LLGameControl::getControllerHeldFrames(LLGameControl::ActionType::BUTTON, i); + keyState.mValue = current ? 1.0f : 0.0f; + keyState.mState = keyState.mDown ? KEYSTATE_DOWN : (keyState.mLevel ? KEYSTATE_LEVEL : KEYSTATE_UP); + scanControllerInput(LLGameControl::ActionType::BUTTON, i, keyState); + } + } +} + bool LLViewerInput::handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down) { bool is_toolmgr_action = false; @@ -1705,6 +2108,7 @@ bool LLViewerInput::scanMouse( bool ignore_additional_masks ) const { + LLKeyPressState keyState = LLKeyPressState(false,false,false,1.0); for (S32 i = 0; i < binding_count; i++) { if (binding[i].mMouse == mouse && (ignore_additional_masks ? (binding[i].mMask & mask) == binding[i].mMask : binding[i].mMask == mask)) @@ -1713,20 +2117,31 @@ bool LLViewerInput::scanMouse( switch (state) { case MOUSE_STATE_DOWN: - res = binding[i].mFunction(KEYSTATE_DOWN); + keyState.mDown = true; + keyState.mState = KEYSTATE_DOWN; + res = binding[i].mFunction(keyState); break; case MOUSE_STATE_CLICK: // Button went down and up in scope of single frame // might not work best with some functions, // but some function need specific states specifically - res = binding[i].mFunction(KEYSTATE_DOWN); - res |= binding[i].mFunction(KEYSTATE_UP); + keyState.mDown = true; + keyState.mState = KEYSTATE_DOWN; + res = binding[i].mFunction(keyState); + keyState.mDown = false; + keyState.mUp = true; + keyState.mState = KEYSTATE_UP; + res |= binding[i].mFunction(keyState); break; case MOUSE_STATE_LEVEL: - res = binding[i].mFunction(KEYSTATE_LEVEL); + keyState.mLevel = true; + keyState.mState = KEYSTATE_LEVEL; + res = binding[i].mFunction(keyState); break; case MOUSE_STATE_UP: - res = binding[i].mFunction(KEYSTATE_UP); + keyState.mUp = true; + keyState.mState = KEYSTATE_UP; + res = binding[i].mFunction(keyState); break; default: break; @@ -1738,7 +2153,7 @@ bool LLViewerInput::scanMouse( return false; } -// todo: this recods key, scanMouse() triggers functions with EKeystate +// todo: this recods key, scanMouse() triggers functions with LLKeyPressState bool LLViewerInput::scanMouse(EMouseClickType click, EMouseState state) const { bool res = false; @@ -1753,6 +2168,8 @@ bool LLViewerInput::scanMouse(EMouseClickType click, EMouseState state) const // and sends AGENT_CONTROL_ML_LBUTTON_DOWN, but it only applies if ML controls // are leftButtonGrabbed(), send a normal click otherwise. + LLKeyPressState keyState = LLKeyPressState(false,false,false,1.0); + if (!res && mLMouseDefaultHandling[mode] && (mode != MODE_FIRST_PERSON || !gAgent.leftButtonGrabbed()) @@ -1762,18 +2179,27 @@ bool LLViewerInput::scanMouse(EMouseClickType click, EMouseState state) const switch (state) { case MOUSE_STATE_DOWN: - agent_control_lbutton_handle(KEYSTATE_DOWN); + keyState.mDown = true; + keyState.mState = KEYSTATE_DOWN; + agent_control_lbutton_handle(keyState); res = true; break; case MOUSE_STATE_CLICK: // might not work best with some functions, // but some function need specific states too specifically - agent_control_lbutton_handle(KEYSTATE_DOWN); - agent_control_lbutton_handle(KEYSTATE_UP); + keyState.mDown = true; + keyState.mState = KEYSTATE_DOWN; + agent_control_lbutton_handle(keyState); + keyState.mDown = false; + keyState.mUp = true; + keyState.mState = KEYSTATE_UP; + agent_control_lbutton_handle(keyState); res = true; break; case MOUSE_STATE_UP: - agent_control_lbutton_handle(KEYSTATE_UP); + keyState.mUp = true; + keyState.mState = KEYSTATE_UP; + agent_control_lbutton_handle(keyState); res = true; break; default: diff --git a/indra/newview/llviewerinput.h b/indra/newview/llviewerinput.h index 80ba99e2019..2f2860a1b52 100644 --- a/indra/newview/llviewerinput.h +++ b/indra/newview/llviewerinput.h @@ -28,9 +28,10 @@ #define LL_LLVIEWERINPUT_H #include "llkeyboard.h" // For EKeystate +#include "llgamecontrol.h" // For LLGameControl::ActionType const S32 MAX_KEY_BINDINGS = 128; // was 60 -const S32 keybindings_xml_version = 1; +const S32 keybindings_xml_version = 2; const std::string script_mouse_handler_name = "script_trigger_lbutton"; class LLWindow; @@ -65,6 +66,15 @@ class LLMouseBinding std::string mFunctionName; }; +class LLControllerBinding +{ + public: + LLGameControl::ActionType mActionType; + U8 mAction; + LLKeyFunc mFunction; + std::string mFunctionName; +}; + typedef enum e_keyboard_mode { @@ -72,6 +82,7 @@ typedef enum e_keyboard_mode MODE_THIRD_PERSON, MODE_EDIT_AVATAR, MODE_SITTING, + MODE_FLYCAM, MODE_COUNT } EKeyboardMode; @@ -84,6 +95,7 @@ class LLViewerInput : public LLKeyBindingToStringHandler mask, command; Optional mouse; // Note, not mandatory for the sake of backward campatibility with keys.xml + Optional controller; KeyBinding(); }; @@ -100,7 +112,8 @@ class LLViewerInput : public LLKeyBindingToStringHandler Optional first_person, third_person, sitting, - edit_avatar; + edit_avatar, + flycam; Optional xml_version; // 'xml', because 'version' appears to be reserved Keys(); }; @@ -108,6 +121,8 @@ class LLViewerInput : public LLKeyBindingToStringHandler LLViewerInput(); virtual ~LLViewerInput(); + void storeDeltaTime(); + bool handleKey(KEY key, MASK mask, bool repeated); bool handleKeyUp(KEY key, MASK mask); @@ -132,6 +147,7 @@ class LLViewerInput : public LLKeyBindingToStringHandler // handleMouse() records state, scanMouse() goes through states, scanMouse(click) processes individual saved states after UI is done with them bool handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down); void scanMouse(); + void scanController(); bool isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const; bool isLMouseHandlingDefault(const S32 mode) const { return mLMouseDefaultHandling[mode]; } @@ -165,9 +181,12 @@ class LLViewerInput : public LLKeyBindingToStringHandler EMouseState state, bool ignore_additional_masks) const; + bool scanControllerInput(const LLGameControl::ActionType actionType, const U8 action, const LLKeyPressState& state) const; + S32 loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mode); bool bindKey(const S32 mode, const KEY key, const MASK mask, const std::string& function_name); bool bindMouse(const S32 mode, const EMouseClickType mouse, const MASK mask, const std::string& function_name); + bool bindController(const S32 mode, const LLGameControl::ActionType actionType, U8 action, const std::string& function_name); void resetBindings(); // Hold all the ugly stuff torn out to make LLKeyboard non-viewer-specific here @@ -175,13 +194,15 @@ class LLViewerInput : public LLKeyBindingToStringHandler // TODO: at some point it is better to remake this, especially keyaboard part // would be much better to send to functions actual state of the button than // to send what we think function wants based on collection of bools (mKeyRepeated, mKeyLevel, mKeyDown) - std::vector mKeyBindings[MODE_COUNT]; - std::vector mMouseBindings[MODE_COUNT]; - bool mLMouseDefaultHandling[MODE_COUNT]; // Due to having special priority + std::vector mKeyBindings[MODE_COUNT]; + std::vector mMouseBindings[MODE_COUNT]; + std::vector mControllerBindings[MODE_COUNT]; + bool mLMouseDefaultHandling[MODE_COUNT]; // Due to having special priority // keybindings that do not consume event and are handled earlier, before floaters - std::vector mGlobalKeyBindings[MODE_COUNT]; - std::vector mGlobalMouseBindings[MODE_COUNT]; + std::vector mGlobalKeyBindings[MODE_COUNT]; + std::vector mGlobalMouseBindings[MODE_COUNT]; + std::vector mGlobalControllerBindings[MODE_COUNT]; typedef std::map key_remap_t; key_remap_t mRemapKeys[MODE_COUNT]; diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp index c4d87d7e16e..d3b4f0e2c2b 100644 --- a/indra/newview/llviewerjoystick.cpp +++ b/indra/newview/llviewerjoystick.cpp @@ -40,6 +40,7 @@ #include "llagent.h" #include "llagentcamera.h" #include "llfocusmgr.h" +#include "llgamecontrol.h" #if LL_WINDOWS && !LL_MESA_HEADLESS // Require DirectInput version 8 @@ -255,25 +256,16 @@ void LLViewerJoystick::updateEnabled(bool autoenable) } if (!gSavedSettings.getBOOL("JoystickEnabled")) { - mOverrideCamera = false; + if (gAgentCamera.isUsingFlycam()) + { + gAgentCamera.toggleFlycam(); + } } } -void LLViewerJoystick::setOverrideCamera(bool val) +bool LLViewerJoystick::getOverrideCamera() { - if (!gSavedSettings.getBOOL("JoystickEnabled")) - { - mOverrideCamera = false; - } - else - { - mOverrideCamera = val; - } - - if (mOverrideCamera) - { - gAgentCamera.changeCameraToDefault(); - } + return gAgentCamera.isUsingFlycam(); } // ----------------------------------------------------------------------------- @@ -288,6 +280,7 @@ NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev) ndof_dump(stderr, dev); joystick->mNdofDev = dev; joystick->mDriverState = JDS_INITIALIZED; + joystick->mDeviceIs3DConnexion = is3DConnexionDevice(joystick->mNdofDev->product); res = NDOF_KEEP_HOTPLUGGED; } joystick->updateEnabled(true); @@ -313,18 +306,11 @@ void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev) // ----------------------------------------------------------------------------- LLViewerJoystick::LLViewerJoystick() -: mDriverState(JDS_UNINITIALIZED), - mNdofDev(NULL), - mResetFlag(false), - mCameraUpdated(true), - mOverrideCamera(false), - mJoystickRun(0) { for (int i = 0; i < 6; i++) { mAxes[i] = sDelta[i] = sLastDelta[i] = 0.0f; } - memset(mBtn, 0, sizeof(mBtn)); // factor in bandwidth? bandwidth = gViewerStats->mKBitStat @@ -403,6 +389,7 @@ void LLViewerJoystick::init(bool autoenable) else { mDriverState = JDS_INITIALIZED; + mDeviceIs3DConnexion = is3DConnexionDevice(mNdofDev->product); } } #endif @@ -499,6 +486,7 @@ void LLViewerJoystick::initDevice(LLSD &guid) else { mDriverState = JDS_INITIALIZED; + mDeviceIs3DConnexion = is3DConnexionDevice(mNdofDev->product); } } #endif @@ -583,6 +571,7 @@ bool LLViewerJoystick::initDevice(void * preffered_device /* LPDIRECTINPUTDEVICE else { mDriverState = JDS_INITIALIZED; + mDeviceIs3DConnexion = is3DConnexionDevice(mNdofDev->product); return true; } #endif @@ -598,6 +587,7 @@ void LLViewerJoystick::terminate() ndof_libcleanup(); // frees alocated memory in mNdofDev mDriverState = JDS_UNINITIALIZED; mNdofDev = NULL; + mDeviceIs3DConnexion = false; LL_INFOS("Joystick") << "Terminated connection with NDOF device." << LL_ENDL; } #endif @@ -1159,10 +1149,6 @@ void LLViewerJoystick::moveAvatar(bool reset) // ----------------------------------------------------------------------------- void LLViewerJoystick::moveFlycam(bool reset) { - static LLQuaternion sFlycamRotation; - static LLVector3d sFlycamPosition; - static F32 sFlycamZoom; - if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) { @@ -1183,11 +1169,15 @@ void LLViewerJoystick::moveFlycam(bool reset) bool in_build_mode = LLToolMgr::getInstance()->inBuildMode(); if (reset || mResetFlag) { - sFlycamPosition = gAgentCamera.getCameraPositionGlobal(); - sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion(); - sFlycamZoom = LLViewerCamera::getInstance()->getView(); + gAgentCamera.resetFlycamToCurrentView(); - resetDeltas(axis); + for (U32 i = 0; i < 6; i++) + { + mJoystickFlycamLastDelta[i] = -getJoystickAxis(axis[i]); + mJoystickFlycamDelta[i] = 0.f; + } + mJoystickFlycamLastDelta[6] = mJoystickFlycamDelta[6] = 0.f; + mResetFlag = false; return; } @@ -1235,9 +1225,9 @@ void LLViewerJoystick::moveFlycam(bool reset) F32 tmp = cur_delta[i]; if (absolute) { - cur_delta[i] = cur_delta[i] - sLastDelta[i]; + cur_delta[i] = cur_delta[i] - mJoystickFlycamLastDelta[i]; } - sLastDelta[i] = tmp; + mJoystickFlycamLastDelta[i] = tmp; if (cur_delta[i] > 0) { @@ -1267,7 +1257,7 @@ void LLViewerJoystick::moveFlycam(bool reset) cur_delta[i] *= time; } - sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather; + mJoystickFlycamDelta[i] = mJoystickFlycamDelta[i] + (cur_delta[i]-mJoystickFlycamDelta[i])*time*feather; is_zero = is_zero && (cur_delta[i] == 0.f); @@ -1286,46 +1276,12 @@ void LLViewerJoystick::moveFlycam(bool reset) } } - sFlycamPosition += LLVector3d(sDelta[VX], sDelta[VY], sDelta[VZ]) * sFlycamRotation; + bool auto_level = gSavedSettings.getBOOL("AutoLeveling"); + F32 auto_level_fraction = llmin(feather*time, 1.f); + bool zoom_direct = gSavedSettings.getBOOL("ZoomDirect"); + F32 direct_view_value = mJoystickFlycamLastDelta[6]*axis_scale[6]+dead_zone[6]; - LLMatrix3 rot_mat(sDelta[3], sDelta[4], sDelta[5]); - sFlycamRotation = LLQuaternion(rot_mat)*sFlycamRotation; - - if (gSavedSettings.getBOOL("AutoLeveling")) - { - LLMatrix3 level(sFlycamRotation); - - LLVector3 x = LLVector3(level.mMatrix[0]); - LLVector3 y = LLVector3(level.mMatrix[1]); - LLVector3 z = LLVector3(level.mMatrix[2]); - - y.mV[2] = 0.f; - y.normVec(); - - level.setRows(x,y,z); - level.orthogonalize(); - - LLQuaternion quat(level); - sFlycamRotation = nlerp(llmin(feather*time,1.f), sFlycamRotation, quat); - } - - if (gSavedSettings.getBOOL("ZoomDirect")) - { - sFlycamZoom = sLastDelta[6]*axis_scale[6]+dead_zone[6]; - } - else - { - sFlycamZoom += sDelta[6]; - } - - LLMatrix3 mat(sFlycamRotation); - - LLViewerCamera::getInstance()->setView(sFlycamZoom); - LLVector3 new_camera_pos = gAgent.getPosAgentFromGlobal(sFlycamPosition); - LLViewerCamera::getInstance()->setOrigin(new_camera_pos); - LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); - LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); - LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + gAgentCamera.applyNdofFlycamFrameDelta(mJoystickFlycamDelta, auto_level, auto_level_fraction, zoom_direct, direct_view_value); } // ----------------------------------------------------------------------------- @@ -1333,11 +1289,14 @@ bool LLViewerJoystick::toggleFlycam() { if (!gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) { - mOverrideCamera = false; + if (gAgentCamera.isUsingFlycam()) + { + gAgentCamera.toggleFlycam(); + } return false; } - if (!mOverrideCamera) + if (!gAgentCamera.isUsingFlycam()) { gAgentCamera.changeCameraToDefault(); } @@ -1351,8 +1310,8 @@ bool LLViewerJoystick::toggleFlycam() gAwayTriggerTimer.reset(); } - mOverrideCamera = !mOverrideCamera; - if (mOverrideCamera) + gAgentCamera.toggleFlycam(); + if (gAgentCamera.isUsingFlycam()) { moveFlycam(true); @@ -1369,7 +1328,9 @@ bool LLViewerJoystick::toggleFlycam() void LLViewerJoystick::scanJoystick() { - if (mDriverState != JDS_INITIALIZED || !gSavedSettings.getBOOL("JoystickEnabled")) + if (mDriverState != JDS_INITIALIZED + || !gSavedSettings.getBOOL("JoystickEnabled") + || !mDeviceIs3DConnexion) { return; } @@ -1377,7 +1338,7 @@ void LLViewerJoystick::scanJoystick() #if LL_WINDOWS // On windows, the flycam is updated syncronously with a timer, so there is // no need to update the status of the joystick here. - if (!mOverrideCamera) + if (!getOverrideCamera()) #endif updateStatus(); @@ -1402,7 +1363,7 @@ void LLViewerJoystick::scanJoystick() toggle_flycam = 0; } - if (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))) + if (!getOverrideCamera() && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))) { moveAvatar(); } @@ -1532,14 +1493,23 @@ std::string LLViewerJoystick::getDescription() return res; } +// static +bool LLViewerJoystick::is3DConnexionDevice(const std::string& device_name) +{ + bool answer = device_name.find("Space") == 0 + && ( (device_name.find("SpaceNavigator") == 0) + || (device_name.find("SpaceExplorer") == 0) + || (device_name.find("SpaceTraveler") == 0) + || (device_name.find("SpacePilot") == 0) + || (device_name.find("SpaceMouse") == 0)); + return answer; +} + bool LLViewerJoystick::isLikeSpaceNavigator() const { #if LIB_NDOF return (isJoystickInitialized() - && (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0 - || strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0 - || strncmp(mNdofDev->product, "SpaceTraveler", 13) == 0 - || strncmp(mNdofDev->product, "SpacePilot", 10) == 0)); + && is3DConnexionDevice(mNdofDev->product)); #else return false; #endif diff --git a/indra/newview/llviewerjoystick.h b/indra/newview/llviewerjoystick.h index c989615653c..b22ecd6441b 100644 --- a/indra/newview/llviewerjoystick.h +++ b/indra/newview/llviewerjoystick.h @@ -71,8 +71,9 @@ class LLViewerJoystick : public LLSingleton void setNeedsReset(bool reset = true) { mResetFlag = reset; } void setCameraNeedsUpdate(bool b) { mCameraUpdated = b; } bool getCameraNeedsUpdate() const { return mCameraUpdated; } - bool getOverrideCamera() { return mOverrideCamera; } - void setOverrideCamera(bool val); + // Flycam's state now lives on LLAgentCamera (shared with the game-control + // flycam); this just forwards. + bool getOverrideCamera(); bool toggleFlycam(); void setSNDefaults(); bool isDeviceUUIDSet(); @@ -81,6 +82,8 @@ class LLViewerJoystick : public LLSingleton std::string getDescription(); void saveDeviceIdToSettings(); + static bool is3DConnexionDevice(const std::string& device_name); + protected: void updateEnabled(bool autoenable); void handleRun(F32 inc); @@ -100,21 +103,30 @@ class LLViewerJoystick : public LLSingleton private: F32 mAxes[6]; long mBtn[16]; - EJoystickDriverState mDriverState; - NDOF_Device *mNdofDev; - bool mResetFlag; - F32 mPerfScale; - bool mCameraUpdated; - bool mOverrideCamera; - U32 mJoystickRun; + EJoystickDriverState mDriverState { JDS_UNINITIALIZED }; + NDOF_Device *mNdofDev { nullptr }; // Windows: _GUID as U8 binary map // MacOS: long as an U8 binary map // Else: integer 1 for no device/ndof's default device LLSD mLastDeviceUUID; + F32 mPerfScale; + U32 mJoystickRun { 0 }; + bool mResetFlag { false }; + bool mCameraUpdated { true }; + bool mDeviceIs3DConnexion { false }; + static F32 sLastDelta[7]; static F32 sDelta[7]; + + // This device's own feathered-delta state feeding + // LLAgentCamera::applyNdofFlycamFrameDelta() (which owns the shared flycam + // transform/engine, also used by the game-control flycam), kept separate + // from sDelta/sLastDelta above (which moveAvatar()/moveObjects() still + // use for non-flycam axis handling). + F32 mJoystickFlycamDelta[7] { 0,0,0,0,0,0,0 }; + F32 mJoystickFlycamLastDelta[7] { 0,0,0,0,0,0,0 }; }; #endif diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 87a891915e8..ec5be02b4a2 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -52,6 +52,7 @@ #include "llagentui.h" #include "llagentwearables.h" #include "llagentpilot.h" +#include "llavataractions.h" #include "llcompilequeue.h" #include "llconsole.h" #include "lldebugview.h" @@ -71,6 +72,7 @@ #include "llfloaterpathfindingcharacters.h" #include "llfloaterpathfindinglinksets.h" #include "llfloaterpay.h" +#include "llfloaterpreference.h" #include "llfloaterreporter.h" #include "llfloatersearch.h" #include "llfloaterscriptdebug.h" @@ -79,9 +81,8 @@ #include "llfloatertools.h" #include "llfloaterworldmap.h" #include "llfloaterbuildoptions.h" -#include "llavataractions.h" -#include "lllandmarkactions.h" #include "llgroupmgr.h" +#include "lllandmarkactions.h" #include "lltooltip.h" #include "lltoolface.h" #include "llhints.h" @@ -1004,17 +1005,36 @@ class LLAdvancedToggleFeature : public view_listener_t class LLAdvancedCheckFeature : public view_listener_t { bool handleEvent(const LLSD& userdata) -{ - U32 feature = feature_from_string( userdata.asString() ); - bool new_value = false; + { + U32 feature = feature_from_string( userdata.asString() ); + bool new_value = false; - if ( feature != 0 ) + if ( feature != 0 ) + { + new_value = LLPipeline::toggleRenderDebugFeatureControl( feature ); + } + + return new_value; + } +}; + +class LLAdvancedToggleExperiment : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) { - new_value = LLPipeline::toggleRenderDebugFeatureControl( feature ); + std::string feature = userdata.asString(); + return false; } +}; - return new_value; -} +class LLAdvancedCheckExperiment : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool value = false; + std::string feature = userdata.asString(); + return value; + } }; class LLAdvancedCheckDisplayTextureDensity : public view_listener_t @@ -6862,8 +6882,8 @@ class LLAvatarResetSkeleton : public view_listener_t } else { - avatar->resetSkeleton(false); - } + avatar->resetSkeleton(false); + } } return true; } @@ -6928,9 +6948,9 @@ class LLAvatarResetSelfSkeletonAndAnimations : public view_listener_t effectp->setSourceObject(gAgentAvatarp); effectp->setTargetObject((LLViewerObject*)avatar); effectp->setResetAnimations(true); - } - else - { + } + else + { avatar->resetSkeleton(true); } } @@ -10115,6 +10135,9 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedToggleFeature(), "Advanced.ToggleFeature"); view_listener_t::addMenu(new LLAdvancedCheckFeature(), "Advanced.CheckFeature"); + view_listener_t::addMenu(new LLAdvancedToggleExperiment(), "Advanced.ToggleExperiment"); + view_listener_t::addMenu(new LLAdvancedCheckExperiment(), "Advanced.CheckExperiment"); + view_listener_t::addMenu(new LLAdvancedCheckDisplayTextureDensity(), "Advanced.CheckDisplayTextureDensity"); view_listener_t::addMenu(new LLAdvancedSetDisplayTextureDensity(), "Advanced.SetDisplayTextureDensity"); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1f76914335c..e8cfcfae722 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -66,6 +66,7 @@ #include "llfloaterpreference.h" #include "llfloatersidepanelcontainer.h" #include "llfloatersnapshot.h" +#include "llgamecontrol.h" #include "llhudeffecttrail.h" #include "llhudmanager.h" #include "llimprocessing.h" @@ -3190,8 +3191,8 @@ void process_crossed_region(LLMessageSystem* msg, void**) } -// sends an AgentUpdate message to the server... or not: -// only when force_send is 'true' OR +// sends an AgentUpdate message to the server... or not +// e.g. only when force_send is 'true' OR // something changed AND the update is not being throttled void send_agent_update(bool force_send, bool send_reliable) { @@ -3260,8 +3261,6 @@ void send_agent_update(bool force_send, bool send_reliable) return; } - bool send_update = force_send || sec_since_last_send > MAX_AGENT_UPDATE_PERIOD; - LLVector3 camera_pos_agent = gAgentCamera.getCameraPositionAgent(); // local to avatar's region LLVector3 camera_at = LLViewerCamera::getInstance()->getAtAxis(); LLQuaternion body_rotation = gAgent.getFrameAgent().getQuaternion(); @@ -3278,6 +3277,7 @@ void send_agent_update(bool force_send, bool send_reliable) flags |= AU_FLAGS_CLIENT_AUTOPILOT; } + bool send_update = force_send || sec_since_last_send > MAX_AGENT_UPDATE_PERIOD; if (!send_update) { // check to see if anything changed @@ -3951,6 +3951,11 @@ void process_sim_stats(LLMessageSystem *msg, void **user_data) if (measurementp ) { measurementp->sample(stat_value); + if (stat_id == LL_SIM_STAT_FPS) + { + // let GameControlData's outgoing throttle track the sim's actual rate + LLGameControl::setServerFrameRate(stat_value); + } } else { @@ -6128,7 +6133,7 @@ void process_teleport_local(LLMessageSystem *msg,void**) gAgent.setPositionAgent(pos); gAgentCamera.slamLookAt(look_at); - if ( !(gAgent.getTeleportKeepsLookAt() && LLViewerJoystick::getInstance()->getOverrideCamera()) ) + if ( !(gAgent.getTeleportKeepsLookAt() && gAgent.isCameraExternallyDriven()) ) { gAgentCamera.resetView(true, true); } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 32af06cd0d9..3e5d95f9be6 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -114,6 +114,7 @@ #include "llfloaterworldmap.h" #include "llfocusmgr.h" #include "llfontfreetype.h" +#include "llgamecontrol.h" #include "llgesturemgr.h" #include "llglheaders.h" #include "llhudmanager.h" @@ -763,6 +764,50 @@ class LLDebugText av_iter++; } } + static LLCachedControl debug_show_game_controller_state(gSavedSettings, "DebugShowControllerState", false); + if (debug_show_game_controller_state()) + { + static const char* axis_names[LLGameControl::NUM_MOVE_DIRS] = { + "LEFT","RIGHT","FORWARD","BACKWARD", "TURN LEFT", "TURN RIGHT", + "LOOK UP","LOOK DOWN", "UP", "DOWN", "ROLL LEFT", "ROLL RIGHT" + }; + static const char* button_names[LLGameControl::NUM_BUTTONS] = { + "A", "B", "X", "Y", "BACK", "SELECT", "START", "LSTICK", "RSTICK", + "LSHOULDER", "RSHOULDER", "UP", "DOWN", "LEFT", "RIGHT", + "MISC1", "PADDLE1", "PADDLE2", "PADDLE3", "PADDLE4", "TOUCHPAD", + "B21", "B22", "B23", "B24", "B25", "B26", "B27", "B28", "B29", "B30", "B31", + }; + const LLGameControl::State& gc_state = LLGameControl::getState(); + for (size_t i = 0; i < gc_state.mAxes.size(); i+=2) + { + addText(xpos, ypos, llformat( + " %-10s %6d %-10s %6d", + (i < LLGameControl::NUM_MOVE_DIRS) ? axis_names[i] : "AXIS", + (S32)gc_state.mAxes[i], + ((i+1) < LLGameControl::NUM_MOVE_DIRS) ? axis_names[i+1] : "AXIS", + i < gc_state.mAxes.size() ? (S32)gc_state.mAxes[i+1] : 0 + )); + ypos += y_inc; + } + addText(xpos, ypos, "Game Controller Final State Axes"); + ypos += y_inc; + static const char* pressed = "░░░░░░░░░░"; + static const char* released = " "; + for (U8 i = 0; i < LLGameControl::NUM_BUTTONS; i += 4) + { + addText(xpos, ypos, llformat( + " %-10s %-10s %-10s %-10s", + gc_state.mButtons & (1 << (i+0)) ? pressed : released, + gc_state.mButtons & (1 << (i+1)) ? pressed : released, + gc_state.mButtons & (1 << (i+2)) ? pressed : released, + gc_state.mButtons & (1 << (i+3)) ? pressed : released + )); + addText(xpos, ypos, llformat(" %-10s %-10s %-10s %-10s", button_names[i],button_names[i+1],button_names[i+2],button_names[i+3])); + ypos += y_inc; + } + addText(xpos, ypos, "Game Controller Final State Buttons"); + ypos += y_inc; + } static LLCachedControl debug_show_render_matrices(gSavedSettings, "DebugShowRenderMatrices", false); if (debug_show_render_matrices()) { @@ -2870,6 +2915,17 @@ void LLViewerWindow::draw() // Draw tool specific overlay on world LLToolMgr::getInstance()->getCurrentTool()->draw(); + if (gAgentCamera.isUsingFlycam() && LLGameControl::isFlycamCrosshairEnabled()) + { + // Same crosshair image/centering LLToolGun::draw() uses for + // mouselook's "ShowCrosshairs" -- flycam has no LLTool of its own + // to hang this off of, so it's drawn here instead. + LLUIImagePtr crosshair = LLUI::getUIImage("crosshairs.tga"); + crosshair->draw( + ( getWorldViewRectScaled().getWidth() - crosshair->getWidth() ) / 2, + ( getWorldViewRectScaled().getHeight() - crosshair->getHeight() ) / 2); + } + if( gAgentCamera.cameraMouselook() || LLFloaterCamera::inFreeCameraMode() ) { drawMouselookInstructions(); @@ -3454,6 +3510,22 @@ void LLViewerWindow::moveCursorToCenter() } } +void LLViewerWindow::moveCursorTo(S32 x, S32 y) +{ + // Clamp to the whole window (not just the world view) so a gamepad-driven + // cursor (see LLAgent::applyExternalActions()'s CONTROL_MODE_CURSOR + // handling) can reach the menu bar and toolbars, but not escape the window. + x = llclamp(x, 0, getWindowWidthScaled()); + y = llclamp(y, 0, getWindowHeightScaled()); + + LLUI::getInstance()->setMousePositionScreen(x, y); + + // on a forced move, all deltas get zeroed out to prevent jumping + mCurrentMousePoint.set(x, y); + mLastMousePoint.set(x, y); + mCurrentMouseDelta.set(0, 0); +} + ////////////////////////////////////////////////////////////////////// // @@ -4584,7 +4656,7 @@ LLViewerObject* LLViewerWindow::cursorIntersect(S32 mouse_x, S32 mouse_y, F32 de LLVector3 mouse_world_start = mouse_point_global; LLVector3 mouse_world_end = mouse_point_global + mouse_direction_global * depth; - if (!LLViewerJoystick::getInstance()->getOverrideCamera()) + if (!gAgent.isCameraExternallyDriven()) { //always set raycast intersection to mouse_world_end unless //flycam is on (for DoF effect) gDebugRaycastIntersection.load3(mouse_world_end.mV); diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index 06d49df56d2..2498742bb9d 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -323,6 +323,7 @@ class LLViewerWindow : public LLWindowCallbacks void hideCursor(); bool getCursorHidden() { return mCursorHidden; } void moveCursorToCenter(); // move to center of window + void moveCursorTo(S32 x, S32 y); // move to an arbitrary screen-space (GL) position, clamped to the world view void initTextures(S32 location_id); void setShowProgress(const bool show); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index ab7058b5df1..686638a0540 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -7869,7 +7869,7 @@ void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) if (focus_point.isExactlyZero()) { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { // focus on point under cursor focus_point.set(gDebugRaycastIntersection.getF32ptr()); } diff --git a/indra/newview/skins/default/textures/bottomtray/Dpad.png b/indra/newview/skins/default/textures/bottomtray/Dpad.png new file mode 100644 index 00000000000..00fcb4beeac Binary files /dev/null and b/indra/newview/skins/default/textures/bottomtray/Dpad.png differ diff --git a/indra/newview/skins/default/xui/en/control_table_contents_game_control.xml b/indra/newview/skins/default/xui/en/control_table_contents_game_control.xml new file mode 100644 index 00000000000..21b8f876330 --- /dev/null +++ b/indra/newview/skins/default/xui/en/control_table_contents_game_control.xml @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/control_table_contents_movement.xml b/indra/newview/skins/default/xui/en/control_table_contents_movement.xml index c9a1c4654ec..fe89308adb2 100644 --- a/indra/newview/skins/default/xui/en/control_table_contents_movement.xml +++ b/indra/newview/skins/default/xui/en/control_table_contents_movement.xml @@ -83,7 +83,7 @@ font="SansSerif" halign="left" name="lst_action" - value="Strafe left" /> + value="Strafe Left" /> + value="Strafe Right" /> + + + diff --git a/indra/newview/skins/default/xui/en/floater_font_test.xml b/indra/newview/skins/default/xui/en/floater_font_test.xml index 8070ed55e68..327cbd6aba3 100644 --- a/indra/newview/skins/default/xui/en/floater_font_test.xml +++ b/indra/newview/skins/default/xui/en/floater_font_test.xml @@ -337,6 +337,44 @@ left="16"> Helvetica size=Large style=BOLD|ITALIC + + + SecondLife-game-control faces/dpad: ↤↥↦↧ ↞↟↠↡ + + + SecondLife-game-control sticks/triggers: ⇄⇅⇆⇵ ↖↗↘↙ + + + + Left stick ⇄ Jump ↧ D-pad ↡ + + diff --git a/indra/newview/skins/default/xui/en/game_control_table_rows.xml b/indra/newview/skins/default/xui/en/game_control_table_rows.xml new file mode 100644 index 00000000000..8d3f2b14a56 --- /dev/null +++ b/indra/newview/skins/default/xui/en/game_control_table_rows.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 761b1542dca..ff90639bec1 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -19,7 +19,7 @@ label="Inventory..." name="Inventory" shortcut="control|I" - visible="true"> + visible="true"> @@ -443,7 +443,7 @@ function="Agent.toggleFlying" /> - + - - @@ -728,8 +728,7 @@ function="Floater.Show" parameter="snapshot" /> - - @@ -1028,7 +1027,7 @@ function="World.EnvSettings" parameter="my_environs" /> - + @@ -1044,7 +1043,7 @@ function="World.EnvSettings" parameter="pause_clouds" /> @@ -1057,7 +1056,7 @@ - - +--> + - + - + - + @@ -1636,7 +1635,7 @@ function="World.EnvPreset" function="ToggleControl" parameter="RenderDisablePostProcessing" /> - + - + - + + parameter="https://support.secondlife.com/"/> - + @@ -2441,7 +2440,7 @@ function="World.EnvPreset" function="Advanced.ToggleFeature" parameter="flexible" /> - + @@ -2603,7 +2602,7 @@ function="World.EnvPreset" - + + + + + @@ -3594,7 +3603,7 @@ function="World.EnvPreset" - + - + - - - - diff --git a/indra/newview/skins/default/xui/en/panel_preferences_controls.xml b/indra/newview/skins/default/xui/en/panel_preferences_controls.xml index f8801f9c89c..5cd3bed5bcd 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_controls.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_controls.xml @@ -6,7 +6,7 @@ label="Controls" layout="topleft" left="102" - name="controls" + name="controls_panel" top="1" width="517"> +