Example plugin: add button combo API usage

This commit is contained in:
Maschell 2025-01-20 18:16:18 +01:00
parent a5e8d2b269
commit dd84ac5b4b
4 changed files with 391 additions and 22 deletions

View File

@ -13,6 +13,8 @@ public:
WUPSConfigCategory(const WUPSConfigCategory &) = delete;
WUPSConfigCategory &operator=(const WUPSConfigCategory &) = delete;
WUPSConfigCategory(WUPSConfigCategory &&src) noexcept;
WUPSConfigCategory &operator=(WUPSConfigCategory &&src) noexcept;

View File

@ -14,6 +14,8 @@ public:
WUPSConfigItem(const WUPSConfigItem &) = delete;
WUPSConfigItem &operator=(const WUPSConfigItem &) = delete;
WUPSConfigItem(WUPSConfigItem &&src) noexcept;
WUPSConfigItem &operator=(WUPSConfigItem &&src) noexcept;

View File

@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stdio.h>
#include <wups.h>
#include <wups/button_combo/api.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <wups/config/WUPSConfigItemMultipleValues.h>
#include <wups/config/WUPSConfigItemStub.h>
@ -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);
WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile);

View File

@ -1,14 +1,19 @@
#include "utils/logger.h"
#include <coreinit/filesystem.h>
#include <malloc.h>
#include <wups.h>
#include <wups/button_combo/api.h>
#include <wups/config/WUPSConfigCategory.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <wups/config/WUPSConfigItemButtonCombo.h>
#include <wups/config/WUPSConfigItemIntegerRange.h>
#include <wups/config/WUPSConfigItemMultipleValues.h>
#include <wups/config/WUPSConfigItemStub.h>
#include <wups/config_api.h>
#include <forward_list>
#include <malloc.h>
/**
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<WUPSButtonComboAPI::ButtonCombo> 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<WUPSButtonComboAPI::ButtonCombo> 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!");
}