diff --git a/src/config.cpp b/src/config.cpp index fbe651c52..5ee61ae14 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -436,6 +436,14 @@ namespace config { } } // namespace dd + alt_gamepad_numbering_t alt_gamepad_numbering { + {}, // alt_gamepad_numbering_mutex + {}, // sDeviceNames + true, // bFirstTimeControllerAllocation + true, // bFirstTimeParsing + true, // bFirstTimeFeedbackQueues + }; + video_t video { false, // headless_mode true, // limit_framerate @@ -572,6 +580,9 @@ namespace config { true, // native pen/touch support false, // enable input only mode true, // forward_rumble + false, // alt_controller enable + 4, // alt_controller_count + "strict", // alt_controller_mode }; sunshine_t sunshine { @@ -1280,6 +1291,9 @@ namespace config { bool_f(vars, "high_resolution_scrolling", input.high_resolution_scrolling); bool_f(vars, "native_pen_touch", input.native_pen_touch); bool_f(vars, "enable_input_only_mode", input.enable_input_only_mode); + bool_f(vars, "enable_alt_controller_numbering_mode", input.enable_alt_controller_numbering_mode); + int_between_f(vars, "alt_controller_count", input.alt_controller_count, {1, 16}); // CORRESPONDS TO MAX_GAMEPADS = 16 in common.h on /src/platform/common.h + string_restricted_f(vars, "alt_controller_mode", input.alt_controller_mode, {"strict"sv, "shared"sv,"bothcontrollermodes"sv}); bool_f(vars, "hide_tray_controls", sunshine.hide_tray_controls); bool_f(vars, "enable_pairing", sunshine.enable_pairing); @@ -1521,4 +1535,10 @@ namespace config { return 0; } + + // Alternate Controller placeholder for feedback queues + // Use alt_gamepad_numbering_mutex to access this element + // This will not work in the config.h file where the other alternate controller placeholder variables are because platf::feedback_queue_t is not defined and adding the "platform/common.h" in the config.h context will cause many files which include config.h to fail. + std::vector placeholder_feedback_queues { platf::MAX_GAMEPADS }; + std::vector< struct config::sDeviceNameOrder > VectorAlternateGamepadParameters; } // namespace config diff --git a/src/config.h b/src/config.h index 8be2b42f8..2d0fb372d 100644 --- a/src/config.h +++ b/src/config.h @@ -11,6 +11,7 @@ #include #include #include +#include // local includes #include "nvenc/nvenc_config.h" @@ -212,6 +213,10 @@ namespace config { bool enable_input_only_mode; bool forward_rumble; + // Alternate Controller Numbering Mode + bool enable_alt_controller_numbering_mode; + int alt_controller_count; + std::string alt_controller_mode; }; namespace flag { @@ -285,7 +290,32 @@ namespace config { std::vector state_cmds; std::vector server_cmds; }; - + + // Alternate Controller Numbering Mode + + struct sDeviceNameOrder { + std::string sDeviceName; + std::string sOrder; + std::vector < int > vOrder; + std::string sShared; + std::vector < int > vShared; + std::string sJitterJoysticks; + std::vector < int > vJitterJoysticks; + std::string sSwapJoysticks; + std::vector < int > vSwapJoysticks; + std::string sUuid; + }; + + struct alt_gamepad_numbering_t { + std::recursive_mutex alt_gamepad_numbering_mutex; + std::vector< std::string > sDeviceNames; + volatile bool bFirstTimeControllerAllocation { true }; + volatile bool bFirstTimeParsing { true }; + volatile bool bFirstTimeFeedbackQueues { true }; + }; + + extern std::vector< struct config::sDeviceNameOrder > VectorAlternateGamepadParameters; + extern alt_gamepad_numbering_t alt_gamepad_numbering; extern video_t video; extern audio_t audio; extern stream_t stream; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index e270dd761..d2190652e 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -872,6 +872,9 @@ namespace confighttp { #ifdef _WIN32 output_tree["platform"] = "windows"; #endif + output_tree["alt_controller_count"] = config::input.alt_controller_count; + output_tree["enable_alt_controller_numbering_mode"] = config::input.enable_alt_controller_numbering_mode; + output_tree["alt_controller_mode"] = config::input.alt_controller_mode; output_tree["status"] = true; send_response(response, output_tree); } @@ -908,6 +911,10 @@ namespace confighttp { std::string uuid = input_tree.value("uuid", ""); std::string name = input_tree.value("name", ""); std::string display_mode = input_tree.value("display_mode", ""); + std::string controller_list_numbers = input_tree.value("controller_list_numbers",""); + std::string controller_list_shared = input_tree.value("controller_list_shared",""); + std::string controller_list_jitter_joysticks = input_tree.value("controller_list_jitter_joysticks",""); + std::string controller_list_swap_joysticks = input_tree.value("controller_list_swap_joysticks",""); bool enable_legacy_ordering = input_tree.value("enable_legacy_ordering", true); bool allow_client_commands = input_tree.value("allow_client_commands", true); bool always_use_virtual_display = input_tree.value("always_use_virtual_display", false); @@ -923,7 +930,11 @@ namespace confighttp { perm, enable_legacy_ordering, allow_client_commands, - always_use_virtual_display + always_use_virtual_display, + controller_list_numbers, + controller_list_shared, + controller_list_jitter_joysticks, + controller_list_swap_joysticks ); send_response(response, output_tree); } catch (std::exception &e) { diff --git a/src/crypto.h b/src/crypto.h index 350bb5cc8..777d6de11 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -107,6 +107,10 @@ namespace crypto { bool enable_legacy_ordering; bool allow_client_commands; bool always_use_virtual_display; + std::string controller_list_numbers; + std::string controller_list_shared; + std::string controller_list_jitter_joysticks; + std::string controller_list_swap_joysticks; }; using p_named_cert_t = std::shared_ptr; diff --git a/src/input.cpp b/src/input.cpp index 6c03d0d33..8f21586e8 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -35,7 +35,74 @@ extern "C" { using namespace std::literals; + +#define ALT_CONTROLLER_NO_PLACEHOLDER 1000 +#define ALT_CONTROLLER_ANY_PLACEHOLDER 999 +#define ALT_CONTROLLER_STOP_LOOKING 998 +#define ALT_CONTROLLER_ASSIGN_FAILED 1001 + + +// For the alternative controller numbering +namespace config { + extern std::vector placeholder_feedback_queues; + extern std::vector< struct config::sDeviceNameOrder > VectorAlternateGamepadParameters; +} + +int matchAlternativeGamepadSharingString(std::string sDeviceName, struct config::sDeviceNameOrder &sReturn ) { + bool bFound = false; + + struct config::sDeviceNameOrder sCurrent; + std::vector< struct config::sDeviceNameOrder >::iterator i; + i = config::VectorAlternateGamepadParameters.begin(); + + while (bFound == false && i != config::VectorAlternateGamepadParameters.end() ) { + sCurrent = *i; + if (sCurrent.sDeviceName == sDeviceName) { + bFound = true; + } + i = i + 1; + } + + if (bFound == true) { + for( int i=0; i < sCurrent.vOrder.size(); i++) { + // Empty code to inspect numbers + } + sReturn = sCurrent; + return 0; + } + return 1; +} + + +// MATCH and extract +int matchAlternativeGamepadNumberingString(std::string sDeviceName, struct config::sDeviceNameOrder &sReturn ) { + bool bFound = false; + + struct config::sDeviceNameOrder sCurrent; + std::vector< struct config::sDeviceNameOrder >::iterator i; + i = config::VectorAlternateGamepadParameters.begin(); + + while (bFound == false && i != config::VectorAlternateGamepadParameters.end() ) { + sCurrent = *i; + if (sCurrent.sDeviceName == sDeviceName) { + bFound = true; + } + i = i + 1; + } + + if (bFound == true) { + for( int i=0; i < sCurrent.vOrder.size(); i++) { + // Empty code to inspect numbers + } + sReturn = sCurrent; + return 0; + } + return 1; +} + namespace input { + // Alternate Controller Numbering Mode + int altgamepaddnumberingDeleteGamepadInStructure( int alt_controller_placeholder_index, int alt_controller_placeholder, int alt_controller_real_index, int id_OS, void *); constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8); #define DISABLE_LEFT_BUTTON_DELAY ((thread_pool_util::ThreadPool::task_id_t) 0x01) @@ -57,6 +124,26 @@ namespace input { UP ///< Button is up }; + enum class acm_e { + ALT_CONTROLLER_STRICT, // Strict only controller assignment + ALT_CONTROLLER_SHARED, // Shared only controller assignment + ALT_CONTROLLER_BOTH_CONTROLLER_MODES, // Can go between Strict and Shared controller modes + }; + + acm_e acm_from_enum_string( const std::string_view &view) { + if (view == "strict") { + return input::acm_e::ALT_CONTROLLER_STRICT; + } + if(view == "shared") { + return input::acm_e::ALT_CONTROLLER_SHARED; + } + if(view == "bothcontrollermodes") { + return input::acm_e::ALT_CONTROLLER_BOTH_CONTROLLER_MODES; + } + + return input::acm_e::ALT_CONTROLLER_BOTH_CONTROLLER_MODES; + } + template int alloc_id(std::bitset &gamepad_mask) { for (int x = 0; x < gamepad_mask.size(); ++x) { @@ -69,6 +156,19 @@ namespace input { return -1; } +// Request a specific ID instead of the next in line + template + int alloc_id_request(std::bitset &gamepad_mask, int iIdRequest) { + if( iIdRequest < gamepad_mask.size()) { + if (!gamepad_mask[iIdRequest]) { + + gamepad_mask[iIdRequest] = true; + return iIdRequest; + } + } + return -1; + } + template void free_id(std::bitset &gamepad_mask, int id) { gamepad_mask[id] = false; @@ -115,26 +215,53 @@ namespace input { static platf::input_t platf_input; static std::bitset gamepadMask {}; +// This is for the alternate gamepad numbering + void free_gamepad_but_not_id(platf::input_t &platf_input, int id, int internal) { + platf::gamepad_update(platf_input, id, platf::gamepad_state_t {}); + + // This will be reused, so do not eliminate it from the vigem + // platf::free_gamepad(platf_input, id); + // Instead just detection the feedback_queue to prevent the rumble messages + platf::detach_feedback_queue_from_gamepad( platf_input, id); + + // This does not free the id since it will be reused. + //free_id(gamepadMask, id); + } void free_gamepad(platf::input_t &platf_input, int id) { platf::gamepad_update(platf_input, id, platf::gamepad_state_t {}); platf::free_gamepad(platf_input, id); free_id(gamepadMask, id); } - struct gamepad_t { gamepad_t(): - gamepad_state {}, - back_timeout_id {}, - id {-1}, - back_button_state {button_state_e::NONE} { + gamepad_state {}, + back_timeout_id {}, + id {-1}, + // Alternate Gamepad Numbering Mode + alt_controller_placeholder { 0 }, // For the alternative controller numbering; 0 if real (not alternate gamepad numbering), 1 if a placeholder, 2 if being used by session and is a placeholder, 3 if being used by session and is a placeholder and to be deleted, 4 if using alt gamepad numbering but there is no room for it + alt_controller_placeholder_index { -1 },// Placeholder index into the placeholder_gamepads vector + alt_controller_real_index { -1 }, // Real Index assigned at the session level + alt_controller_real_devicename { -1}, // Real Device Name (string) index at the external vector made for the alternate gamepad numbering + alt_controller_shared_list {}, + alt_controller_undo_shared_list {}, + alt_controller_jitter_joystick_number { 0 }, + alt_controller_swap_joysticks { 0 }, + back_button_state {button_state_e::NONE} { } ~gamepad_t() { if (id >= 0) { - task_pool.push([id = this->id]() { + // Alternate Gamepad Numbering Mode + if ((config::input.enable_alt_controller_numbering_mode == true ) && (this->alt_controller_placeholder == 2 || this->alt_controller_placeholder == 3)) { + task_pool.push([id = this->id, alt_controller_placeholder_index = this->alt_controller_placeholder_index, alt_controller_placeholder = this->alt_controller_placeholder, alt_controller_real_index = this->alt_controller_real_index, localthis = this]() { + altgamepaddnumberingDeleteGamepadInStructure(alt_controller_placeholder_index, alt_controller_placeholder, alt_controller_real_index, id, localthis); + }); + } else { + task_pool.push([id = this->id]() { free_gamepad(platf_input, id); - }); + }); + } } } @@ -144,6 +271,16 @@ namespace input { int id; + // Alternate gamepad numbering + int alt_controller_placeholder; // For the alternative controller numbering; 0 if real (not alternate gamepad numbering), 1 if a placeholder, 2 if being used by session and is a placeholder, 3 if being used by session and is a placeholder and to be deleted, 4 if using alt gamepad numbering but there is no room for it + int alt_controller_placeholder_index; // Placeholder index + int alt_controller_real_index; // Real Index assigned in session + int alt_controller_real_devicename; // Real Device Name index + std::vector alt_controller_shared_list; + std::vector alt_controller_undo_shared_list; + int alt_controller_jitter_joystick_number; + int alt_controller_swap_joysticks; + // When emulating the HOME button, we may need to artificially release the back button. // Afterwards, the gamepad state on sunshine won't match the state on Moonlight. // To prevent Sunshine from sending erroneous input data to the active application, @@ -152,6 +289,19 @@ namespace input { button_state_e back_button_state; }; +// struct gamepad_t; + std::vector placeholder_gamepads; // Holds the placeholder for the gamepads + struct alt_override_gamepad_t { + + // These are to store the shared session currently being used; If it is not being used then the pointers are NULL and the currentoverridesessioncontroller is -1 + std::shared_ptr *alt_controller_overridesessioninput = NULL; + int alt_controller_overridesessioncontroller = -1; + int alt_controller_overridesessionplaceholdergamepadindex = -1; + int alt_controller_overridesessionid = -1; + int alt_controller_overrideplaceholder = -1; + platf::gamepad_arrival_t *alt_controller_overridesessionarrival = NULL; + std::vector *alt_controller_overridesessiongamepads; + }; struct input_t { enum shortkey_e { CTRL = 0x1, ///< Control key @@ -164,15 +314,17 @@ namespace input { safe::mail_raw_t::event_t touch_port_event, platf::feedback_queue_t feedback_queue ): - shortcutFlags {}, - gamepads(MAX_GAMEPADS), - client_context {platf::allocate_client_input_context(platf_input)}, - touch_port_event {std::move(touch_port_event)}, - feedback_queue {std::move(feedback_queue)}, - mouse_left_button_timeout {}, - touch_port {{0, 0, 0, 0}, 0, 0, 1.0f}, - accumulated_vscroll_delta {}, - accumulated_hscroll_delta {} { + shortcutFlags {}, + gamepads(MAX_GAMEPADS), + client_context {platf::allocate_client_input_context(platf_input)}, + touch_port_event {std::move(touch_port_event)}, + feedback_queue {std::move(feedback_queue)}, + mouse_left_button_timeout {}, + touch_port {{0, 0, 0, 0}, 0, 0, 1.0f}, + accumulated_vscroll_delta {}, + accumulated_hscroll_delta {} + { + // empty body } // Keep track of alt+ctrl+shift key combo @@ -193,8 +345,17 @@ namespace input { int32_t accumulated_vscroll_delta; int32_t accumulated_hscroll_delta; + // Alternate Controller Numbering + int iAltControllerNameIndex; }; + // 2 Methods to assign the gamepad. The first one is for client gamepad initialization. The second is to go between the modes + int altOrderingAssignGamepad( std::shared_ptr &input, int controllerNumber, platf::gamepad_arrival_t &arrival, int iOverride = 0 ); + int simplealtOrderingAssignGamepad( int iOverride ); + + // Key flags as to whether sharemode is activated and if the keypress is activated + volatile bool bAltControllerShareMode = false; + volatile bool bEnableAltControllerModeKeypress = true; /** * @brief Apply shortcut based on VKEY * @param keyCode The VKEY code @@ -210,8 +371,12 @@ namespace input { mail::man->event(mail::switch_display)->raise(keyCode - VK_F1); return 1; } - - switch (keyCode) { + if( (bEnableAltControllerModeKeypress == true) && (keyCode == 0x49) ) + { + simplealtOrderingAssignGamepad( 4 ); + return 0; + } + switch (keyCode) { case 0x4E /* VKEY_N */: display_cursor = !display_cursor; return 1; @@ -797,6 +962,59 @@ namespace input { } } + // Alternate gamepad numbering + int find_empty_placeholder_gamepad( void ) { + int count; + count = 0; + for( auto& gamepad : placeholder_gamepads ) { + if( gamepad.id == -1) { + return count; + } + count += 1; + } + return -1; + } + + int find_id_OS_placeholder_gamepad( int id_OS ) { + int count; + count = 0; + for( auto& gamepad : placeholder_gamepads ) { + if( gamepad.id == id_OS ) { + return count; + } + count += 1; + } + return -1; + } + + int find_empty_gamepad( std::vector &gamepads ) { + int count; + int maxindex = -1; + count = 0; + + for( auto& gamepad : gamepads ) { + if( gamepad.id == -1) { + maxindex = count; + } + count += 1; + } + return maxindex; + } + + int find_id_OS_gamepad( std::vector gamepads, int id_OS ) { + int count; + int maxindex = -1; + count = 0; + for( auto& gamepad : gamepads ) { + if( gamepad.id == id_OS ) { + maxindex = count; + } + count += 1; + } + return maxindex; + } + + /** * @brief Called to pass a horizontal scroll message the platform backend. * @param input The input context pointer. @@ -820,6 +1038,472 @@ namespace input { } } +// This is only called when the client session is disconnected or removed. This routine is NOT called when a controller is removed from an existing session. + int altgamepaddnumberingDeleteGamepadInStructure( int alt_controller_placeholder_index, int alt_controller_placeholder, int alt_controller_real_index, int id_OS, void *pToDelete ) { + if( (alt_controller_placeholder == 2 || alt_controller_placeholder == 3) ) { + int iIndex; + int iIndex2; + int iIndex3; + if( config::input.enable_alt_controller_numbering_mode == true ) { + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + // See if the controller is a member of the placeholder vector + if( alt_controller_placeholder_index >= 0 ) { + // Set the controller back into the placeholder array as useable + (placeholder_gamepads[ alt_controller_placeholder_index]).alt_controller_placeholder = 1; + + // Reset the real indexes + (placeholder_gamepads[ alt_controller_placeholder_index]).alt_controller_real_index = -1; + (placeholder_gamepads[ alt_controller_real_index]).alt_controller_real_devicename = -1; + for( iIndex = 0; iIndex < placeholder_gamepads.size(); iIndex += 1) { + for( iIndex2 = 0; iIndex2 < placeholder_gamepads[iIndex].alt_controller_shared_list.size(); iIndex2 += 1) { + struct alt_override_gamepad_t tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2]; + if(tSharedInfo.alt_controller_overridesessiongamepads != NULL ) { + for(iIndex3 =0; iIndex3 < (tSharedInfo.alt_controller_overridesessiongamepads)->size(); iIndex3++) { + if( pToDelete == &((*(tSharedInfo.alt_controller_overridesessiongamepads))[iIndex3]) ) { + // This is the correct session and the pointer is still active + // Check if the controller numbers match + if( iIndex3 == tSharedInfo.alt_controller_overridesessioncontroller ) { + tSharedInfo.alt_controller_overridesessiongamepads = NULL; + tSharedInfo.alt_controller_overridesessionid = -1; + placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2] = tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2]; + break; + } + } + } + } + } + for( iIndex2 = 0; iIndex2 < placeholder_gamepads[iIndex].alt_controller_undo_shared_list.size(); iIndex2 += 1) { + struct alt_override_gamepad_t tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2]; + if(tSharedInfo.alt_controller_overridesessiongamepads != NULL ) { + for(iIndex3 =0; iIndex3 < (tSharedInfo.alt_controller_overridesessiongamepads)->size(); iIndex3++) { + if( pToDelete == &((*(tSharedInfo.alt_controller_overridesessiongamepads))[iIndex3]) ) { + // This is the correct session and the pointer is still active + // Check if the controller numbers match + if( iIndex3 == tSharedInfo.alt_controller_overridesessioncontroller ) { + tSharedInfo.alt_controller_overridesessiongamepads = NULL; + tSharedInfo.alt_controller_overridesessionid = -1; + placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2] = tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2]; + break; + } + } + } + } + } + } + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + } + } + task_pool.push([id_OS = id_OS]() { + free_gamepad_but_not_id(platf_input, id_OS,0); + }); + } + + return 0; + } + +// This is only called when a gamepad is disconnected but the client session is still active + int altOrderingDeleteGamepad( std::shared_ptr &input, int controllerNumber) { + auto &gamepad = input->gamepads[controllerNumber]; + int iIndex; + int iIndex2; + int iIndex3; + + void *pToDelete = &gamepad; + if( gamepad.alt_controller_placeholder == 2 || gamepad.alt_controller_placeholder == 3) { + if( config::input.enable_alt_controller_numbering_mode == true ) { + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + for( iIndex = 0; iIndex < placeholder_gamepads.size(); iIndex += 1) { + for( iIndex2 = 0; iIndex2 < placeholder_gamepads[iIndex].alt_controller_shared_list.size(); iIndex2 += 1) { + struct alt_override_gamepad_t tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2]; + if(tSharedInfo.alt_controller_overridesessiongamepads != NULL ) { + for(iIndex3 =0; iIndex3 < (tSharedInfo.alt_controller_overridesessiongamepads)->size(); iIndex3++) { + if( pToDelete == &((*(tSharedInfo.alt_controller_overridesessiongamepads))[iIndex3]) ) { + if( iIndex3 == tSharedInfo.alt_controller_overridesessioncontroller ) { + tSharedInfo.alt_controller_overridesessiongamepads = NULL; + tSharedInfo.alt_controller_overridesessionid = -1; + placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2] = tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2]; + break; + } + } + } + } + } + for( iIndex2 = 0; iIndex2 < placeholder_gamepads[iIndex].alt_controller_undo_shared_list.size(); iIndex2 += 1) { + struct alt_override_gamepad_t tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2]; + if(tSharedInfo.alt_controller_overridesessiongamepads != NULL ) { + for(iIndex3 =0; iIndex3 < (tSharedInfo.alt_controller_overridesessiongamepads)->size(); iIndex3++) { + if( pToDelete == &((*(tSharedInfo.alt_controller_overridesessiongamepads))[iIndex3]) ) { + // Check if the controller numbers match + if( iIndex3 == tSharedInfo.alt_controller_overridesessioncontroller ) { + tSharedInfo.alt_controller_overridesessiongamepads = NULL; + tSharedInfo.alt_controller_overridesessionid = -1; + placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2] = tSharedInfo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2]; + break; + } + } + } + } + } + } + // See if the controller is a member of the placeholder vector + if( gamepad.alt_controller_placeholder_index >= 0 ) { + // Set the controller back into the placeholder array as useable + (placeholder_gamepads[ gamepad.alt_controller_placeholder_index]).alt_controller_placeholder = 1; + + // Reset the real indexes + (placeholder_gamepads[ gamepad.alt_controller_placeholder_index]).alt_controller_real_index = -1; + (placeholder_gamepads[ gamepad.alt_controller_real_index]).alt_controller_real_devicename = -1; + } + free_gamepad_but_not_id(platf_input, gamepad.id, 0); + + input->gamepads[controllerNumber].id = -1; + input->gamepads[controllerNumber].alt_controller_placeholder = 0; + + gamepad_t gamepadempty; + input->gamepads[controllerNumber] = gamepadempty; + // Index within the client of the gamepads array + input->gamepads[controllerNumber].alt_controller_real_index = -1; + // Set the index that represents the string for the gamepad + input->gamepads[controllerNumber].alt_controller_real_devicename = -1; + input->gamepads[controllerNumber].alt_controller_placeholder_index = -1; + input->gamepads[controllerNumber].id = -1; + input->gamepads[controllerNumber].alt_controller_placeholder = 0; + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + } + } + return 0; + } + + // iOverride = 4 means that we are toggling the share mode either on or off; iOverride = 5 means that we are redoing the entrance into the share mode assuming we already in the share mode. This usually means that a controller was added. If share mode was not active, then nothing is done + int simplealtOrderingAssignGamepad( int iOverride ) { + int iReturn = 0; + + if (config::input.enable_alt_controller_numbering_mode == true) { + struct config::sDeviceNameOrder ListNumbers; + std::string sLocalDeviceName; + int iIndex; + int iIndex2; + + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + if( iOverride == 4 || iOverride == 5) { + if( (bAltControllerShareMode == false && iOverride == 4) || (bAltControllerShareMode == true && iOverride == 5) ) { + // Go through all of the placeholder array and add the ones from the shared list + for( iIndex = 0; iIndex < placeholder_gamepads.size(); iIndex += 1) { + for( iIndex2 = 0; iIndex2 < placeholder_gamepads[iIndex].alt_controller_shared_list.size(); iIndex2 += 1) { + struct alt_override_gamepad_t tSharedInfo; + struct alt_override_gamepad_t tSharedInfoUndo; + tSharedInfo = placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2]; + tSharedInfoUndo = placeholder_gamepads[iIndex].alt_controller_shared_list[iIndex2]; + if(tSharedInfo.alt_controller_overridesessiongamepads != NULL ) { + // Save the current ID used by the controller for the undo list so it can be restored + // This is to ignore adding to the undo list if controllers come in when already stated; Those controllers will need to be reinitialized when sharing ends + if( iOverride != 5) { + tSharedInfoUndo.alt_controller_overridesessionid = (*(tSharedInfo.alt_controller_overridesessiongamepads))[tSharedInfo.alt_controller_overridesessioncontroller].id; + tSharedInfoUndo.alt_controller_overrideplaceholder = (*(tSharedInfo.alt_controller_overridesessiongamepads))[tSharedInfo.alt_controller_overridesessioncontroller].alt_controller_placeholder; + placeholder_gamepads[iIndex].alt_controller_undo_shared_list.push_back( tSharedInfoUndo ); + } + // Actually perform the sharing operation + //tSharedInfo.alt_controller_overridesessionid = (input->gamepads[iIndex2]).id; + (*(tSharedInfo.alt_controller_overridesessiongamepads))[tSharedInfo.alt_controller_overridesessioncontroller].id = tSharedInfo.alt_controller_overridesessionid; + if( tSharedInfo.alt_controller_overrideplaceholder >= 0 ) { + (*(tSharedInfo.alt_controller_overridesessiongamepads))[tSharedInfo.alt_controller_overridesessioncontroller].alt_controller_placeholder = tSharedInfo.alt_controller_overrideplaceholder; + } + } + } + } + bAltControllerShareMode = true; + } + else if( bAltControllerShareMode == true ) { + // Go through all of the placeholder array and restore those from the undo list + for( iIndex = 0; iIndex < placeholder_gamepads.size(); iIndex += 1) { + for( iIndex2 = 0; iIndex2 < placeholder_gamepads[iIndex].alt_controller_undo_shared_list.size(); iIndex2 += 1) { + struct alt_override_gamepad_t tSharedInfoUndo; + tSharedInfoUndo = placeholder_gamepads[iIndex].alt_controller_undo_shared_list[iIndex2]; + if(tSharedInfoUndo.alt_controller_overridesessiongamepads != NULL ) { + // Actually undo the sharing operation + (*(tSharedInfoUndo.alt_controller_overridesessiongamepads))[tSharedInfoUndo.alt_controller_overridesessioncontroller].id = tSharedInfoUndo.alt_controller_overridesessionid; + if( tSharedInfoUndo.alt_controller_overrideplaceholder >= 0 ) { + (*(tSharedInfoUndo.alt_controller_overridesessiongamepads))[tSharedInfoUndo.alt_controller_overridesessioncontroller].alt_controller_placeholder = tSharedInfoUndo.alt_controller_overrideplaceholder; + } + } + } + placeholder_gamepads[iIndex].alt_controller_undo_shared_list.clear(); + } + bAltControllerShareMode = false; + } + } + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + return iReturn; + } + return iReturn; + } + int altOrderingAssignGamepad(std::shared_ptr &input, int controllerNumber, platf::gamepad_arrival_t &arrival, int iOverride) { + bool bDoLegacyAdd = false; + bool bCompletedAdd = false; + + bool bSharedAdd = false; + struct config::sDeviceNameOrder ListNumbers; + std::string sLocalDeviceName; + int iIndex; + int iIndex2; + + int iReturn = 0; + if(config::input.enable_alt_controller_numbering_mode == true && iOverride == 0) { + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + int temp_value; + if (input->iAltControllerNameIndex != -1) { + // Search the vector to see if the string is already there + if( + config::alt_gamepad_numbering.sDeviceNames.size() > input->iAltControllerNameIndex + && input->iAltControllerNameIndex >= 0 ) { + + // Get the string + sLocalDeviceName = config::alt_gamepad_numbering.sDeviceNames[input->iAltControllerNameIndex]; + + // Get the number list based on the name + if (matchAlternativeGamepadNumberingString(sLocalDeviceName, ListNumbers) == 0) { + // Go through all of the numbers and see if there is a controller that matches that number + // Sets the vShared list to "-1" which means to do nothing and keep the strict assignment if empty from the user input which we do not have just yet + if( ListNumbers.vShared.size() == 0) { + ListNumbers.vShared.push_back(-1); + } + + if( ListNumbers.vJitterJoysticks.size() == 0) { + ListNumbers.vJitterJoysticks.push_back(0); + } + + if( ListNumbers.vSwapJoysticks.size() == 0) { + ListNumbers.vSwapJoysticks.push_back(0); + } + if( ListNumbers.vJitterJoysticks.size() > 0 ) { + // Loop around the vector if the controller number is greater than the list of jitter controllers + temp_value = ListNumbers.vJitterJoysticks[ controllerNumber % ListNumbers.vJitterJoysticks.size() ]; + input->gamepads[controllerNumber].alt_controller_jitter_joystick_number = temp_value; + } + if( ListNumbers.vSwapJoysticks.size() > 0 ) + { + // Loop around the vector if the controller number is greater than the list of swap joysticks + temp_value = ListNumbers.vSwapJoysticks[ controllerNumber % ListNumbers.vSwapJoysticks.size() ]; + input->gamepads[controllerNumber].alt_controller_swap_joysticks = temp_value; + } + struct alt_override_gamepad_t tSharedInfo; + if( ListNumbers.vShared.size() > 0 ) { + // Loop around the vector if the controller number is greater than the list of shared controllers + iIndex2 = ListNumbers.vShared[ controllerNumber % ListNumbers.vShared.size() ] ; + + tSharedInfo.alt_controller_overridesessioninput = &input; + tSharedInfo.alt_controller_overridesessioncontroller = controllerNumber; // Use this as the index into gamepads array + tSharedInfo.alt_controller_overridesessionarrival = &arrival; + tSharedInfo.alt_controller_overridesessionplaceholdergamepadindex = iIndex2; + if( iIndex2 >= 1 ) { + tSharedInfo.alt_controller_overridesessionid = placeholder_gamepads[iIndex2-1].id; + } + else if( iIndex2 == -1) { + // Do nothing - Do not change the controller + tSharedInfo.alt_controller_overridesessionid = -1; + } + else if( iIndex2 == -2) + { + // Remove the controller in shared mode + tSharedInfo.alt_controller_overridesessionid = -1; + tSharedInfo.alt_controller_overrideplaceholder = 4; + } + // Vector of the gamepads for the session + tSharedInfo.alt_controller_overridesessiongamepads = &(input->gamepads); + + // Need a (iIndex2-1) because the user input starts at 1 but the internal numbers starts at 0 + if( ((iIndex2-1) < placeholder_gamepads.size()) && ((iIndex2-1) >= 0)) { + struct alt_override_gamepad_t tSharedInfoExisting; + bool bSkipSharedListAddition = false; + for( iIndex = 0; iIndex < (placeholder_gamepads[iIndex2-1].alt_controller_shared_list).size(); iIndex += 1 ) { + tSharedInfoExisting = (placeholder_gamepads[iIndex2-1].alt_controller_shared_list)[iIndex]; + if( (tSharedInfoExisting.alt_controller_overridesessioncontroller == tSharedInfo.alt_controller_overridesessioncontroller) && + (tSharedInfoExisting.alt_controller_overridesessiongamepads == tSharedInfo.alt_controller_overridesessiongamepads) && + (tSharedInfoExisting.alt_controller_overridesessionid == tSharedInfo.alt_controller_overridesessionid) ) { + bSkipSharedListAddition = true; + break; + } + } + if( bSkipSharedListAddition == false ) { + (placeholder_gamepads[iIndex2-1].alt_controller_shared_list).push_back( tSharedInfo ); + } + bSharedAdd = true; + } else if( iIndex2 == -2 ) { + // Place the remove controller into the first controller placeholder + // Find where to put the change element + for( iIndex2 = 0; iIndex2 < placeholder_gamepads.size(); iIndex2++) { + if( placeholder_gamepads[ iIndex2 ].id == input->gamepads[controllerNumber].id ) + { + break; + } + } + if( iIndex2 < placeholder_gamepads.size() ) { + (placeholder_gamepads[iIndex2].alt_controller_shared_list).push_back( tSharedInfo ); + } + } + } + for (iIndex2 = 0; iIndex2 < ListNumbers.vOrder.size() && bCompletedAdd == false; iIndex2 += 1) { + // Stop looking directive + if( (ListNumbers.vOrder[ iIndex2 ] == ALT_CONTROLLER_STOP_LOOKING) || + (bAltControllerShareMode == true)) { + break; + } + for (iIndex = 0; iIndex < placeholder_gamepads.size() && bCompletedAdd == false ; iIndex += 1) { + // See if the gamepad number as a place holder matches the number from the string configuration + // The -1 is there because the strings start at 1 whereas the index starts at 0 + // 999 means that any available one is fine + if ((placeholder_gamepads[iIndex].alt_controller_placeholder_index == ((ListNumbers.vOrder[iIndex2]) -1)) || + ((ListNumbers.vOrder[iIndex2]) == ALT_CONTROLLER_ANY_PLACEHOLDER)) { + // See if the gamepad is available + + if (placeholder_gamepads[iIndex].alt_controller_placeholder == 1) { + // Found the controller to match up + + // Copy from the placeholder to the real controller needed + input->gamepads[controllerNumber].id = placeholder_gamepads[iIndex].id; + + // Remove the placeholder values as that one cannot be used. + placeholder_gamepads[iIndex].alt_controller_placeholder = 3; + + // Set the state to controller that it is a placeholder one that is being used so special actions need to be done for deletion + input->gamepads[controllerNumber].alt_controller_placeholder = 2; + + // Index within the client of the gamepads array + input->gamepads[controllerNumber].alt_controller_real_index = controllerNumber; + + // Set the index that represents the string for the gamepad + input->gamepads[controllerNumber].alt_controller_real_devicename = input->iAltControllerNameIndex; + input->gamepads[controllerNumber].alt_controller_placeholder_index = placeholder_gamepads[iIndex].alt_controller_placeholder_index; + // Connect up the feedback_queue only + iReturn = platf::alloc_gamepad(platf_input, { placeholder_gamepads[ iIndex].id, (uint8_t)controllerNumber, (uint8_t)iIndex}, arrival, input->feedback_queue ); + bCompletedAdd = true; + } + } + if ((ListNumbers.vOrder[iIndex2] == ALT_CONTROLLER_NO_PLACEHOLDER)) { + if (bCompletedAdd == false) { + // Do legacy actions since one can just add another one + bDoLegacyAdd = true; + } + } + } + } + } + } + } + // Add any new controllers to be shared if in the shared mode + if( bAltControllerShareMode == true ) { + simplealtOrderingAssignGamepad(5); + } + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + + // Share mode has been assigned but it does not do anything + if( bCompletedAdd == false && bSharedAdd == true && bAltControllerShareMode == false ) + { + input->gamepads[controllerNumber].alt_controller_placeholder = 2; + return iReturn; + } + + // Need to wait until Share Mode is off to assign controller permanently + if( bCompletedAdd == false && bSharedAdd == true && bAltControllerShareMode == true ) + { + input->gamepads[controllerNumber].alt_controller_placeholder = 5; + return iReturn; + } + if (bCompletedAdd == false) { + if (bDoLegacyAdd == true) { + return ALT_CONTROLLER_NO_PLACEHOLDER; + } else { + input->gamepads[controllerNumber].alt_controller_placeholder = 4; + return ALT_CONTROLLER_ASSIGN_FAILED; + } + } + } + return iReturn; + } + + int altOrderingInitialPlaceholderAllocation(std::shared_ptr &input, platf::gamepad_arrival_t &arrival, int iControllerNumber) { + + if (config::input.enable_alt_controller_numbering_mode == true) { + int iNumberOfPlaceholders = config::input.alt_controller_count; + int iIndexPlaceholder; + int iCount; + int iStartCount = 2; + auto iOld_OS = alloc_id_request(gamepadMask, -1); + int iPlaceHolderIndex = 15; + int allocatedIndex = 0; + int iPlaceholdersAllocated = 0; + + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + if (config::alt_gamepad_numbering.bFirstTimeControllerAllocation == true ) { + placeholder_gamepads.resize(MAX_GAMEPADS); + + + iIndexPlaceholder = 16; + int iLoopCount = 0; + + iCount = iStartCount; + + // Setup the mode correctly - STRICT or SHARED or BOTH + if( input::acm_from_enum_string(config::input.alt_controller_mode) == input::acm_e::ALT_CONTROLLER_STRICT ) { + bEnableAltControllerModeKeypress = false; + bAltControllerShareMode = false; + } + else if( input::acm_from_enum_string(config::input.alt_controller_mode) == input::acm_e::ALT_CONTROLLER_SHARED ) { + bEnableAltControllerModeKeypress = false; + bAltControllerShareMode = true; + } + else if( input::acm_from_enum_string(config::input.alt_controller_mode) == input::acm_e::ALT_CONTROLLER_BOTH_CONTROLLER_MODES ) { + bEnableAltControllerModeKeypress = true; + bAltControllerShareMode = false; + } + while (iPlaceholdersAllocated < iNumberOfPlaceholders) { + iIndexPlaceholder -= 1; + iIndexPlaceholder = find_empty_gamepad( input->gamepads ); + + if (iIndexPlaceholder != -1) { + iOld_OS = alloc_id_request(gamepadMask, iCount); + iPlaceHolderIndex = iControllerNumber; + input->gamepads[iPlaceHolderIndex].alt_controller_placeholder = 1; + if (iOld_OS != -1) { + + // Make all of the controllers in the same element in the gamepad array, and then move them to the holder array + input->gamepads[iPlaceHolderIndex].id = iOld_OS; + input->gamepads[iPlaceHolderIndex].alt_controller_placeholder = 1; + input->gamepads[iPlaceHolderIndex].alt_controller_placeholder_index = iLoopCount; + input->gamepads[iPlaceHolderIndex].alt_controller_real_index = -1; + input->gamepads[iPlaceHolderIndex].alt_controller_real_devicename = -1; + + allocatedIndex = platf::alloc_gamepad(platf_input, { iOld_OS, (uint8_t)(((uint8_t)MAX_GAMEPADS)-(1)-((uint8_t)iLoopCount)), ((uint8_t)iLoopCount)}, arrival, config::placeholder_feedback_queues[iOld_OS] ); + + if (allocatedIndex) { + free_id(gamepadMask, iOld_OS); + return -1; + } + placeholder_gamepads[ iLoopCount] = input->gamepads[iPlaceHolderIndex]; + iPlaceholdersAllocated += 1; + } + iLoopCount += 1; + } + iCount +=1 ; + if (iCount >= gamepadMask.size()) { + break; + } + } + config::alt_gamepad_numbering.bFirstTimeControllerAllocation = false; + } + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + } + return 0; + } + void passthrough(PNV_UNICODE_PACKET packet) { if (!config::input.keyboard) { return; @@ -844,29 +1528,54 @@ namespace input { return; } - if (input->gamepads[packet->controllerNumber].id >= 0) { - BOOST_LOG(warning) << "ControllerNumber already allocated ["sv << packet->controllerNumber << ']'; + // If controller has already been refused allocation because there is no more preallocated ones, then just return and drop messages + if( config::input.enable_alt_controller_numbering_mode == true && input->gamepads[packet->controllerNumber].alt_controller_placeholder == 4 ) { return; } + if( config::input.enable_alt_controller_numbering_mode == false ) + { + if (input->gamepads[packet->controllerNumber].id >= 0) { + BOOST_LOG(warning) << "ControllerNumber already allocated ["sv << packet->controllerNumber << ']'; + return; + } + } + platf::gamepad_arrival_t arrival { packet->type, util::endian::little(packet->capabilities), util::endian::little(packet->supportedButtonFlags), }; - auto id = alloc_id(gamepadMask); - if (id < 0) { - return; + + int allocatedIndex = 0; + + // This is where the work is done for the alt controller number + if( (config::input.enable_alt_controller_numbering_mode == true) && (config::alt_gamepad_numbering.bFirstTimeControllerAllocation == true) && (input->gamepads[packet->controllerNumber].alt_controller_placeholder != 4) ) { + // Preallocate if not done yet + altOrderingInitialPlaceholderAllocation(input, arrival, packet->controllerNumber ); } - // Allocate a new gamepad - if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber}, arrival, input->feedback_queue)) { - free_id(gamepadMask, id); - return; + if( (config::input.enable_alt_controller_numbering_mode == true) && (input->gamepads[packet->controllerNumber].alt_controller_placeholder != 4) && ( ( (input->gamepads[packet->controllerNumber].alt_controller_placeholder == 5 && (bAltControllerShareMode != true)) || (input->gamepads[packet->controllerNumber].alt_controller_placeholder != 5) ) )) { + // Assign gamepad to the session + allocatedIndex = altOrderingAssignGamepad( input, packet->controllerNumber, arrival, 0 ); } - input->gamepads[packet->controllerNumber].id = id; + if( config::input.enable_alt_controller_numbering_mode == false || allocatedIndex == ALT_CONTROLLER_NO_PLACEHOLDER ) { + auto id = alloc_id(gamepadMask); + if (id < 0) { + return; + } + + // Allocate a new gamepad + if (platf::alloc_gamepad(platf_input, {id, packet->controllerNumber, (uint8_t)-1}, arrival, input->feedback_queue)) { + free_id(gamepadMask, id); + return; + } + + input->gamepads[packet->controllerNumber].id = id; + input->gamepads[packet->controllerNumber].alt_controller_placeholder = 0; + } } /** @@ -1078,6 +1787,71 @@ namespace input { platf::gamepad_battery(platf_input, battery); } + bool gamepad_swap_joysticks(platf::gamepad_state_t ¤t, platf::gamepad_state_t &previous, int iPerformSwap) { + if( iPerformSwap > 0 ) + { + platf::gamepad_state_t &report = current; + platf::gamepad_state_t temp; + + temp.rsX = report.rsX; + temp.rsY = report.rsY; + temp.lsX = report.lsX; + temp.lsY = report.lsY; + + report.rsX = temp.lsX; + report.rsY = temp.lsY; + report.lsX = temp.rsX; + report.lsY = temp.rsY; + + return true; + } + return false; + } + + bool gamepad_check_ranges(platf::gamepad_state_t ¤t, platf::gamepad_state_t &previous, int iAmount) { + + platf::gamepad_state_t &report = current; + platf::gamepad_state_t &report_previous = previous; + + if( report.buttonFlags != report_previous.buttonFlags || + report.lt != report_previous.lt || + report.rt != report_previous.rt) + { + return true; + } + if( (std::abs( report.lsX ) > iAmount) ) + { + return true; + } + if( (std::abs( report.lsY ) > iAmount) ) + { + return true; + } + if( (std::abs( report.rsX ) > iAmount) ) + { + return true; + } + if( (std::abs( report.rsY ) > iAmount) ) + { + return true; + } + + report.rsX = 0; + report.rsY = 0; + report.lsX = 0; + report.lsY = 0; + + + if( (report.rsX != report_previous.rsX) || + (report.rsY != report_previous.rsY) || + (report.lsX != report_previous.lsX) || + (report.lsY != report_previous.lsY) ) + { + return true; + } + + return false; + } void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { if (!config::input.controller) { return; @@ -1089,26 +1863,59 @@ namespace input { return; } + // Return Immediately if the controller cannot fit into the alternate gamepad numbering and has been marked as such + if( config::input.enable_alt_controller_numbering_mode == true && input->gamepads[packet->controllerNumber].alt_controller_placeholder == 4 ) { + return; + } + auto &gamepad = input->gamepads[packet->controllerNumber]; + int allocatedIndex = 0; // If this is an event for a new gamepad, create the gamepad now. Ideally, the client would // send a controller arrival instead of this but it's still supported for legacy clients. - if ((packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id < 0) { - auto id = alloc_id(gamepadMask); - if (id < 0) { - return; + if ((packet->activeGamepadMask & (1 << packet->controllerNumber)) && ((gamepad.id < 0) || (( input->gamepads[packet->controllerNumber].alt_controller_placeholder == 5) && (bAltControllerShareMode != true) && config::input.enable_alt_controller_numbering_mode ))) { + if ( + config::input.enable_alt_controller_numbering_mode + && config::alt_gamepad_numbering.bFirstTimeControllerAllocation + && input->gamepads[packet->controllerNumber].alt_controller_placeholder != 4 + ) { + platf::gamepad_arrival_t arrival{}; + altOrderingInitialPlaceholderAllocation(input, arrival, packet->controllerNumber); + } + if ( + ( config::input.enable_alt_controller_numbering_mode + && input->gamepads[packet->controllerNumber].alt_controller_placeholder != 4 ) + && ( ( (input->gamepads[packet->controllerNumber].alt_controller_placeholder == 5 && (bAltControllerShareMode != true)) || (input->gamepads[packet->controllerNumber].alt_controller_placeholder != 5 )) ) + ) { + platf::gamepad_arrival_t arrival{}; + allocatedIndex = altOrderingAssignGamepad( input, packet->controllerNumber, arrival, 0 ); } - if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber}, {}, input->feedback_queue)) { - free_id(gamepadMask, id); - return; + if (!config::input.enable_alt_controller_numbering_mode || allocatedIndex == ALT_CONTROLLER_NO_PLACEHOLDER) { + auto id = alloc_id(gamepadMask); + if (id < 0) { + return; } + if (platf::alloc_gamepad(platf_input, {id, (uint8_t) packet->controllerNumber, (uint8_t)-1}, {}, input->feedback_queue)) { + free_id(gamepadMask, id); + return; + } - gamepad.id = id; + gamepad.id = id; + gamepad.alt_controller_placeholder = 0; + } } else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) { // If this is the final event for a gamepad being removed, free the gamepad and return. - free_gamepad(platf_input, gamepad.id); - gamepad.id = -1; + + if ( + config::input.enable_alt_controller_numbering_mode + && (gamepad.alt_controller_placeholder == 2 || gamepad.alt_controller_placeholder == 3) + ) { + altOrderingDeleteGamepad(input, packet->controllerNumber); + } else { + free_gamepad(platf_input, gamepad.id); + gamepad.id = -1; + } return; } @@ -1188,7 +1995,11 @@ namespace input { } } - platf::gamepad_update(platf_input, gamepad.id, gamepad_state); + if( gamepad_check_ranges(gamepad_state, gamepad.gamepad_state, gamepad.alt_controller_jitter_joystick_number ) == true ) { + gamepad_swap_joysticks(gamepad_state, gamepad.gamepad_state, gamepad.alt_controller_swap_joysticks ); + platf::gamepad_update(platf_input, gamepad.id, gamepad_state); + } else { + } gamepad.gamepad_state = gamepad_state; } @@ -1576,8 +2387,8 @@ namespace input { * @param input The input context pointer. * @param input_data The input message. */ - void passthrough(std::shared_ptr &input, std::vector &&input_data, const crypto::PERM& permission) { - // No input permissions at all + void passthrough(std::shared_ptr &input, std::vector &&input_data, const crypto::PERM& permission, int iAltControllerNameIndex ) { + input->iAltControllerNameIndex = iAltControllerNameIndex; if (!(permission & crypto::PERM::_all_inputs)) { return; } diff --git a/src/input.h b/src/input.h index 37fe7d607..b4c9c37ae 100644 --- a/src/input.h +++ b/src/input.h @@ -12,12 +12,16 @@ #include "thread_safe.h" #include "crypto.h" +namespace config { + extern std::vector placeholder_feedback_queues; +} + namespace input { struct input_t; void print(void *input); void reset(std::shared_ptr &input); - void passthrough(std::shared_ptr &input, std::vector &&input_data, const crypto::PERM& permission); + void passthrough(std::shared_ptr &input, std::vector &&input_data, const crypto::PERM& permission, int iAltControllerNameIndex ); [[nodiscard]] std::unique_ptr init(); diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index e6df1c0bb..8491ef736 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -64,6 +64,8 @@ namespace nvhttp { static std::string otp_device_name; static std::chrono::time_point otp_creation_time; + bool update_global_controller_info( void ); + class SunshineHTTPSServer: public SimpleWeb::ServerBase { public: SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file): @@ -251,7 +253,10 @@ namespace nvhttp { named_cert_node["enable_legacy_ordering"] = named_cert_p->enable_legacy_ordering; named_cert_node["allow_client_commands"] = named_cert_p->allow_client_commands; named_cert_node["always_use_virtual_display"] = named_cert_p->always_use_virtual_display; - + named_cert_node["controller_list_numbers"] = named_cert_p->controller_list_numbers; + named_cert_node["controller_list_shared"] = named_cert_p->controller_list_shared; + named_cert_node["controller_list_jitter_joysticks"] = named_cert_p->controller_list_jitter_joysticks; + named_cert_node["controller_list_swap_joysticks"] = named_cert_p->controller_list_swap_joysticks; // Add "do" commands if available. if (!named_cert_p->do_cmds.empty()) { nlohmann::json do_cmds_node = nlohmann::json::array(); @@ -274,6 +279,7 @@ namespace nvhttp { } } + update_global_controller_info(); root["root"]["named_devices"] = named_cert_nodes; try { @@ -330,6 +336,11 @@ namespace nvhttp { named_cert_p->enable_legacy_ordering = true; named_cert_p->allow_client_commands = true; named_cert_p->always_use_virtual_display = false; + named_cert_p->controller_list_numbers = ""; + named_cert_p->controller_list_shared = ""; + named_cert_p->controller_list_jitter_joysticks = ""; + named_cert_p->controller_list_swap_joysticks = ""; + client.named_devices.emplace_back(named_cert_p); } } @@ -351,6 +362,12 @@ namespace nvhttp { // Load command entries for "do" and "undo" keys. named_cert_p->do_cmds = extract_command_entries(el, "do"); named_cert_p->undo_cmds = extract_command_entries(el, "undo"); + // Load Controller List + named_cert_p->controller_list_numbers = el.value("controller_list_numbers", ""); + named_cert_p->controller_list_shared = el.value("controller_list_shared", ""); + named_cert_p->controller_list_jitter_joysticks = el.value("controller_list_jitter_joysticks",""); + named_cert_p->controller_list_swap_joysticks = el.value("controller_list_swap_joysticks",""); + client.named_devices.emplace_back(named_cert_p); } } @@ -360,6 +377,7 @@ namespace nvhttp { for (auto &named_cert : client.named_devices) { cert_chain.add(named_cert); } + update_global_controller_info(); client_root = client; } @@ -1025,7 +1043,10 @@ namespace nvhttp { named_cert_node["enable_legacy_ordering"] = named_cert->enable_legacy_ordering; named_cert_node["allow_client_commands"] = named_cert->allow_client_commands; named_cert_node["always_use_virtual_display"] = named_cert->always_use_virtual_display; - + named_cert_node["controller_list_numbers"] = named_cert->controller_list_numbers; + named_cert_node["controller_list_shared"] = named_cert->controller_list_shared; + named_cert_node["controller_list_jitter_joysticks"] = named_cert->controller_list_jitter_joysticks; + named_cert_node["controller_list_swap_joysticks"] = named_cert->controller_list_swap_joysticks; // Add "do" commands if available if (!named_cert->do_cmds.empty()) { nlohmann::json do_cmds_node = nlohmann::json::array(); @@ -1657,6 +1678,8 @@ namespace nvhttp { bool verified = false; p_named_cert_t named_cert_p; + update_global_controller_info(); + auto fg = util::fail_guard([&]() { char subject_name[256]; @@ -1805,6 +1828,8 @@ namespace nvhttp { return false; } + bool update_global_controller_info( void ); + bool update_device_info( const std::string& uuid, const std::string& name, @@ -1814,9 +1839,14 @@ namespace nvhttp { const crypto::PERM newPerm, const bool enable_legacy_ordering, const bool allow_client_commands, - const bool always_use_virtual_display + const bool always_use_virtual_display, + const std::string& controller_list_numbers, + const std::string& controller_list_shared, + const std::string& controller_list_jitter_joysticks, + const std::string& controller_list_swap_joysticks ) { find_and_udpate_session_info(uuid, name, newPerm); + update_global_controller_info(); client_t &client = client_root; auto it = client.named_devices.begin(); @@ -1831,6 +1861,10 @@ namespace nvhttp { named_cert_p->enable_legacy_ordering = enable_legacy_ordering; named_cert_p->allow_client_commands = allow_client_commands; named_cert_p->always_use_virtual_display = always_use_virtual_display; + named_cert_p->controller_list_numbers = controller_list_numbers; + named_cert_p->controller_list_shared = controller_list_shared; + named_cert_p->controller_list_jitter_joysticks = controller_list_jitter_joysticks; + named_cert_p->controller_list_swap_joysticks = controller_list_swap_joysticks; save_state(); return true; } @@ -1867,4 +1901,84 @@ namespace nvhttp { return removed; } +// Use this one for getting the controller_list_numbers + bool update_global_controller_info( void ) { + bool updated = true; + client_t &client = client_root; + std::string_view uuid; + std::string controller_list_numbers; + std::string controller_list_shared; + std::string controller_list_jitter_joysticks; + std::string controller_list_swap_joysticks; + + struct config::sDeviceNameOrder sCurrent; + std::vector< struct config::sDeviceNameOrder >VectorAlternateGamepadParameterTemp; + + std::vector Numbers; + std::vector Shared; + std::vector vJitterJoysticks; + std::vector vSwapJoysticks; + + get_all_clients(); + + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + // Get the uuid and the controller_list_numbers + for (auto it = client.named_devices.begin(); it != client.named_devices.end(); it++) { + controller_list_numbers = ((*it)->controller_list_numbers); + controller_list_shared = ((*it)->controller_list_shared); + controller_list_jitter_joysticks = ((*it)->controller_list_jitter_joysticks); + controller_list_swap_joysticks = ((*it)->controller_list_swap_joysticks); + uuid = ((*it)->uuid); + try { + nlohmann::json J = nlohmann::json::parse(controller_list_numbers); + Numbers = J.get>(); + } + catch (std::exception& e) { + } + try { + nlohmann::json JShared = nlohmann::json::parse(controller_list_shared); + Shared = JShared.get>(); + } + catch (std::exception& e) { + } + try { + nlohmann::json JShared = nlohmann::json::parse(controller_list_jitter_joysticks); + vJitterJoysticks = JShared.get>(); + } + catch (std::exception& e) { + } + try { + nlohmann::json JShared = nlohmann::json::parse(controller_list_swap_joysticks); + vSwapJoysticks = JShared.get>(); + } + catch (std::exception& e) { + } + if( Numbers.size() == 0 ) { + Numbers.push_back(999); + } + + sCurrent.sDeviceName = (*it)->name; + sCurrent.sOrder = (*it)->controller_list_numbers; + sCurrent.vOrder = Numbers; + sCurrent.sShared = (*it)->controller_list_shared; + sCurrent.vShared = Shared; + sCurrent.sUuid = (*it)->uuid; + sCurrent.sJitterJoysticks = (*it)->controller_list_jitter_joysticks; + sCurrent.vJitterJoysticks = vJitterJoysticks; + sCurrent.sSwapJoysticks = (*it)->controller_list_swap_joysticks; + sCurrent.vSwapJoysticks = vSwapJoysticks; + + for( int i= 0; i < sCurrent.vOrder.size(); i = i + 1 ) { + } + + VectorAlternateGamepadParameterTemp.push_back( sCurrent ); + + if( (*it)->controller_list_numbers == " " ) { + } + } + config::VectorAlternateGamepadParameters = VectorAlternateGamepadParameterTemp; + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + return updated; + } + } // namespace nvhttp diff --git a/src/nvhttp.h b/src/nvhttp.h index 6d2c6e06c..2d59656f6 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -280,6 +280,10 @@ namespace nvhttp { const crypto::PERM newPerm, const bool enable_legacy_ordering, const bool allow_client_commands, - const bool always_use_virtual_display + const bool always_use_virtual_display, + const std::string& controller_list_numbers, + const std::string& controller_list_shared, + const std::string& controller_list_jitter_joysticks, + const std::string& controller_list_swap_joysticks ); } // namespace nvhttp diff --git a/src/platform/common.h b/src/platform/common.h index 2073d5937..176aa01f2 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -301,6 +301,9 @@ namespace platf { // client. It must be used when communicating back to the client via // the input feedback queue. std::uint8_t clientRelativeIndex; + + // This is the holder relative index that should be used to detect when the holder is being used (Just checking the config::enabled flag is not good enough) + std::uint8_t holderRelativeIndex = -1; }; struct gamepad_arrival_t { @@ -820,6 +823,7 @@ namespace platf { */ int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue); void free_gamepad(input_t &input, int nr); + void detach_feedback_queue_from_gamepad(input_t &input, int nr); /** * @brief Get the supported platform capabilities to advertise to the client. diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 9d182c7f6..c3078054d 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -88,6 +88,8 @@ namespace platf { gamepad_feedback_msg_t last_rumble; gamepad_feedback_msg_t last_rgb_led; + int alt_numbering_feedback_queue_index; + }; constexpr float EARTH_G = 9.80665f; @@ -211,6 +213,9 @@ namespace platf { gamepads.resize(MAX_GAMEPADS); + + placeholder_gamepads.resize(MAX_GAMEPADS); + return 0; } @@ -225,7 +230,9 @@ namespace platf { auto &gamepad = gamepads[id.globalIndex]; assert(!gamepad.gp); + VIGEM_API VIGEM_ERROR status; gamepad.client_relative_index = id.clientRelativeIndex; + gamepad.alt_numbering_feedback_queue_index = id.holderRelativeIndex; gamepad.last_report_ts = std::chrono::steady_clock::now(); // Establish a connect to the ViGEm driver if we don't have one yet @@ -241,49 +248,53 @@ namespace platf { } } - if (gp_type == Xbox360Wired) { - gamepad.gp.reset(vigem_target_x360_alloc()); - XUSB_REPORT_INIT(&gamepad.report.x360); - } else { - gamepad.gp.reset(vigem_target_ds4_alloc()); + if( (config::input.enable_alt_controller_numbering_mode == true) && (config::alt_gamepad_numbering.bFirstTimeControllerAllocation == false ) && (id.holderRelativeIndex != -1) ) { + vigem_target_x360_unregister_notification(gamepad.gp.get()); + } else { + if (gp_type == Xbox360Wired) { + gamepad.gp.reset(vigem_target_x360_alloc()); + XUSB_REPORT_INIT(&gamepad.report.x360); + } else { + gamepad.gp.reset(vigem_target_ds4_alloc()); - // There is no equivalent DS4_REPORT_EX_INIT() - gamepad.report.ds4 = ds4_report_init_ex; + // There is no equivalent DS4_REPORT_EX_INIT() + gamepad.report.ds4 = ds4_report_init_ex; - // Set initial accelerometer and gyro state - ds4_update_motion(gamepad, LI_MOTION_TYPE_ACCEL, 0.0f, EARTH_G, 0.0f); - ds4_update_motion(gamepad, LI_MOTION_TYPE_GYRO, 0.0f, 0.0f, 0.0f); + // Set initial accelerometer and gyro state + ds4_update_motion(gamepad, LI_MOTION_TYPE_ACCEL, 0.0f, EARTH_G, 0.0f); + ds4_update_motion(gamepad, LI_MOTION_TYPE_GYRO, 0.0f, 0.0f, 0.0f); - // Request motion events from the client at 100 Hz - feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(gamepad.client_relative_index, LI_MOTION_TYPE_ACCEL, 100)); - feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(gamepad.client_relative_index, LI_MOTION_TYPE_GYRO, 100)); + // Request motion events from the client at 100 Hz + feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(gamepad.client_relative_index, LI_MOTION_TYPE_ACCEL, 100)); + feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(gamepad.client_relative_index, LI_MOTION_TYPE_GYRO, 100)); - // We support pointer index 0 and 1 - gamepad.available_pointers = 0x3; - } + // We support pointer index 0 and 1 + gamepad.available_pointers = 0x3; + } - auto status = vigem_target_add(client.get(), gamepad.gp.get()); - if (!VIGEM_SUCCESS(status)) { - BOOST_LOG(error) << "Couldn't add Gamepad to ViGEm connection ["sv << util::hex(status).to_string_view() << ']'; + status = vigem_target_add(client.get(), gamepad.gp.get()); + if (!VIGEM_SUCCESS(status)) { + BOOST_LOG(error) << "Couldn't add Gamepad to ViGEm connection ["sv << util::hex(status).to_string_view() << ']'; - return -1; + return -1; + } } gamepad.feedback_queue = std::move(feedback_queue); - if (gp_type == Xbox360Wired) { - status = vigem_target_x360_register_notification(client.get(), gamepad.gp.get(), x360_notify, this); - } else { - status = vigem_target_ds4_register_notification(client.get(), gamepad.gp.get(), ds4_notify, this); - } + if (gp_type == Xbox360Wired) { + status = vigem_target_x360_register_notification(client.get(), gamepad.gp.get(), x360_notify, this); + } else { + status = vigem_target_ds4_register_notification(client.get(), gamepad.gp.get(), ds4_notify, this); + } - if (!VIGEM_SUCCESS(status)) { + if (!VIGEM_SUCCESS(status)) { BOOST_LOG(warning) << "Couldn't register notifications for rumble support ["sv << util::hex(status).to_string_view() << ']'; - } - - return 0; } + return 0; + } + /** * @brief Detaches the specified gamepad * @param nr The gamepad. @@ -402,6 +413,7 @@ namespace platf { } std::vector gamepads; + std::vector placeholder_gamepads; client_t client; }; @@ -1236,6 +1248,26 @@ namespace platf { raw->vigem->free_target(nr); } + void detach_feedback_queue_from_gamepad(input_t &input, int nr) { + auto raw = (input_raw_t *) input.get(); + + if (!raw->vigem) { + return; + } + + auto &gamepad = raw->vigem->gamepads[nr]; + + if (gamepad.repeat_task) { + task_pool.cancel(gamepad.repeat_task); + gamepad.repeat_task = 0; + } + + if (gamepad.gp && vigem_target_is_attached(gamepad.gp.get())) { + vigem_target_x360_unregister_notification(gamepad.gp.get()); + } + return; + } + /** * @brief Converts the standard button flags into X360 format. * @param gamepad_state The gamepad button/axis state sent from the client. diff --git a/src/stream.cpp b/src/stream.cpp index bceeb93d3..59cdb7280 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -84,6 +84,10 @@ using asio::ip::udp; using namespace std::literals; +namespace config { + extern std::vector placeholder_feedback_queues; +} + namespace stream { enum class socket_e : int { @@ -423,6 +427,7 @@ namespace stream { safe::signal_t controlEnd; std::atomic state; + int iAltControllerNameIndex; }; /** @@ -997,8 +1002,7 @@ namespace stream { if (tagged_cipher_length >= 16 + iv.size()) { std::copy(payload.end() - 16, payload.end(), std::begin(iv)); } - - input::passthrough(session->input, std::move(plaintext), session->permission); + input::passthrough(session->input, std::move(plaintext), session->permission, session->iAltControllerNameIndex ); }); server->map(packetTypes[IDX_EXEC_SERVER_CMD], [server](session_t *session, const std::string_view &payload) { @@ -1112,7 +1116,7 @@ namespace stream { // IDX_INPUT_DATA callback will attempt to decrypt unencrypted data, therefore we need pass it directly if (type == packetTypes[IDX_INPUT_DATA]) { plaintext.erase(std::begin(plaintext), std::begin(plaintext) + 4); - input::passthrough(session->input, std::move(plaintext), session->permission); + input::passthrough(session->input, std::move(plaintext), session->permission, session->iAltControllerNameIndex ); } else { server->call(type, session, next_payload, true); } @@ -2145,14 +2149,64 @@ namespace stream { return 0; } + + struct alt_controller_info + { + std::string sDeviceName; + int iIndex; + }; + + int findDeviceNameInAltGamePadNumbering( std::string sToFind ) + { + size_t Index; + size_t Max; + + Max = config::alt_gamepad_numbering.sDeviceNames.size(); + + for( Index = 0; Index < Max; Index += 1 ) { + if( sToFind == config::alt_gamepad_numbering.sDeviceNames[Index] ) { + return Index; + } + } + return -1; + } + std::shared_ptr alloc(config_t &config, rtsp_stream::launch_session_t &launch_session) { auto session = std::make_shared(); + int iTempAltGamepad = 0; auto mail = std::make_shared(); session->shutdown_event = mail->event(mail::shutdown); session->launch_session_id = launch_session.id; session->device_name = launch_session.device_name; + if( config::input.enable_alt_controller_numbering_mode == true ) { + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.lock(); + iTempAltGamepad = findDeviceNameInAltGamePadNumbering( session->device_name ); + + // If not found, set the index to the next element after a pushback + if( iTempAltGamepad == -1 ) { + iTempAltGamepad = config::alt_gamepad_numbering.sDeviceNames.size(); + config::alt_gamepad_numbering.sDeviceNames.push_back( session->device_name ); + } + + // Set the index to the appropriate element + session->iAltControllerNameIndex = iTempAltGamepad; + + // Initialize all of the feedback queues properly + if( config::alt_gamepad_numbering.bFirstTimeFeedbackQueues == true ) { + int x; + platf::feedback_queue_t a; + for(x = 0; x < 16; x++) { + a = mail->queue(mail::gamepad_feedback); + config::placeholder_feedback_queues[x] = a; + } + config::alt_gamepad_numbering.bFirstTimeFeedbackQueues = false; + } + config::alt_gamepad_numbering.alt_gamepad_numbering_mutex.unlock(); + } else { + } + session->device_uuid = launch_session.unique_id; session->permission = launch_session.perm; diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 3ebad8ffb..671421fe1 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -178,6 +178,9 @@

{{ $t('config.configuration') }}

"enable_input_only_mode": "disabled", "forward_rumble": "enabled", "keybindings": "[0x10,0xA0,0x11,0xA2,0x12,0xA4]", // todo: add this to UI + "enable_alt_controller_numbering_mode": "disabled", + "alt_controller_count": 4, + "alt_controller_mode": "strict", }, }, { diff --git a/src_assets/common/assets/web/configs/tabs/Inputs.vue b/src_assets/common/assets/web/configs/tabs/Inputs.vue index f92baecbd..6978df377 100644 --- a/src_assets/common/assets/web/configs/tabs/Inputs.vue +++ b/src_assets/common/assets/web/configs/tabs/Inputs.vue @@ -188,6 +188,37 @@ const config = ref(props.config) default="true" > + +
+ + diff --git a/src_assets/common/assets/web/pin.html b/src_assets/common/assets/web/pin.html index 63a5f00f3..8db9bcc8d 100644 --- a/src_assets/common/assets/web/pin.html +++ b/src_assets/common/assets/web/pin.html @@ -186,6 +186,114 @@

{{ $t('pin.device_management') }}

+ {{ $t('config.add') }} + +
@@ -304,6 +412,22 @@

{{ $t('pin.device_management') }}

_all: 0x071F1F00 }; + const altControllerMapping = { + // Number representing any preallocated controller + alt_controller_any_placeholder: 999, + // Number representing to stop looking for controllers + alt_controller_stop_looking: 998, + // Number representing to look for a non-preallocated controller + alt_controller_no_placeholder: 1000, + // Number representing the no change controller for shared list + alt_controller_shared_list_no_change_controller: -1, + // Number representing the remove controller for shared list + alt_controller_shared_list_remove_controller: -2, + // Number representing the enable swap joysticks for the swap list + alt_controller_swap_joysticks_list_disable: 0, + alt_controller_swap_joysticks_list_enable: 1 + }; + const permissionGroups = [ { name: 'Action', permissions: [ { @@ -370,7 +494,24 @@

{{ $t('pin.device_management') }}

clients: [], showApplyMessage: false, unpairAllPressed: false, - unpairAllStatus: null + unpairAllStatus: null, + // Will be read in from the refreshClients() + altControllerCount: 4, + // Will be read in from the refreshClients() + enableAltControllerNumberingMode: false, + // Will be read in from the refreshClients() + altControllerMode: 'strict', + // Constants for the special numbers of the controllers for conditions + alt_controller_any_placeholder: altControllerMapping.alt_controller_any_placeholder, + alt_controller_stop_looking: altControllerMapping.alt_controller_stop_looking, + alt_controller_no_placeholder: altControllerMapping.alt_controller_no_placeholder, + // Number representing the no change controller for shared list + alt_controller_shared_list_no_change_controller: altControllerMapping.alt_controller_shared_list_no_change_controller, + // Number representing the remove controller for shared list + alt_controller_shared_list_remove_controller: altControllerMapping.alt_controller_shared_list_remove_controller, + // Number representing the enable swap joysticks for the swap list + alt_controller_swap_joysticks_list_disable: altControllerMapping.alt_controller_swap_joysticks_list_disable, + alt_controller_swap_joysticks_list_enable: altControllerMapping.alt_controller_swap_joysticks_list_enable } } @@ -396,7 +537,7 @@

{{ $t('pin.device_management') }}

}, created() { this.refreshClients(); - }, + }, methods: { switchTab(currentTab) { location.hash = currentTab; @@ -404,6 +545,8 @@

{{ $t('pin.device_management') }}

Object.assign(this, data(), { clients }); hostInfoCache = null; clearTimeout(resetOTPTimeout); + // Need to restore the Alternate controller limits after the switch; This also fixes a bug because the this.platform will also get reset without doing a refreshClient() + this.refreshClients(); }, editHost() { this.editingHost = !this.editingHost; @@ -522,6 +665,245 @@

{{ $t('pin.device_management') }}

removeCmd(arr, idx) { arr.splice(idx, 1); }, + languageTranslationControllerList( list, mode ) { + const newlist = []; + // Go through all of the elements in the list and replace the special numbers with text + for( const element of list ) { + // Go from numbers to string elements - Typically after reading from the state file or after adding a new number + if( mode === 1 ) { + if( element === altControllerMapping.alt_controller_any_placeholder ) { + newlist.push(this.i18n.t('pin.alt_controller_order_list_any_preallocated_button')); + } else if( element === altControllerMapping.alt_controller_no_placeholder ) { + newlist.push(this.i18n.t('pin.alt_controller_order_list_new_non_preallocated_button')); + } else if (element === altControllerMapping.alt_controller_stop_looking ) { + newlist.push(this.i18n.t('pin.alt_controller_order_list_stop_looking_button')); + } else { + newlist.push(element); + } + } + if( mode === 2 ) { + // Go from string elements to numbers - Typically when saving to the state file + if( element === this.i18n.t('pin.alt_controller_order_list_any_preallocated_button') ) { + newlist.push( altControllerMapping.alt_controller_any_placeholder ); + } else if( element === this.i18n.t('pin.alt_controller_order_list_new_non_preallocated_button') ) { + newlist.push(altControllerMapping.alt_controller_no_placeholder ); + } else if (element === this.i18n.t('pin.alt_controller_order_list_stop_looking_button') ) { + newlist.push(altControllerMapping.alt_controller_stop_looking); + } else { + newlist.push(element); + } + } + // Shared List - Go from string to numbers + if( mode == 4 ) { + if( element === this.i18n.t('pin.alt_controller_shared_list_no_change_controller_button') ) { + newlist.push( altControllerMapping.alt_controller_shared_list_no_change_controller ); + } + else if ( element === this.i18n.t('pin.alt_controller_shared_list_remove_controller_button') ) { + newlist.push( altControllerMapping.alt_controller_shared_list_remove_controller ); + } else { + newlist.push(element); + } + } + // Shared List - Go from numbers to string + if( mode === 3 ) { + if( element === altControllerMapping.alt_controller_shared_list_no_change_controller ) { + newlist.push(this.i18n.t('pin.alt_controller_shared_list_no_change_controller_button' ) ); + } else if ( element === altControllerMapping.alt_controller_shared_list_remove_controller ) { + newlist.push(this.i18n.t('pin.alt_controller_shared_list_remove_controller_button' ) ); + } else { + newlist.push(element); + } + } + // SwapJoystick - Enable and disable - Go from string to numbers + if( mode == 6 ) { + if( element === this.i18n.t('pin.alt_controller_swap_joysticks_list_enable_button' ) ) { + newlist.push( altControllerMapping.alt_controller_swap_joysticks_list_enable ); + } + else if ( element === this.i18n.t('pin.alt_controller_swap_joysticks_list_disable_button' ) ) { + newlist.push( altControllerMapping.alt_controller_swap_joysticks_list_disable ); + } else { + newlist.push(element); + } + } + // SwapJoystick -- Enable and disable - Go from numbers to string + if( mode == 5 ) { + if( element === altControllerMapping.alt_controller_swap_joysticks_list_enable ) { + newlist.push(this.i18n.t('pin.alt_controller_swap_joysticks_list_enable_button' ) ); + } + else if ( element === altControllerMapping.alt_controller_swap_joysticks_list_disable ) { + newlist.push(this.i18n.t('pin.alt_controller_swap_joysticks_list_disable_button' ) ); + } else { + newlist.push(element); + } + } + } + return newlist; + }, + buttonControllerListAction( client, n ) { + if( n === 0 ) { + // Clear the internal number list + client.editControllerListNumbers.splice(0); + } else { + // Just add the passed number to the list + client.editControllerListNumbers.push( n ); + } + // Replace the number element with the string element for ease of use by the user + client.editControllerListNumbers = this.languageTranslationControllerList( client.editControllerListNumbers, 1 ); + return; + }, + buttonControllerSharedListAction( client, n ) { + if( n === 0 ) { + // Clear the internal number list + client.editControllerListShared.splice(0); + } else { + // Just add the passed number to the list + client.editControllerListShared.push( n ); + } + client.editControllerListShared = this.languageTranslationControllerList( client.editControllerListShared, 3); + return; + }, + buttonControllerJitterJoysticksListAction( client, n ) { + if( n === -1 ) { + // Clear the internal number list + client.editControllerListJitterJoysticks.splice(0); + } else { + // Just add the passed number to the list + client.editControllerListJitterJoysticks.push( n ); + } + return; + }, + buttonControllerSwapJoysticksListAction( client, n ) { + if( n === -1 ) { + // Clear the internal number list + client.editControllerListSwapJoysticks.splice(0); + } else { + // Just add the passed number to the list + client.editControllerListSwapJoysticks.push( n ); + } + // Replace the number element with the string element for ease of use by the user + client.editControllerListSwapJoysticks = this.languageTranslationControllerList( client.editControllerListSwapJoysticks, 5 ); + return; + }, + checkControllerDisable( client, n ) { + let returnCode = false; + // Create list to do the number comparison against + const compareList = this.languageTranslationControllerList( client.editControllerListNumbers, 2 ); + + if( n === altControllerMapping.alt_controller_any_placeholder ) { + // The any button is disabled if any, no, or stop is in the current internal controller list + if( compareList.includes(altControllerMapping.alt_controller_any_placeholder) || + compareList.includes(altControllerMapping.alt_controller_no_placeholder) || + compareList.includes(altControllerMapping.alt_controller_stop_looking) ) { + returnCode = true; + } else { + returnCode = false; + } + } else if( n === altControllerMapping.alt_controller_no_placeholder ) { + // The no button is disabled if no or stop is in the current internal controller list + if( compareList.includes(altControllerMapping.alt_controller_no_placeholder) || + compareList.includes(altControllerMapping.alt_controller_stop_looking) ) { + returnCode = true; + } else { + returnCode = false; + } + } else if( n === altControllerMapping.alt_controller_stop_looking ) { + // The stop button is disabled if no or stop is in the current internal controller list + if( compareList.includes(altControllerMapping.alt_controller_no_placeholder) || + compareList.includes(altControllerMapping.alt_controller_stop_looking) ) { + returnCode = true; + } else { + returnCode = false; + } + } else if( n === 0 ) { + // Nothing disables the clear button or the user text of the list of controllers + returnCode = false; + } else { + // A direct controller number was passed in for the final check + // Disable the number if the number is already there or the any, no, or stop special numbers are already present + if( compareList.includes( n ) || + compareList.includes( altControllerMapping.alt_controller_any_placeholder ) || + compareList.includes( altControllerMapping.alt_controller_no_placeholder ) || + compareList.includes( altControllerMapping.alt_controller_stop_looking ) ) { + returnCode = true; + } else { + returnCode = false; + } + } + return returnCode; + }, + checkControllerSharedDisable( client, n ) { + let returnCode = false; + // Create list to do the shared comparison against + const compareList = client.editControllerListShared; + if( n === 0 ) { + // Nothing disables the clear button or the user text of the list of controllers + returnCode = false; + } else { + // Disable the number if there are over 15 elements + if( compareList.length > 15 ) { + returnCode = true; + } else { + returnCode = false; + } + } + return returnCode; + }, + checkControllerSwapJoysticksDisable( client, n ) { + let returnCode = false; + // Create list to do the shared comparison against + const compareList = client.editControllerListSwapJoysticks; + if( n === -1 ) { + // Nothing disables the clear button or the user text of the list of controllers + returnCode = false; + } else { + // Disable the number if there are over 15 elements + if( compareList.length > 15 ) { + returnCode = true; + } else { + returnCode = false; + } + } + return returnCode; + }, + checkControllerJitterJoysticksDisable( client, n ) { + let returnCode = false; + // Create list to do the shared comparison against + const compareList = client.editControllerListJitterJoysticks; + if( n === -1 ) { + // Nothing disables the clear button or the user text of the list of controllers + returnCode = false; + } else { + // Disable the number if there are over 15 elements + if( compareList.length > 15 ) { + returnCode = true; + } else { + returnCode = false; + } + } + return returnCode; + }, + returnControllerList( rawValue, mode ) { + // Convert what was received from the refreshClient() response into a list + try { + rawValue = JSON.parse( rawValue ); + } catch( e ) { + rawValue = []; + } + // Convert the special number elements in the list into strings + if( mode === 0 ) { + rawValue = this.languageTranslationControllerList( rawValue, 1 ); + } + // Convert shared elements in the list into strings + if( mode === 1 ) { + rawValue = this.languageTranslationControllerList( rawValue, 3 ); + } + // Convert swap joystick elements in the list into strings + if( mode === 2 ) { + rawValue = this.languageTranslationControllerList( rawValue, 5 ); + } + // Any other mode means no conversion + return rawValue; + }, editClient(client) { if (currentEditingClient) { this.cancelEdit(currentEditingClient); @@ -535,6 +917,10 @@

{{ $t('pin.device_management') }}

client.editDisplayMode = client.display_mode; client.edit_do = JSON.parse(JSON.stringify(client.do || [])); client.edit_undo = JSON.parse(JSON.stringify(client.undo || [])); + client.editControllerListNumbers = this.returnControllerList( client.controller_list_numbers, 0 ); + client.editControllerListShared = this.returnControllerList( client.controller_list_shared, 1 ); + client.editControllerListJitterJoysticks = this.returnControllerList( client.controller_list_jitter_joysticks, -1 ); + client.editControllerListSwapJoysticks = this.returnControllerList( client.controller_list_swap_joysticks, 2 ); currentEditingClient = client; console.log(client.do, client.undo) @@ -548,6 +934,10 @@

{{ $t('pin.device_management') }}

client.editAllowClientCommands = client.allow_client_commands; client.editEnableLegacyOrdering = client.enable_legacy_ordering; client.editAlwaysUseVirtualDisplay = client.always_use_virtual_display; + client.editControllerListNumbers = this.returnControllerList( client.controller_list_numbers, 0 ); + client.editControllerListShared = this.returnControllerList( client.controller_list_shared, 1 ); + client.editControllerListJitterJoysticks = this.returnControllerList( client.controller_list_jitter_joysticks, -1 ); + client.editControllerListSwapJoysticks = this.returnControllerList( client.controller_list_swap_joysticks, 2 ); }, saveClient(client) { client.editing = false; @@ -579,7 +969,11 @@

{{ $t('pin.device_management') }}

}) } return filtered - }, []) + }, []), + controller_list_numbers: JSON.stringify(this.languageTranslationControllerList( client.editControllerListNumbers, 2 )), + controller_list_shared: JSON.stringify(this.languageTranslationControllerList( client.editControllerListShared,4 )), + controller_list_jitter_joysticks: JSON.stringify(client.editControllerListJitterJoysticks), + controller_list_swap_joysticks: JSON.stringify(this.languageTranslationControllerList(client.editControllerListSwapJoysticks,6)), } fetch("./api/clients/update", { credentials: 'include', @@ -637,6 +1031,15 @@

{{ $t('pin.device_management') }}

event.target.reportValidity(); }, + validateJitterJoysticks(event) { + const value = parseInt( event.target.value.trim() ); + if (value < 0 || value > 32000 ) { + event.target.setCustomValidity(this.i18n.t('pin.pin.alt_controller_jitter_joysticks_error')); + } else { + event.target.setCustomValidity(''); + } + event.target.reportValidity(); + }, disconnectClient(uuid) { fetch("./api/clients/disconnect", { credentials: 'include', @@ -689,6 +1092,10 @@

{{ $t('pin.device_management') }}

.then((response) => { if (response.status && response.named_certs && response.named_certs.length) { this.platform = response.platform + // Get the alternate controller count and enable for the web page information - These parameters are not part of the state file, but the .conf file + this.altControllerCount = response.alt_controller_count; + this.enableAltControllerNumberingMode = response.enable_alt_controller_numbering_mode; + this.altControllerMode = response.alt_controller_mode; this.clients = response.named_certs.map(({ name, uuid, @@ -699,7 +1106,11 @@

{{ $t('pin.device_management') }}

undo, allow_client_commands, always_use_virtual_display, - enable_legacy_ordering + enable_legacy_ordering, + controller_list_numbers, + controller_list_shared, + controller_list_jitter_joysticks, + controller_list_swap_joysticks }) => { const permInt = parseInt(perm, 10); return { @@ -713,7 +1124,11 @@

{{ $t('pin.device_management') }}

undo, allow_client_commands, enable_legacy_ordering, - always_use_virtual_display + always_use_virtual_display, + controller_list_numbers, + controller_list_shared, + controller_list_jitter_joysticks, + controller_list_swap_joysticks } }) currentEditingClient = null; diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 24e4d7a03..86e151ded 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -143,6 +143,13 @@ "address_family_both": "IPv4+IPv6", "address_family_desc": "Set the address family used by Apollo", "address_family_ipv4": "IPv4 only", + "alt_controller_count": "Number of Controllers to preallocate", + "alt_controller_count_desc": "Number of Controllers to preallocate\nBy default, this value will be 4.\nFor some single player games, a single controller may only be allowed so specifying a 1 here and using the shared mode may be most appropriate.\n\nAfter this value is changed, Apollo needs to be restarted.\n\nFor 2 or more controllers, it has been observed that clearing out the existing controllers in device manager can help maintain the numbering. This can be done by:\n1. Go to the Applications tab.\n2. Click on the blue edit icon for Desktop (or whatever application one typically uses.)\n3. Under Command Preparations, add a command.\n3b. In the \"Do command\" section, add\ncmd /C \"C:\\Program Files\\Apollo\\scripts\\uninstall-existing-controllers-for-alt-controller-enumerating.bat\"\n(Or use the path where Apollo is installed.)\n3c. Make sure that Elevated is checked.\n4. (optional) Edit via notpad the uninstall-existing-controllers-for-alt-controller-enumerating.bat file and look at the script for more details since it does remove devices from device manager.\n5. (Optional) In the Output field, specify the complete path and file name for the log output if one wants to see what the script is doing.\n6. Click on the Save button.\n7. When starting the session via Apollo client, select DESKTOP so that the script is run.\n\n\nAlternatively, one can start a command prompt as Administrator and run the C:\\Program Files\\Apollo\\scripts\\uninstall-existing-controllers-for-alt-controller-enumerating.bat script manually before Apollo is started.", + "alt_controller_mode": "Alternate Controller Mode", + "alt_controller_mode_desc": "Strict only - 1:1 controller relationship maintained between client controllers and the host alternative controllers.\nShared Only - Multiple client controllers can be assigned to the host alternative controllers\nBoth - Strict and Shared modes are both used. The system starts in Strict mode. Use SHIIFT+CONTROL+ALT+I to switch between the 2 modes.\n\nA use case for strict mode is when a game/program associates a specific controller number for a particular purpose or player (like the first player). Sometimes a game will assign the highest controller as the master for the game.\nA use case for the shared mode is when there is a single player game in which several users want to share the same controller on the host, and tell each other over who should do the currently level / game piece.\nA use case for the both mode would be to be able to swap controllers mid-game with a keypress. PLEASE NOTE: The specific controller numbers under these modes are assigned under each client device which is in the PIN webpage. These settings need to be changes as the defaults are most likely not what one wants.\nControllers are only assigned when the controller is connected to the client or the client is connected to the host. In the case of the both mode, the controllers are also assigned when the keypress to switch between the modes is done.\n\nAfter this value is changed, Apollo needs to be restarted.", + "alt_controller_mode_strict": "Strict Alternate Controller Mode", + "alt_controller_mode_shared": "Shared Alternate Controller Mode", + "alt_controller_mode_bothcontrollermodes": "Both Strict and Shared Controller Modes", "always_send_scancodes": "Always Send Scancodes", "always_send_scancodes_desc": "Sending scancodes enhances compatibility with games and apps but may result in incorrect keyboard input from certain clients that aren't using a US English keyboard layout. Enable if keyboard input is not working at all in certain applications. Disable if keys on the client are generating the wrong input on the host.", "amd_coder": "AMF Coder (H264)", @@ -255,6 +262,8 @@ "enable_discovery_desc": "When disabled, you'll need to manually enter host IP on the client to pair.", "enable_input_only_mode": "Enable Input Only Mode", "enable_input_only_mode_desc": "Add an Input Only app entry. When enabled, the app list will only show the current running app and the Input Only entry when streaming. The Input Only entry will not receive any image or audio. Useful for operating the desktop on TV or connecting peripherals which the TV doesn't support with a phone.", + "enable_alt_controller_numbering_mode": "Enable alternative controller numbering mode", + "enable_alt_controller_numbering_mode_desc": "Enable alternative controller mode which preallocates all of the controllers when Apollo is started up. This enables the advantage of keeping those controllers allocated on the host even when clients disconnect. Many programes/games do not like it when controllers disappear. Secondly, this also enables the ability to assign and share controllers on the host to specific clients in both a shared and strict fashion.\n\nAfter this is changed, Apollo needs to be restarted.", "enable_pairing": "Enable Pairing", "enable_pairing_desc": "Enable pairing for the Moonlight client. This allows the client to authenticate with the host and establish a secure connection.", "encoder": "Force a Specific Encoder", @@ -511,6 +520,29 @@ "pin": { "allow_client_commands": "Allow client commands", "allow_client_commands_desc": "Allow client commands to be executed when connecting to this device.", + "alt_controller_order_list": "Alternate Controller Strict Mode List (Use Buttons to Change List)", + "alt_controller_order_list_desc": "This only has an effect in strict mode.
Use the above buttons to specific the pre-allocated number order of the controllers. If the list is empty, the selection of \"any pre-allocated\" is used.
Numbered button: Try to assign that controller to the named device number(if available). If it is not available, the next number will be tried.
CLEAR button: Restart the numbered controller list (There is no back button.)
Any Pre-allocated button: Assign a \"new\" controller to any of the available pre-allocated controllers
Non Pre-allocated( allocate dynamically ) button: Dynamically allocate a controller (no longer under the alt controller mechanisms
Stop Assigning button: Stop looking for controllers to assign (Useful to override the default of any pre-allocated)

Changes will effect future connections.
In summary, there is a 1:1 correspondence with client controllers and the host pre-allocated controllers", + "alt_controller_order_list_any_preallocated_button": "Any Pre-allocated", + "alt_controller_order_list_clear_button": "Clear", + "alt_controller_order_list_new_non_preallocated_button": "Non Pre-allocated( allocate dynamically )", + "alt_controller_order_list_stop_looking_button": "Stop assigning", + "alt_controller_shared_list": "Alternate Controller Shared Mode List (Use Buttons to Change List)", + "alt_controller_shared_list_desc": "This only has an effect in shared mode.
Use the below buttons to specific the controller on the host to share. In most cases, only one number needs to specified and all controllers coming from the client will get that same host controller.
\"No change\" means that the controller assigned during the strict mode will still be used. If the strict mode is not available, then no controller will be assigned.
\"No controller\" means that no controller will be assigned in the shared mode. The remote controller will not be able to do anything.
Different clients can also specify the same number. All controllers should get the number selected as long as that controller number was preallocated.

If the list is empty, the selection of \"no change\" is used, meaning that the strict allocation will not be changed. If there is no strict allocation, then no controller will be mapped from the client to the host.
This parameter is a list so that each controller on the guest system can share a different controller on the host. Secondly, the list is wrapped around based on the local client controller number.
If 2 controllers are added at the client and the list only contains the number 1, then both controllers will be sharing controller 1 on the host.
A use case for this would be to have several clients share a single controller on the host, in which case, the number 1 should be specified.", + "alt_controller_shared_list_clear_button": "Clear", + "alt_controller_shared_list_no_change_controller_button": "No Change", + "alt_controller_shared_list_remove_controller_button": "Remove Controller", + "alt_controller_jitter_joysticks_text_entry": "Alternate Controller Jitter Joystick Correction Text Entry For List (BE SURE TO ADD THIS NUMBER TO THE LIST VIA THE BUTTON)", + "alt_controller_jitter_joysticks_desc": "This applies to both the strict and shared mode, but is typically more useful in the shared mode.
In shared mode, it is the last controller which had a change in state that is \"in control\" of the host controller. If there is a jittery joystick, that jitter can sieze control of the host controller which is typically undesired behavior.
Apollo will drop joystick messages from reaching the host controller that are below the number threshold specified.
Again, this tends to be important for SHARED mode since any joystick with such jitter can take control. Like the shared controller list, this is also a list if there are multiple gamepads attached to the same client so that each one can have their own jitter correction.

For a use case, a typical value to start out at is 1000 or 2000 which can usually eliminate most problems for the shared mode. If the jitter is corrected at the OS level in some fashion on each and every controller, then this list is not needed.", + "alt_controller_jitter_joysticks_error": "Invalid Joystick Jitter Number. Must be between 0-32000", + "alt_controller_jitter_joysticks_list": "Alternate Controller Jitter Joystick List (Use Above Button to Add to List)", + "alt_controller_jitter_joysticks_list_desc": "This list applies to the controller(s) connected to the client. The list will wrap as appropriate if there are not enough entries in the list. ", + "alt_controller_jitter_joysticks_list_clear_button": "Clear", + "alt_controller_jitter_joysticks_list_add_button": "Add", + "alt_controller_swap_joysticks_list": "Alternate Controller Swap Joysticks List (Use Buttons to Change List)", + "alt_controller_swap_joysticks_list_desc": "This has an effect in strict and shared mode.
This list applied to the controller(s) connected to the client. Use the below buttons to specific whether the joysticks are swapped. Default is disabled or 0. Typically a controller has both an upper left joystick and a lower right joystick. Again, like the client controller based lists, if there are more than one controller connected to the client, different values can be specified. In most cases, if there is only controller connected, only a single number needs to be specified.", + "alt_controller_swap_joysticks_list_clear_button": "Clear", + "alt_controller_swap_joysticks_list_enable_button": "Enable Swap Joysticks", + "alt_controller_swap_joysticks_list_disable_button": "Disable Swap Joysticks", "always_use_virtual_display": "Always create virtual display", "always_use_virtual_display_desc": "Always create a virtual display when connecting from this device.", "client_do_cmd": "Client connect commands", diff --git a/src_assets/windows/misc/gamepad/removeGhosts.ps1 b/src_assets/windows/misc/gamepad/removeGhosts.ps1 new file mode 100644 index 000000000..463a07793 --- /dev/null +++ b/src_assets/windows/misc/gamepad/removeGhosts.ps1 @@ -0,0 +1,509 @@ +# Originally from - https://github.com/istvans/scripts/blob/master/removeGhosts.ps1 + +<# +.SYNOPSIS + Removes ghost devices from your system + +.DESCRIPTION + This script will remove ghost devices from your system. These are devices that are present but have an "InstallState" as false. These devices are typically shown as 'faded' + in Device Manager, when you select "Show hidden and devices" from the view menu. This script has been tested on Windows 2008 R2 SP2 with PowerShell 3.0, 5.1, Server 2012R2 + with Powershell 4.0 and Windows 10 Pro with Powershell 5.1. There is no warranty with this script. Please use cautiously as removing devices is a destructive process without + an undo. + +.PARAMETER filterByFriendlyName +This parameter will exclude devices that match the partial name provided. This paramater needs to be specified in an array format for all the friendly names you want to be excluded. +"Intel" will match "Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz". "Loop" will match "Microsoft Loopback Adapter". + +.PARAMETER narrowByFriendlyName +This parameter will include devices that match the partial name provided. This paramater needs to be specified in an array format for all the friendly names you want to be included. +"Intel" will match "Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz". "Loop" will match "Microsoft Loopback Adapter". + +.PARAMETER filterByClass +This parameter will exclude devices that match the class name provided. This paramater needs to be specified in an array format for all the class names you want to be excluded. +This is an exact string match so "Disk" will not match "DiskDrive". + +.PARAMETER narrowByClass +This parameter will include devices that match the class name provided. This paramater needs to be specified in an array format for all the class names you want to be included. +This is an exact string match so "Disk" will not match "DiskDrive". + +.PARAMETER listDevicesOnly +listDevicesOnly will output a table of all devices found in this system. + +.PARAMETER listGhostDevicesOnly +listGhostDevicesOnly will output a table of all 'ghost' devices found in this system. + +.PARAMETER force +If specified, each matching device will be removed WITHOUT any confirmation! + +.PARAMETER regardlessOfInstallState +If specified, each matching device will be removed regardless of InstallState! + +.EXAMPLE +Lists all devices +. "removeGhosts.ps1" -listDevicesOnly + +.EXAMPLE +Save the list of devices as an object +$Devices = . "removeGhosts.ps1" -listDevicesOnly + +.EXAMPLE +Lists all 'ghost' devices +. "removeGhosts.ps1" -listGhostDevicesOnly + +.EXAMPLE +Lists all 'ghost' devices with a class of "Net" +. "removeGhosts.ps1" -listGhostDevicesOnly -narrowByClass Net + +.EXAMPLE +Lists all 'ghost' devices with a class of "Net" AND a friendly name matching "Realtek" +. "removeGhosts.ps1" -listGhostDevicesOnly -narrowbyfriendlyname Realtek -narrowbyclass Net + +.EXAMPLE +Save the list of 'ghost' devices as an object +$ghostDevices = . "removeGhosts.ps1" -listGhostDevicesOnly + +.EXAMPLE +Remove all ghost devices EXCEPT any devices that have "Intel" or "Citrix" in their friendly name +. "removeGhosts.ps1" -filterByFriendlyName @("Intel","Citrix") + +.EXAMPLE +Remove all ghost devices that have "Intel" in their friendly name +. "removeGhosts.ps1" -narrowByFriendlyName Intel + +.EXAMPLE +Remove all ghost devices EXCEPT any devices that are apart of the classes "LegacyDriver" or "Processor" +. "removeGhosts.ps1" -filterByClass @("LegacyDriver","Processor") + +.EXAMPLE +Remove all ghost devices EXCEPT for devices with a friendly name of "Intel" or "Citrix" or with a class of "LegacyDriver" or "Processor" +. "removeGhosts.ps1" -filterByClass @("LegacyDriver","Processor") -filterByFriendlyName @("Intel","Citrix") + +.EXAMPLE +Remove all ghost network devices i.e. the ones with a class of "Net" +. "removeGhosts.ps1" -narrowByClass Net + +.EXAMPLE +Remove all ghost devices without confirmation +. "removeGhosts.ps1" -Force + +.NOTES +Permission level has not been tested. It is assumed you will need to have sufficient rights to uninstall devices from device manager for this script to run properly. +#> + +Param( + [array]$FilterByClass, + [array]$NarrowByClass, + [array]$FilterByFriendlyName, + [array]$NarrowByFriendlyName, + [switch]$listDevicesOnly, + [switch]$listGhostDevicesOnly, + [switch]$Force, + [switch]$regardlessOfInstallState +) + +#parameter futzing +$removeDevices = $true +if ($FilterByClass -ne $null) { + write-host "FilterByClass: $FilterByClass" +} + +if ($NarrowByClass -ne $null) { + write-host "NarrowByClass: $NarrowByClass" +} + +if ($FilterByFriendlyName -ne $null) { + write-host "FilterByFriendlyName: $FilterByFriendlyName" +} + +if ($NarrowByFriendlyName -ne $null) { + write-host "NarrowByFriendlyName: $NarrowByFriendlyName" +} + +if ($listDevicesOnly -eq $true) { + write-host "List devices without removal: $listDevicesOnly" + $removeDevices = $false +} + +if ($listGhostDevicesOnly -eq $true) { + write-host "List ghost devices without removal: $listGhostDevicesOnly" + $removeDevices = $false +} + +if ($Force -eq $true) { + write-host "Each removal will happen without any confirmation: $Force" +} + +if ($regardlessOfInstallState -eq $true) { + write-host "Each removal will happen regardless of its InstallState: $regardlessOfInstallState" +} + +function Filter-Device { + Param ( + [System.Object]$dev + ) + $Class = $dev.Class + $FriendlyName = $dev.FriendlyName + $matchFilter = $false + + if (($matchFilter -eq $false) -and ($FilterByClass -ne $null)) { + foreach ($ClassFilter in $FilterByClass) { + if ($ClassFilter -eq $Class) { + Write-verbose "Class filter match $ClassFilter, skipping" + $matchFilter = $true + break + } + } + } + if (($matchFilter -eq $false) -and ($NarrowByClass -ne $null)) { + $shouldInclude = $false + foreach ($ClassFilter in $NarrowByClass) { + if ($ClassFilter -eq $Class) { + $shouldInclude = $true + break + } + } + $matchFilter = !$shouldInclude + } + if (($matchFilter -eq $false) -and ($FilterByFriendlyName -ne $null)) { + foreach ($FriendlyNameFilter in $FilterByFriendlyName) { + if ($FriendlyName -like '*'+$FriendlyNameFilter+'*') { + Write-verbose "FriendlyName filter match $FriendlyName, skipping" + $matchFilter = $true + break + } + } + } + if (($matchFilter -eq $false) -and ($NarrowByFriendlyName -ne $null)) { + $shouldInclude = $false + foreach ($FriendlyNameFilter in $NarrowByFriendlyName) { + if ($FriendlyName -like '*'+$FriendlyNameFilter+'*') { + $shouldInclude = $true + break + } + } + $matchFilter = !$shouldInclude + } + return $matchFilter +} + +function Filter-Devices { + Param ( + [array]$devices + ) + $filteredDevices = @() + foreach ($dev in $devices) { + $matchFilter = Filter-Device -Dev $dev + if ($matchFilter -eq $false) { + $filteredDevices += @($dev) + } + } + return $filteredDevices +} +function Get-Ghost-Devices { + Param ( + [array]$devices + ) + return ($devices | where {$_.InstallState -eq $false} | sort -Property FriendlyName) +} + +# NOTE: White spaces are important in $setupapi for some reason! +$setupapi = @" +using System; +using System.Diagnostics; +using System.Text; +using System.Runtime.InteropServices; +namespace Win32 +{ + public static class SetupApi + { + // 1st form using a ClassGUID only, with Enumerator = IntPtr.Zero + [DllImport("setupapi.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SetupDiGetClassDevs( + ref Guid ClassGuid, + IntPtr Enumerator, + IntPtr hwndParent, + int Flags + ); + + // 2nd form uses an Enumerator only, with ClassGUID = IntPtr.Zero + [DllImport("setupapi.dll", CharSet = CharSet.Auto)] + public static extern IntPtr SetupDiGetClassDevs( + IntPtr ClassGuid, + string Enumerator, + IntPtr hwndParent, + int Flags + ); + + [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool SetupDiEnumDeviceInfo( + IntPtr DeviceInfoSet, + uint MemberIndex, + ref SP_DEVINFO_DATA DeviceInfoData + ); + + [DllImport("setupapi.dll", SetLastError = true)] + public static extern bool SetupDiDestroyDeviceInfoList( + IntPtr DeviceInfoSet + ); + [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool SetupDiGetDeviceRegistryProperty( + IntPtr deviceInfoSet, + ref SP_DEVINFO_DATA deviceInfoData, + uint property, + out UInt32 propertyRegDataType, + byte[] propertyBuffer, + uint propertyBufferSize, + out UInt32 requiredSize + ); + [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SetupDiGetDeviceInstanceId( + IntPtr DeviceInfoSet, + ref SP_DEVINFO_DATA DeviceInfoData, + StringBuilder DeviceInstanceId, + int DeviceInstanceIdSize, + out int RequiredSize + ); + + + [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool SetupDiRemoveDevice(IntPtr DeviceInfoSet,ref SP_DEVINFO_DATA DeviceInfoData); + } + [StructLayout(LayoutKind.Sequential)] + public struct SP_DEVINFO_DATA + { + public uint cbSize; + public Guid classGuid; + public uint devInst; + public IntPtr reserved; + } + [Flags] + public enum DiGetClassFlags : uint + { + DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE + DIGCF_PRESENT = 0x00000002, + DIGCF_ALLCLASSES = 0x00000004, + DIGCF_PROFILE = 0x00000008, + DIGCF_DEVICEINTERFACE = 0x00000010, + } + public enum SetupDiGetDeviceRegistryPropertyEnum : uint + { + SPDRP_DEVICEDESC = 0x00000000, // DeviceDesc (R/W) + SPDRP_HARDWAREID = 0x00000001, // HardwareID (R/W) + SPDRP_COMPATIBLEIDS = 0x00000002, // CompatibleIDs (R/W) + SPDRP_UNUSED0 = 0x00000003, // unused + SPDRP_SERVICE = 0x00000004, // Service (R/W) + SPDRP_UNUSED1 = 0x00000005, // unused + SPDRP_UNUSED2 = 0x00000006, // unused + SPDRP_CLASS = 0x00000007, // Class (R--tied to ClassGUID) + SPDRP_CLASSGUID = 0x00000008, // ClassGUID (R/W) + SPDRP_DRIVER = 0x00000009, // Driver (R/W) + SPDRP_CONFIGFLAGS = 0x0000000A, // ConfigFlags (R/W) + SPDRP_MFG = 0x0000000B, // Mfg (R/W) + SPDRP_FRIENDLYNAME = 0x0000000C, // FriendlyName (R/W) + SPDRP_LOCATION_INFORMATION = 0x0000000D, // LocationInformation (R/W) + SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E, // PhysicalDeviceObjectName (R) + SPDRP_CAPABILITIES = 0x0000000F, // Capabilities (R) + SPDRP_UI_NUMBER = 0x00000010, // UiNumber (R) + SPDRP_UPPERFILTERS = 0x00000011, // UpperFilters (R/W) + SPDRP_LOWERFILTERS = 0x00000012, // LowerFilters (R/W) + SPDRP_BUSTYPEGUID = 0x00000013, // BusTypeGUID (R) + SPDRP_LEGACYBUSTYPE = 0x00000014, // LegacyBusType (R) + SPDRP_BUSNUMBER = 0x00000015, // BusNumber (R) + SPDRP_ENUMERATOR_NAME = 0x00000016, // Enumerator Name (R) + SPDRP_SECURITY = 0x00000017, // Security (R/W, binary form) + SPDRP_SECURITY_SDS = 0x00000018, // Security (W, SDS form) + SPDRP_DEVTYPE = 0x00000019, // Device Type (R/W) + SPDRP_EXCLUSIVE = 0x0000001A, // Device is exclusive-access (R/W) + SPDRP_CHARACTERISTICS = 0x0000001B, // Device Characteristics (R/W) + SPDRP_ADDRESS = 0x0000001C, // Device Address (R) + SPDRP_UI_NUMBER_DESC_FORMAT = 0X0000001D, // UiNumberDescFormat (R/W) + SPDRP_DEVICE_POWER_DATA = 0x0000001E, // Device Power Data (R) + SPDRP_REMOVAL_POLICY = 0x0000001F, // Removal Policy (R) + SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000020, // Hardware Removal Policy (R) + SPDRP_REMOVAL_POLICY_OVERRIDE = 0x00000021, // Removal Policy Override (RW) + SPDRP_INSTALL_STATE = 0x00000022, // Device Install State (R) + SPDRP_LOCATION_PATHS = 0x00000023, // Device Location Paths (R) + SPDRP_BASE_CONTAINERID = 0x00000024 // Base ContainerID (R) + } +} +"@ +Add-Type -TypeDefinition $setupapi + + #Array for all removed devices report + $removeArray = @() + #Array for all devices report + $array = @() + + $setupClass = [Guid]::Empty + #Get all devices + $devs = [Win32.SetupApi]::SetupDiGetClassDevs([ref]$setupClass, [IntPtr]::Zero, [IntPtr]::Zero, [Win32.DiGetClassFlags]::DIGCF_ALLCLASSES) + + #Initialise Struct to hold device info Data + $devInfo = new-object Win32.SP_DEVINFO_DATA + $devInfo.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($devInfo) + + #Device Counter + $devCount = 0 + #Enumerate Devices + while([Win32.SetupApi]::SetupDiEnumDeviceInfo($devs, $devCount, [ref]$devInfo)) { + + #Will contain an enum depending on the type of the registry Property, not used but required for call + $propType = 0 + #Buffer is initially null and buffer size 0 so that we can get the required Buffer size first + [byte[]]$propBuffer = $null + $propBufferSize = 0 + #Get Buffer size + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_FRIENDLYNAME, [ref]$propType, $propBuffer, 0, [ref]$propBufferSize) | Out-null + #Initialize Buffer with right size + [byte[]]$propBuffer = New-Object byte[] $propBufferSize + + #Get HardwareID + $propTypeHWID = 0 + [byte[]]$propBufferHWID = $null + $propBufferSizeHWID = 0 + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_HARDWAREID, [ref]$propTypeHWID, $propBufferHWID, 0, [ref]$propBufferSizeHWID) | Out-null + [byte[]]$propBufferHWID = New-Object byte[] $propBufferSizeHWID + + #Get DeviceDesc (this name will be used if no friendly name is found) + $propTypeDD = 0 + [byte[]]$propBufferDD = $null + $propBufferSizeDD = 0 + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_DEVICEDESC, [ref]$propTypeDD, $propBufferDD, 0, [ref]$propBufferSizeDD) | Out-null + [byte[]]$propBufferDD = New-Object byte[] $propBufferSizeDD + + #Get Install State + $propTypeIS = 0 + [byte[]]$propBufferIS = $null + $propBufferSizeIS = 0 + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_INSTALL_STATE, [ref]$propTypeIS, $propBufferIS, 0, [ref]$propBufferSizeIS) | Out-null + [byte[]]$propBufferIS = New-Object byte[] $propBufferSizeIS + + #Get Class + $propTypeCLSS = 0 + [byte[]]$propBufferCLSS = $null + $propBufferSizeCLSS = 0 + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo, [Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_CLASS, [ref]$propTypeCLSS, $propBufferCLSS, 0, [ref]$propBufferSizeCLSS) | Out-null + [byte[]]$propBufferCLSS = New-Object byte[] $propBufferSizeCLSS + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_CLASS, [ref]$propTypeCLSS, $propBufferCLSS, $propBufferSizeCLSS, [ref]$propBufferSizeCLSS) | out-null + $Class = [System.Text.Encoding]::Unicode.GetString($propBufferCLSS) + + #Read FriendlyName property into Buffer + if(![Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_FRIENDLYNAME, [ref]$propType, $propBuffer, $propBufferSize, [ref]$propBufferSize)){ + [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_DEVICEDESC, [ref]$propTypeDD, $propBufferDD, $propBufferSizeDD, [ref]$propBufferSizeDD) | out-null + $FriendlyName = [System.Text.Encoding]::Unicode.GetString($propBufferDD) + #The friendly Name ends with a weird character + if ($FriendlyName.Length -ge 1) { + $FriendlyName = $FriendlyName.Substring(0,$FriendlyName.Length-1) + } + } else { + #Get Unicode String from Buffer + $FriendlyName = [System.Text.Encoding]::Unicode.GetString($propBuffer) + #The friendly Name ends with a weird character + if ($FriendlyName.Length -ge 1) { + $FriendlyName = $FriendlyName.Substring(0,$FriendlyName.Length-1) + } + } + + #InstallState returns true or false as an output, not text + $InstallState = [Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_INSTALL_STATE, [ref]$propTypeIS, $propBufferIS, $propBufferSizeIS, [ref]$propBufferSizeIS) + + # Read HWID property into Buffer + if(![Win32.SetupApi]::SetupDiGetDeviceRegistryProperty($devs, [ref]$devInfo,[Win32.SetupDiGetDeviceRegistryPropertyEnum]::SPDRP_HARDWAREID, [ref]$propTypeHWID, $propBufferHWID, $propBufferSizeHWID, [ref]$propBufferSizeHWID)){ + #Ignore if Error + $HWID = "" + } else { + #Get Unicode String from Buffer + $HWID = [System.Text.Encoding]::Unicode.GetString($propBufferHWID) + #trim out excess names and take first object + $HWID = $HWID.split([char]0x0000)[0].ToUpper() + } + + #all detected devices list + $device = New-Object System.Object + $device | Add-Member -type NoteProperty -name FriendlyName -value $FriendlyName + $device | Add-Member -type NoteProperty -name HWID -value $HWID + $device | Add-Member -type NoteProperty -name InstallState -value $InstallState + $device | Add-Member -type NoteProperty -name Class -value $Class + if ($array.count -le 0) { + #for some reason the script will blow by the first few entries without displaying the output + #this brief pause seems to let the objects get created/displayed so that they are in order. + sleep 1 + } + $array += @($device) + + <# + We need to execute the filtering at this point because we are in the current device context + where we can execute an action (eg, removal). + InstallState : False == ghosted device + #> + if ($removeDevices -eq $true) { + #we want to remove devices so let's check the filters... + $matchFilter = Filter-Device -Dev $device + + if ($InstallState -eq $False -or $regardlessOfInstallState -eq $true) { + if ($matchFilter -eq $false) { + $message = "Attempting to remove device $FriendlyName" + $confirmed = $false + if (!$Force -eq $true) { + $question = 'Are you sure you want to proceed?' + $choices = '&Yes', '&No' + $decision = $Host.UI.PromptForChoice($message, $question, $choices, 1) + if ($decision -eq 0) { + $confirmed = $true + } + } else { + $confirmed = $true + } + if ($confirmed -eq $true) { + Write-Host $message -ForegroundColor Yellow + $removeObj = New-Object System.Object + $removeObj | Add-Member -type NoteProperty -name FriendlyName -value $FriendlyName + $removeObj | Add-Member -type NoteProperty -name HWID -value $HWID + $removeObj | Add-Member -type NoteProperty -name InstallState -value $InstallState + $removeObj | Add-Member -type NoteProperty -name Class -value $Class + $removeArray += @($removeObj) + if([Win32.SetupApi]::SetupDiRemoveDevice($devs, [ref]$devInfo)){ + Write-Host "Removed device $FriendlyName" -ForegroundColor Green + } else { + Write-Host "Failed to remove device $FriendlyName" -ForegroundColor Red + } + } else { + Write-Host "OK, skipped" -ForegroundColor Yellow + } + } else { + write-host "Filter matched. Skipping $FriendlyName" -ForegroundColor Yellow + } + } + } + $devcount++ + } + + #output objects so you can take the output from the script + if ($listDevicesOnly) { + $allDevices = $array | sort -Property FriendlyName + $filteredDevices = Filter-Devices -Devices $allDevices + $filteredDevices | ft + write-host "Total devices found : $($allDevices.count)" + write-host "Total filtered devices found : $($filteredDevices.count)" + $ghostDevices = Get-Ghost-Devices -Devices $array + $filteredGhostDevices = Filter-Devices -Devices $ghostDevices + write-host "Total ghost devices found : $($ghostDevices.count)" + write-host "Total filtered ghost devices found : $($filteredGhostDevices.count)" + return $filteredDevices | out-null + } + + if ($listGhostDevicesOnly) { + $ghostDevices = Get-Ghost-Devices -Devices $array + $filteredGhostDevices = Filter-Devices -Devices $ghostDevices + $filteredGhostDevices | ft + write-host "Total ghost devices found : $($ghostDevices.count)" + write-host "Total filtered ghost devices found : $($filteredGhostDevices.count)" + return $filteredGhostDevices | out-null + } + + if ($removeDevices -eq $true) { + write-host "Removed devices:" + $removeArray | sort -Property FriendlyName | ft + write-host "Total removed devices : $($removeArray.count)" + return $removeArray | out-null + } \ No newline at end of file diff --git a/src_assets/windows/misc/gamepad/uninstall-existing-controllers-for-alt-controller-enumerating.bat b/src_assets/windows/misc/gamepad/uninstall-existing-controllers-for-alt-controller-enumerating.bat new file mode 100644 index 000000000..8f6aa0a01 --- /dev/null +++ b/src_assets/windows/misc/gamepad/uninstall-existing-controllers-for-alt-controller-enumerating.bat @@ -0,0 +1,31 @@ +@echo off +cls +pushd "%~dp0" 2> null +pushd "\\%~p0" 2> null +echo Purpose: This script will clear out the gamepad controllers so that the alternate controller enumerating can be more effective and be less biased by previous controllers. +echo The will get rid of the unconnected devices for: +echo "Xbox 360 Controller for Windows" +echo "HID-compliant game controller" +echo "USB Input Device" +echo ..... +echo This should can be run once or as part of the commands when an application in Apollo is run. +echo 1. Make a Windows Restore Point if concerned about device removal. (When running for the first time) +echo 2. Be sure to run this as Administrator +echo 3. Run this when Apollo is not running. (Unless part of the sequence before the main Apollo application starts.) +echo 4. Run this when there are no controllers connected. +echo 5. Make sure the number of alternate controllers that you is filled in Apollo. +echo 6. Once clients start joining with controllers, the controller enumeration should be better. This should also get past the 5th+ controller odd numbering situation as long as it is run every time the Apollo application is started. +echo .... +echo Press CTRL-C to stop script +rem pause +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -listGhostDevicesOnly +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -narrowbyfriendlyname "Xbox 360 Controller for Windows" -listGhostDevicesOnly +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -narrowbyfriendlyname "HID-compliant game controller" -listGhostDevicesOnly +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -narrowbyfriendlyname "USB Input Device" -listGhostDevicesOnly +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -narrowbyfriendlyname "Xbox 360 Controller for Windows" -force +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -narrowbyfriendlyname "HID-compliant game controller" -force +powershell.exe -ExecutionPolicy Bypass -File ".\removeGhosts.ps1" -narrowbyfriendlyname "USB Input Device" -force +echo .... +echo Done +rem pause +exit 0 \ No newline at end of file