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>
@ -30,6 +31,12 @@ WUPS_USE_WUT_DEVOPTAB(); // Use the wut devoptabs
WUPS_USE_STORAGE("example_plugin"); // Unique id for the storage api
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;
}
/**
@ -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);

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!");
}