From dd84ac5b4b697f97833cc23029e640b8fd8b0834 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 20 Jan 2025 18:16:18 +0100 Subject: [PATCH] Example plugin: add button combo API usage --- include/wups/config/WUPSConfigCategory.h | 2 + include/wups/config/WUPSConfigItem.h | 2 + plugins/example_plugin/src/main.c | 225 +++++++++++++++++++++-- plugins/example_plugin_cpp/src/main.cpp | 184 +++++++++++++++++- 4 files changed, 391 insertions(+), 22 deletions(-) diff --git a/include/wups/config/WUPSConfigCategory.h b/include/wups/config/WUPSConfigCategory.h index fbfbd5b..ad5570e 100644 --- a/include/wups/config/WUPSConfigCategory.h +++ b/include/wups/config/WUPSConfigCategory.h @@ -13,6 +13,8 @@ public: WUPSConfigCategory(const WUPSConfigCategory &) = delete; + WUPSConfigCategory &operator=(const WUPSConfigCategory &) = delete; + WUPSConfigCategory(WUPSConfigCategory &&src) noexcept; WUPSConfigCategory &operator=(WUPSConfigCategory &&src) noexcept; diff --git a/include/wups/config/WUPSConfigItem.h b/include/wups/config/WUPSConfigItem.h index 72de61c..ceca87a 100644 --- a/include/wups/config/WUPSConfigItem.h +++ b/include/wups/config/WUPSConfigItem.h @@ -14,6 +14,8 @@ public: WUPSConfigItem(const WUPSConfigItem &) = delete; + WUPSConfigItem &operator=(const WUPSConfigItem &) = delete; + WUPSConfigItem(WUPSConfigItem &&src) noexcept; WUPSConfigItem &operator=(WUPSConfigItem &&src) noexcept; diff --git a/plugins/example_plugin/src/main.c b/plugins/example_plugin/src/main.c index b73c5c4..6ff9ac6 100644 --- a/plugins/example_plugin/src/main.c +++ b/plugins/example_plugin/src/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,13 @@ WUPS_PLUGIN_LICENSE("BSD"); WUPS_USE_WUT_DEVOPTAB(); // Use the wut devoptabs WUPS_USE_STORAGE("example_plugin"); // Unique id for the storage api -bool logFSOpen = true; +bool logFSOpen = true; +static WUPSButtonCombo_ComboHandle sPressDownButtonComboExampleHandle = {}; +static WUPSButtonCombo_ComboHandle sPressDownObserverButtonComboExampleHandle = {}; +static WUPSButtonCombo_ComboHandle sHoldButtonComboExampleHandle = {}; +static WUPSButtonCombo_ComboHandle sHoldObserverExButtonComboExampleHandle = {}; +WUPSButtonCombo_Buttons DEFAULT_PRESS_DOWN_BUTTON_COMBO = WUPS_BUTTON_COMBO_BUTTON_L | WUPS_BUTTON_COMBO_BUTTON_R; +WUPSButtonCombo_Buttons DEFAULT_PRESS_HOLD_COMBO = WUPS_BUTTON_COMBO_BUTTON_L | WUPS_BUTTON_COMBO_BUTTON_R | WUPS_BUTTON_COMBO_BUTTON_DOWN; /** * Callback that will be called if the config has been changed @@ -46,13 +53,15 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro // Let's create a new category called "Settings" WUPSConfigCategoryHandle settingsCategory; WUPSConfigAPICreateCategoryOptionsV1 settingsCategoryOptions = {.name = "Settings"}; - if (WUPSConfigAPI_Category_Create(settingsCategoryOptions, &settingsCategory) != WUPSCONFIG_API_RESULT_SUCCESS) { + if (WUPSConfigAPI_Category_Create(settingsCategoryOptions, &settingsCategory) != + WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to create settings category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } // Add a new item to this settings category - if (WUPSConfigItemBoolean_AddToCategory(settingsCategory, LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", true, logFSOpen, &logFSOpenChanged) != WUPSCONFIG_API_RESULT_SUCCESS) { + if (WUPSConfigItemBoolean_AddToCategory(settingsCategory, LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", true, + logFSOpen, &logFSOpenChanged) != WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to add item to category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } @@ -62,7 +71,6 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro DEBUG_FUNCTION_LINE_ERR("Failed to add category to root item"); } } - { // We can also have categories inside categories! WUPSConfigCategoryHandle categoryLevel1; @@ -77,7 +85,9 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro DEBUG_FUNCTION_LINE_ERR("Failed to create categoryLevel1"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } - if (WUPSConfigItemBoolean_AddToCategory(categoryLevel2, "stubInsideCategory", "This is stub item inside a nested category", false, false, NULL) != WUPSCONFIG_API_RESULT_SUCCESS) { + if (WUPSConfigItemBoolean_AddToCategory(categoryLevel2, "stubInsideCategory", + "This is stub item inside a nested category", false, false, + NULL) != WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to add stub item to root category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } @@ -96,7 +106,8 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro } { // We can also directly add items to the root category - if (WUPSConfigItemStub_AddToCategory(root, "This is stub item without category") != WUPSCONFIG_API_RESULT_SUCCESS) { + if (WUPSConfigItemStub_AddToCategory(root, "This is stub item without category") != + WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to add stub item to root category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } @@ -112,7 +123,8 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro values[i].value = i; values[i].valueName = str; } - WUPSConfigAPIStatus multValuesRes = WUPSConfigItemMultipleValues_AddToCategory(root, "multival", "Multiple values", 0, 0, values, numOfElements, NULL); + WUPSConfigAPIStatus multValuesRes = WUPSConfigItemMultipleValues_AddToCategory( + root, "multival", "Multiple values", 0, 0, values, numOfElements, NULL); for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) { free((void *) values[i].valueName); } @@ -128,6 +140,22 @@ void ConfigMenuClosedCallback() { WUPSStorageAPI_SaveStorage(false); } +void pressDownComboCallback(const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("Button combo has been pressed down by controller %s", WUPSButtonComboAPI_GetControllerTypeStr(triggeredBy)); +} + +void pressDownObserverComboCallback(const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("[OBSERVER] Button combo has been pressed down by controller %s", WUPSButtonComboAPI_GetControllerTypeStr(triggeredBy)); +} + +void holdComboCallback(const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("Button combo has been hold by controller %s", WUPSButtonComboAPI_GetControllerTypeStr(triggeredBy)); +} + +void holdObserverExComboCallback(const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("[OBSERVER] Button combo has been hold by controller %s", WUPSButtonComboAPI_GetControllerTypeStr(triggeredBy)); +} + /** Gets called ONCE when the plugin was loaded. **/ @@ -137,14 +165,15 @@ INITIALIZE_PLUGIN() { DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!"); WUPSConfigAPIOptionsV1 configOptions = {.name = "example_plugin"}; - if (WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != WUPSCONFIG_API_RESULT_SUCCESS) { + if (WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != + WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to init config api"); } WUPSStorageError storageRes; // Try to get value from storage - if ((storageRes = WUPSStorageAPI_GetBool(NULL, LOG_FS_OPEN_CONFIG_ID, &logFSOpen)) == WUPS_STORAGE_ERROR_NOT_FOUND) { - + if ((storageRes = WUPSStorageAPI_GetBool(NULL, LOG_FS_OPEN_CONFIG_ID, &logFSOpen)) == + WUPS_STORAGE_ERROR_NOT_FOUND) { // Add the value to the storage if it's missing. if (WUPSStorageAPI_StoreBool(NULL, LOG_FS_OPEN_CONFIG_ID, logFSOpen) != WUPS_STORAGE_ERROR_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to store bool"); @@ -152,10 +181,157 @@ INITIALIZE_PLUGIN() { } else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to get bool %s (%d)", WUPSConfigAPI_GetStatusStr(storageRes), storageRes); } else { - DEBUG_FUNCTION_LINE_ERR("Successfully read the value from storage: %d %s (%d)", logFSOpen, WUPSConfigAPI_GetStatusStr(storageRes), storageRes); + DEBUG_FUNCTION_LINE_ERR("Successfully read the value from storage: %d %s (%d)", logFSOpen, + WUPSConfigAPI_GetStatusStr(storageRes), storageRes); } WUPSStorageAPI_SaveStorage(false); + // To register a button combo, we can use the C++ wrapper class "WUPSButtonComboAPI::ButtonCombo". + // The combo will be added on construction of that wrapper, and removed again in the destructor. Use `std::move` to move it around. + // Like the C++ config api there are two versions of all function, one that throws an exception on error and one that returns a std::optional but set an additional error parameter. + + { + WUPSButtonCombo_ComboStatus comboStatus = WUPS_BUTTON_COMBO_COMBO_STATUS_INVALID_STATUS; + // Create a button combo which detects if a combo has been pressed down on any controller. + // This version will check for conflicts. It's useful to check for conflicts if you want to use that button combo for a global unique thing + // that's always possible, like taking screenshots. + const WUPSButtonCombo_Error err = WUPSButtonComboAPI_AddButtonComboPressDown( + "Example Plugin: Press Down test", + DEFAULT_PRESS_DOWN_BUTTON_COMBO, // L + R + pressDownComboCallback, + NULL, + &sPressDownButtonComboExampleHandle, // We will use the handle in the config menu + &comboStatus); + if (err == WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + // On success, we can check if the combo is actually active by checking the combo status. + // If there is already another combo that conflicts with us, the status will be set to WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT + switch (comboStatus) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + DEBUG_FUNCTION_LINE_INFO("Button combo is valid and active"); + break; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + DEBUG_FUNCTION_LINE_INFO("Conflict detected for button combo"); + break; + default: + DEBUG_FUNCTION_LINE_ERR("Invalid combo status"); + break; + } + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to add press down button combo"); + } + // To remove that button combo, we explicitly have to call "WUPSButtonComboAPI_RemoveButtonCombo", we'll do it in DEINITIALIZE_PLUGIN + } + { + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // But we can also create button combos without caring about conflicts. + // E.g. when a new Aroma update is detected, the updater can be launched by holding the PLUS button. This should always be possible. + // If we don't want to check for conflicts, we need to create a "PressDownObserver" + WUPSButtonCombo_ComboStatus comboStatus = WUPS_BUTTON_COMBO_COMBO_STATUS_INVALID_STATUS; + const WUPSButtonCombo_Error err = WUPSButtonComboAPI_AddButtonComboPressDownObserver( + "Example Plugin: Press Down observer test", + DEFAULT_PRESS_DOWN_BUTTON_COMBO, // L + R Even though this is same combo as in buttonComboPressDown an observer will ignore conflicts. + pressDownObserverComboCallback, + NULL, + &sPressDownObserverButtonComboExampleHandle, + &comboStatus); // comboStatus will always be WUPS_BUTTON_COMBO_COMBO_STATUS_VALID for observers. + + if (err == WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + // To remove that button combo, we explicitly have to call "WUPSButtonComboAPI_RemoveButtonCombo", we'll do it in DEINITIALIZE_PLUGIN + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to add press down observer button combo"); + } + } + + { + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // In case of a conflict, the function will return SUCCESS, but the combo status will be WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT + + // Let's create a button combo which will lead to a conflict. This time we want to check if a combo has been hold for 500ms. Conflicts are checked across + // non-observer combo types. + WUPSButtonCombo_ComboStatus comboStatus = WUPS_BUTTON_COMBO_COMBO_STATUS_INVALID_STATUS; + WUPSButtonCombo_Error err = WUPSButtonComboAPI_AddButtonComboHold( + "Example Plugin: Hold test", + DEFAULT_PRESS_HOLD_COMBO, // L+R+DPAD+DOWN. This combo includes the combo "L+R" of the buttonComboPressDown, so this will lead to a conflict. + 500, // We need to hold that combo for 500ms + holdComboCallback, + NULL, + &sHoldButtonComboExampleHandle, + &comboStatus); // comboStatus will always be WUPS_BUTTON_COMBO_COMBO_STATUS_VALID for observers. + + if (err == WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + // API returned "WUPS_BUTTON_COMBO_ERROR_SUCCESS", but we have a conflict because of the existing press down combo. + switch (comboStatus) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + DEBUG_FUNCTION_LINE_INFO("Button combo is valid and active"); + break; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + DEBUG_FUNCTION_LINE_INFO("Conflict detected for button combo"); // <-- this is expected to happen + break; + default: + DEBUG_FUNCTION_LINE_ERR("Invalid combo status"); + break; + } + + // Once combo is in the "WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT" state it can only be valid again, if the button combo or the controllerMask changes. + // Other combos won't ever affect this state of this combo + // We can easily update the button combo + err = WUPSButtonComboAPI_UpdateButtonCombo( + sHoldButtonComboExampleHandle, + WUPS_BUTTON_COMBO_BUTTON_ZR | WUPS_BUTTON_COMBO_BUTTON_R | WUPS_BUTTON_COMBO_BUTTON_DOWN, + &comboStatus); + if (err == WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_INFO("Updated button combo"); + // Check the comboStatus after updating the combo + switch (comboStatus) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + DEBUG_FUNCTION_LINE_INFO("Button combo is valid and active"); // <-- this is expected to happen + break; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + DEBUG_FUNCTION_LINE_INFO("Conflict detected for button combo"); + break; + default: + DEBUG_FUNCTION_LINE_ERR("Invalid combo status"); + break; + } + } else { + DEBUG_FUNCTION_LINE_INFO("Failed to update button combo"); + } + // To remove that button combo, we explicitly have to call "WUPSButtonComboAPI_RemoveButtonCombo", we'll do it in DEINITIALIZE_PLUGIN + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to add press down observer button combo"); + } + } + { + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // To register a combo for just one controller, we can't use the helper function we're using above. + // We have fill in the options instead: + WUPSButtonCombo_ComboOptions options; + options.metaOptions.label = "Combo for WPAD_0"; // can be NULL + options.callbackOptions.callback = holdObserverExComboCallback; // must not be NULL + options.callbackOptions.context = NULL; // can be NULL + // We want to a "hold" combination where we have to hold for 100ms. Let's create an observer to not care about conflicts for this example plugin. + options.buttonComboOptions.type = WUPS_BUTTON_COMBO_COMBO_TYPE_HOLD_OBSERVER; + options.buttonComboOptions.optionalHoldForXMs = 100; // <-- will be ignored if the type is WUPS_BUTTON_COMBO_COMBO_TYPE_PRESS_DOWN* + + // Defines which button the combo is using and which controllers should be checked + options.buttonComboOptions.basicCombo.controllerMask = WUPS_BUTTON_COMBO_CONTROLLER_WPAD_0; // We check for WPAD_0, but we could also do something like (WUPS_BUTTON_COMBO_CONTROLLER_WPAD_0 | WUPS_BUTTON_COMBO_CONTROLLER_VPAD_0) + options.buttonComboOptions.basicCombo.combo = WUPS_BUTTON_COMBO_BUTTON_A | WUPS_BUTTON_COMBO_BUTTON_B; // <-- will be ignored if the type is WUPS_BUTTON_COMBO_COMBO_TYPE_PRESS_DOWN* + + WUPSButtonCombo_ComboStatus comboStatus = WUPS_BUTTON_COMBO_COMBO_STATUS_INVALID_STATUS; + const WUPSButtonCombo_Error err = WUPSButtonComboAPI_AddButtonCombo(&options, + &sHoldObserverExButtonComboExampleHandle, + &comboStatus); // comboStatus will always be WUPS_BUTTON_COMBO_COMBO_STATUS_VALID for observers. + + if (err == WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + // To remove that button combo, we explicitly have to call "WUPSButtonComboAPI_RemoveButtonCombo", we'll do it in DEINITIALIZE_PLUGIN + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to add hold observer button combo for WPAD_0"); + } + } + deinitLogging(); } @@ -164,6 +340,14 @@ INITIALIZE_PLUGIN() { **/ DEINITIALIZE_PLUGIN() { DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!"); + WUPSButtonComboAPI_RemoveButtonCombo(sPressDownButtonComboExampleHandle); + WUPSButtonComboAPI_RemoveButtonCombo(sPressDownObserverButtonComboExampleHandle); + WUPSButtonComboAPI_RemoveButtonCombo(sHoldButtonComboExampleHandle); + WUPSButtonComboAPI_RemoveButtonCombo(sHoldObserverExButtonComboExampleHandle); + sPressDownButtonComboExampleHandle.handle = NULL; + sPressDownObserverButtonComboExampleHandle.handle = NULL; + sHoldButtonComboExampleHandle.handle = NULL; + sHoldObserverExButtonComboExampleHandle.handle = NULL; } /** @@ -191,23 +375,23 @@ ON_APPLICATION_REQUESTS_EXIT() { /** This defines a function replacement. - It allows to replace the system function with an own function. + It allows to replace the system function with an own function. So whenever a game / application calls an overridden function, your function gets called instead. - + Currently it's only possible to override functions that are loaded from .rpl files of OSv10 (00050010-1000400A). - + Signature of this macro: DECL_FUNCTION( RETURN_TYPE, ARBITRARY_NAME_OF_FUNCTION , ARGS_SEPERATED_BY_COMMA){ //Your code goes here. } - + Within this macro, two more function get declare you can use. my_ARBITRARY_NAME_OF_FUNCTION and real_ARBITRARY_NAME_OF_FUNCTION - + RETURN_TYPE my_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA): - is just name of the function that gets declared in this macro. + is just name of the function that gets declared in this macro. It has the same effect as calling the overridden function directly. - + RETURN_TYPE real_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA): is the name of the function, that leads to function that was overridden. Use this to call the original function that will be overridden. @@ -215,7 +399,8 @@ ON_APPLICATION_REQUESTS_EXIT() { Use this macro for each function you want to override **/ -DECL_FUNCTION(int, FSOpenFile, FSClient *pClient, FSCmdBlock *pCmd, const char *path, const char *mode, int *handle, int error) { +DECL_FUNCTION(int, FSOpenFile, FSClient *pClient, FSCmdBlock *pCmd, const char *path, const char *mode, int *handle, + int error) { int result = real_FSOpenFile(pClient, pCmd, path, mode, handle, error); if (logFSOpen) { DEBUG_FUNCTION_LINE_INFO("FSOpenFile called for folder %s! Result %d", path, result); @@ -231,4 +416,4 @@ WUPS_MUST_REPLACE(FUNCTION_NAME_IN_THIS_FILE, NAME_OF_LIB_WHICH_CONTAINS_THIS_ Define this for each function you want to override. **/ -WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile); \ No newline at end of file +WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile); diff --git a/plugins/example_plugin_cpp/src/main.cpp b/plugins/example_plugin_cpp/src/main.cpp index 647cc51..71b59c9 100644 --- a/plugins/example_plugin_cpp/src/main.cpp +++ b/plugins/example_plugin_cpp/src/main.cpp @@ -1,14 +1,19 @@ #include "utils/logger.h" #include -#include #include +#include #include #include +#include #include #include #include #include +#include + +#include + /** Mandatory plugin information. If not set correctly, the loader will refuse to use the plugin. @@ -20,6 +25,8 @@ WUPS_PLUGIN_AUTHOR("Maschell"); WUPS_PLUGIN_LICENSE("BSD"); #define LOG_FS_OPEN_CONFIG_ID "logFSOpen" +#define BUTTON_COMBO_PRESS_DOWN_CONFIG_ID "pressDownItem" +#define BUTTON_COMBO_HOLD_CONFIG_ID "holdItem" #define OTHER_EXAMPLE_BOOL_CONFIG_ID "otherBoolItem" #define OTHER_EXAMPLE2_BOOL_CONFIG_ID "other2BoolItem" #define INTEGER_RANGE_EXAMPLE_CONFIG_ID "intRangeExample" @@ -48,6 +55,14 @@ bool sLogFSOpen = LOF_FS_OPEN_DEFAULT_VALUE; int sIntegerRangeValue = INTEGER_RANGE_DEFAULT_VALUE; ExampleOptions sExampleOptionValue = MULTIPLE_VALUES_DEFAULT_VALUE; +static std::forward_list sButtonComboInstances; + +static WUPSButtonCombo_ComboHandle sPressDownExampleHandle(nullptr); +static WUPSButtonCombo_ComboHandle sHoldExampleHandle(nullptr); + +WUPSButtonCombo_Buttons DEFAULT_PRESS_DOWN_BUTTON_COMBO = WUPS_BUTTON_COMBO_BUTTON_L | WUPS_BUTTON_COMBO_BUTTON_R; +WUPSButtonCombo_Buttons DEFAULT_PRESS_HOLD_COMBO = WUPS_BUTTON_COMBO_BUTTON_L | WUPS_BUTTON_COMBO_BUTTON_R | WUPS_BUTTON_COMBO_BUTTON_DOWN; + /** * Callback that will be called if the config has been changed */ @@ -84,6 +99,10 @@ void multipleValueItemChanged(ConfigItemIntegerRange *item, uint32_t newValue) { } } +void buttonComboItemChanged(ConfigItemButtonCombo *item, uint32_t newValue) { + DEBUG_FUNCTION_LINE_INFO("New value in buttonComboItemChanged: %d for %s", newValue, item->identifier); +} + WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) { // To use the C++ API, we create new WUPSConfigCategory from the root handle! WUPSConfigCategory root = WUPSConfigCategory(rootHandle); @@ -117,6 +136,14 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro 0, 50, &integerRangeItemChanged)); + // To change a button combo, we can use the ButtonCombo ConfigItem + root.add(WUPSConfigItemButtonCombo::Create(BUTTON_COMBO_PRESS_DOWN_CONFIG_ID, "Press Down button combo", + DEFAULT_PRESS_DOWN_BUTTON_COMBO, sPressDownExampleHandle, + buttonComboItemChanged)); + root.add(WUPSConfigItemButtonCombo::Create(BUTTON_COMBO_HOLD_CONFIG_ID, "Hold button combo", + DEFAULT_PRESS_HOLD_COMBO, sHoldExampleHandle, + buttonComboItemChanged)); + // To select value from an enum WUPSConfigItemMultipleValues fits the best. constexpr WUPSConfigItemMultipleValues::ValuePair possibleValues[] = { @@ -152,7 +179,7 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro // In case we don't like exception, we can use the API as well. // If we add a "WUPSConfigAPIStatus" reference to the API calls, the function won't throw an exception. - // Instead it will return std::optionals and write the result into the WUPSConfigAPIStatus. + // Instead, it will return std::optionals and write the result into the WUPSConfigAPIStatus. WUPSConfigAPIStatus err; auto categoryOpt = WUPSConfigCategory::Create("Just another Category", err); if (!categoryOpt) { @@ -209,6 +236,157 @@ INITIALIZE_PLUGIN() { DEBUG_FUNCTION_LINE_ERR("GetOrStoreDefault failed: %s (%d)", WUPSStorageAPI_GetStatusStr(storageRes), storageRes); } + // To register a button combo, we can use the C++ wrapper class "WUPSButtonComboAPI::ButtonCombo". + // The combo will be added on construction of that wrapper, and removed again in the destructor. Use `std::move` to move it around. + // Like the C++ config api there are two versions of all function, one that throws an exception on error and one that returns a std::optional but set an additional error parameter. + + // Example of the exception throwing API + try { + WUPSButtonCombo_ComboStatus comboStatus; + // Create a button combo which detects if a combo has been pressed down on any controller. + // This version will check for conflicts. It's useful to check for conflicts if you want to use that button combo for a global unique thing + // that's always possible, like taking screenshots. + WUPSButtonComboAPI::ButtonCombo buttonComboPressDown = WUPSButtonComboAPI::CreateComboPressDown( + "Example Plugin: Press Down test", + DEFAULT_PRESS_DOWN_BUTTON_COMBO, // L + R + [](const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("Button combo has been pressed down by controller %s", WUPSButtonComboAPI::GetControllerTypeStr(triggeredBy).data()); + }, + nullptr, + comboStatus); + // On success, we can check if the combo is actually active by checking the combo status. + // If there is already another combo that conflicts with us, the status will be set to WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT + switch (comboStatus) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + DEBUG_FUNCTION_LINE_INFO("Button combo is valid and active"); + break; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + DEBUG_FUNCTION_LINE_INFO("Conflict detected for button combo"); + break; + default: + DEBUG_FUNCTION_LINE_ERR("Invalid combo status"); + break; + } + // We want to save the handle, so we can use it for the config menu + sPressDownExampleHandle = buttonComboPressDown.getHandle(); + // BUT. We need to make sure to keep that button combo instance. Otherwise, the combo will be removed. + // We save it in this list, which gets cleared in DEINITIALIZE_PLUGIN + sButtonComboInstances.emplace_front(std::move(buttonComboPressDown)); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // But we can also create button combos without caring about conflicts. + // E.g. when a new Aroma update is detected, the updater can be launched by holding the PLUS button. This should always be possible. + + // If we don't want to check for conflicts, we need to create a "PressDownObserver" + WUPSButtonComboAPI::ButtonCombo buttonComboPressDownObserver = WUPSButtonComboAPI::CreateComboPressDownObserver( + "Example Plugin: Press Down observer test", + DEFAULT_PRESS_DOWN_BUTTON_COMBO, // L + R Even though this is same combo as in buttonComboPressDown an observer will ignore conflicts. + [](const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("[OBSERVER] Button combo has been pressed down by controller %s", WUPSButtonComboAPI::GetControllerTypeStr(triggeredBy).data()); + }, + nullptr, + comboStatus); // comboStatus will always be WUPS_BUTTON_COMBO_COMBO_STATUS_VALID for observers. + + // Let's move this instance into the list as well. But in this case we don't need the handle + sButtonComboInstances.emplace_front(std::move(buttonComboPressDownObserver)); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // In case of a conflict, the button combo instance will be returned, but the combo status will be WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT + + // Let's create a button combo which will lead to a conflict. This time we want to check if a combo has been hold for 500ms. Conflicts are checked across + // non-observer combo types. + WUPSButtonComboAPI::ButtonCombo buttonComboHold = WUPSButtonComboAPI::CreateComboHold( + "Example Plugin: Hold test", + DEFAULT_PRESS_HOLD_COMBO, // L+R+DPAD+DOWN. This combo includes the combo "L+R" of the buttonComboPressDown, so this will lead to a conflict. + 500, // We need to hold that combo for 500ms + [](const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("Button combo has been hold for 500ms by controller %s", WUPSButtonComboAPI::GetControllerTypeStr(triggeredBy).data()); + }, + nullptr, + comboStatus); // comboStatus will always be WUPS_BUTTON_COMBO_COMBO_STATUS_VALID for observers. + + switch (comboStatus) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + DEBUG_FUNCTION_LINE_INFO("Button combo is valid and active"); + break; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + DEBUG_FUNCTION_LINE_INFO("Conflict detected for button combo"); // <-- this is expected to happen + break; + default: + DEBUG_FUNCTION_LINE_ERR("Invalid combo status"); + break; + } + + // Once combo is in the "WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT" state it can only be valid again, if the combo or the controllerMask changes. Other combo won't ever affect this state of this combo + // We can easily update the button combo + if (const auto res = buttonComboHold.UpdateButtonCombo(WUPS_BUTTON_COMBO_BUTTON_ZR | WUPS_BUTTON_COMBO_BUTTON_R | WUPS_BUTTON_COMBO_BUTTON_DOWN, comboStatus); res != WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_INFO("Failed to update button combo"); + } else { + DEBUG_FUNCTION_LINE_INFO("Updated button combo"); + // Check the comboStatus after updating the combo + switch (comboStatus) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + DEBUG_FUNCTION_LINE_INFO("Button combo is valid and active"); // <-- this is expected to happen + break; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + DEBUG_FUNCTION_LINE_INFO("Conflict detected for button combo"); + break; + default: + DEBUG_FUNCTION_LINE_ERR("Invalid combo status"); + break; + } + } + + // We want to save the handle, so we can use it for the config menu + sHoldExampleHandle = buttonComboHold.getHandle(); + // Let's move this instance into the list as well. But in this case we don't need the handle + sButtonComboInstances.emplace_front(std::move(buttonComboHold)); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // If we want to create combo for only a specific controller (or specific controllers), we have to use the "Ex" functions. + + // If we don't want to check for conflicts, we need to create a "PressDownObserver", but this time we have to provide a controllerMask and if it's a observer. + WUPSButtonComboAPI::ButtonCombo buttonComboPressDownExObserver = WUPSButtonComboAPI::CreateComboPressDownEx( + "Example Plugin: Press Down observer test", + WUPS_BUTTON_COMBO_CONTROLLER_WPAD_0, // Define which controllers should be checked. Could be something (WUPS_BUTTON_COMBO_CONTROLLER_WPAD_0 | WUPS_BUTTON_COMBO_CONTROLLER_VPAD). + DEFAULT_PRESS_DOWN_BUTTON_COMBO, // L + R Even though this is same combo as in buttonComboPressDown an observer will ignore conflicts. + [](const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("[OBSERVER WPAD_0] Button combo has been pressed down by controller %s", WUPSButtonComboAPI::GetControllerTypeStr(triggeredBy).data()); + }, + nullptr, + true, // we want an observer + comboStatus); // comboStatus will always be WUPS_BUTTON_COMBO_COMBO_STATUS_VALID for observers. + + // Let's move this instance into the list as well. But in this case we don't need the handle + sButtonComboInstances.emplace_front(std::move(buttonComboPressDownExObserver)); + } catch (std::exception &e) { + DEBUG_FUNCTION_LINE_ERR("Caught exception: %s", e.what()); + } + + + // But we can also use the version which doesn't throw any exceptions + WUPSButtonCombo_ComboStatus comboStatus; + WUPSButtonCombo_Error comboError; + + // We add a "WUPSButtonCombo_Error" parameter at the end, the function will not throw any exception, but return a std::optional instead. + // Create an observer, because we don't care for conflicts. + std::optional buttonComboPressDownObserverOpt = WUPSButtonComboAPI::CreateComboPressDownObserver( + "Example Plugin: Press Down test 2", + WUPS_BUTTON_COMBO_BUTTON_X | WUPS_BUTTON_COMBO_BUTTON_Y, + [](const WUPSButtonCombo_ControllerTypes triggeredBy, WUPSButtonCombo_ComboHandle, void *) { + DEBUG_FUNCTION_LINE_INFO("[OBSERVER] Other button combo has been pressed down by controller %s", WUPSButtonComboAPI::GetControllerTypeStr(triggeredBy).data()); + }, + nullptr, + comboStatus, + comboError); + if (buttonComboPressDownObserverOpt && comboError == WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + // Creating was successful! Let's move it to the list as well. + sButtonComboInstances.emplace_front(std::move(*buttonComboPressDownObserverOpt)); + } + deinitLogging(); } @@ -216,6 +394,8 @@ INITIALIZE_PLUGIN() { Gets called when the plugin will be unloaded. **/ DEINITIALIZE_PLUGIN() { + // Remove all button combos from this plugin. + sButtonComboInstances.clear(); DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!"); }