overlay: toast notifications (#704)

## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds toast notifications to the overlay.

Toast notifications are transient windows (shown for about 4 seconds).
These are drawn on top of the game even when the overlay is inactive
(i.e., when no window is open), and does not cause any input sink
behavior.

Add toasts for some common user-driven actions that could use a
notification, such as:

* screenshot
* card insert
* PIN macro
* API client connect/disconnect
* virtual printer
* screen resize toggle / scene switch

Add an option to change where the toasts are displayed, or to turn it
off entirely.

Add SDK function for it, and add it to the samples.

## Testing
Tested DDR (shown at bottom right, as is for most games) and RB (shown
top right by default).
This commit is contained in:
bicarus
2026-05-27 00:45:27 -07:00
committed by GitHub
parent d929cdf7c8
commit 311404f56b
20 changed files with 574 additions and 43 deletions
+33 -13
View File
@@ -2,18 +2,18 @@
#ifndef SPICE_SDK_H
#define SPICE_SDK_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
#define SPICE_SDK_ENTRY_POINT extern "C" __declspec(dllexport) int __cdecl
#else
#define SPICE_SDK_ENTRY_POINT __declspec(dllexport) int __cdecl
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
#define SPICE_SDK_ENTRY_POINT extern "C" __declspec(dllexport) int __cdecl
#else
#define SPICE_SDK_ENTRY_POINT __declspec(dllexport) int __cdecl
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef enum SPICE_SDK_STATUS_CODE {
SPICE_SDK_STATUS_SUCCESS = 0,
@@ -41,6 +41,13 @@ typedef enum SPICE_SDK_LOG_LEVEL {
SPICE_SDK_LOG_LEVEL_FATAL = 3,
} SPICE_SDK_LOG_LEVEL;
typedef enum SPICE_SDK_TOAST_SEVERITY {
SPICE_SDK_TOAST_LEVEL_INFO = 0,
SPICE_SDK_TOAST_LEVEL_SUCCESS = 1,
SPICE_SDK_TOAST_LEVEL_WARNING = 2,
SPICE_SDK_TOAST_LEVEL_ERROR = 3,
} SPICE_SDK_TOAST_SEVERITY;
typedef struct SPICE_SDK_TOUCH_POINT {
uint32_t id;
int x;
@@ -219,6 +226,17 @@ typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_keypad_func)(
char key
);
// add_toast (v0.2 and up)
// adds an overlay toast notification
//
// severity: see SPICE_SDK_TOAST_SEVERITY; controls the accent color (purely cosmetic)
// text: null-terminated UTF-8 message to display; wraps inside the toast
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_add_toast_func)(
SPICE_SDK_TOAST_SEVERITY severity,
const char *text
);
typedef struct SPICE_SDK_V0 {
uint32_t size;
@@ -242,6 +260,8 @@ typedef struct SPICE_SDK_V0 {
spice_sdk_insert_card_func *insert_card;
spice_sdk_set_keypad_func *set_keypad;
spice_sdk_add_toast_func *add_toast;
} SPICE_SDK_V0;
typedef void (__cdecl spice_sdk_destroy_callback_func)(
@@ -270,4 +290,4 @@ typedef int (__cdecl spice_sdk_entry_point_func)(
} // extern "C"
#endif
#endif // SPICE_SDK_H
#endif // SPICE_SDK_H
+9 -1
View File
@@ -84,7 +84,15 @@ static void worker_thread_main(std::stop_token stop_token) {
spice.set_button(arrow.button, true, 1.f);
if (!arrow.previous_state) {
LOG_INFO(std::format("let me hear you say: {}", arrow.name).c_str());
if (spice.add_toast) {
spice.add_toast(
SPICE_SDK_TOAST_LEVEL_INFO,
std::format("let me hear you say: {}", arrow.name).c_str());
} else {
LOG_INFO(std::format("let me hear you say: {}", arrow.name).c_str());
}
arrow.previous_state = true;
}
} else {
+18 -18
View File
@@ -38,10 +38,10 @@ static unsigned __stdcall worker_thread(void *arg);
// this sample assumes that the game is IIDX, but it doesn't check for it.
SPICE_SDK_ENTRY_POINT
spice_sdk_entry_point(
spice_sdk_init_func *init
)
SPICE_SDK_ENTRY_POINT
spice_sdk_entry_point(
spice_sdk_init_func *init
)
{
SPICE_SDK_STATUS_CODE status;
@@ -108,59 +108,59 @@ static unsigned __stdcall worker_thread(void *arg) {
phase += 1;
switch (phase) {
case 1:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get buttons...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_INFO, "get buttons...");
get_buttons();
break;
case 2:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set buttons...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_WARNING, "set buttons...");
set_buttons();
break;
case 3:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear buttons...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_ERROR, "clear buttons...");
clear_buttons();
break;
case 4:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get analogs...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_INFO, "get analogs...");
get_analogs();
break;
case 5:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set analogs...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_WARNING, "set analogs...");
set_analogs();
break;
case 6:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear analogs...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_ERROR, "clear analogs...");
clear_analogs();
break;
case 7:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get lights...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_INFO, "get lights...");
get_lights();
break;
case 8:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set lights...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_WARNING, "set lights...");
set_lights();
break;
case 9:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear lights...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_ERROR, "clear lights...");
clear_lights();
break;
case 10:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set touch...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_INFO, "set touch...");
set_touch();
break;
case 11:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear touch...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_ERROR, "clear touch...");
clear_touch();
break;
case 12:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "insert card...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_SUCCESS, "insert card...");
insert_card();
break;
case 13:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set keypad...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_SUCCESS, "set keypad...");
set_keypad();
break;
case 14:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear keypad...");
spice.add_toast(SPICE_SDK_TOAST_LEVEL_ERROR, "clear keypad...");
clear_keypad();
break;
default:
+46
View File
@@ -7,6 +7,7 @@
#include "games/io.h"
#include "launcher/launcher.h"
#include "misc/eamuse.h"
#include "overlay/notifications.h"
#include "sdk/include/spicesdk.h"
#include "touch/touch.h"
#include "util/logging.h"
@@ -29,6 +30,7 @@ static spice_sdk_set_touch_func sdk_set_touch;
static spice_sdk_clear_touch_func sdk_clear_touch;
static spice_sdk_insert_card_func sdk_insert_card;
static spice_sdk_set_keypad_func sdk_set_keypad;
static spice_sdk_add_toast_func sdk_add_toast;
struct SdkModule {
std::string dll;
@@ -169,6 +171,12 @@ sdk_init(
v0->insert_card = sdk_insert_card;
v0->set_keypad = sdk_set_keypad;
// end of 0.1
if (v0->size >= RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, add_toast)) {
v0->add_toast = sdk_add_toast;
}
// end of 0.2
// any newer minor iterations will need to check the size
{
@@ -594,5 +602,43 @@ sdk_set_keypad(
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_add_toast(
SPICE_SDK_TOAST_SEVERITY severity,
const char *text
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!text) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
overlay::notifications::Severity sev;
switch (severity) {
case SPICE_SDK_TOAST_LEVEL_INFO:
sev = overlay::notifications::Severity::Info;
break;
case SPICE_SDK_TOAST_LEVEL_SUCCESS:
sev = overlay::notifications::Severity::Success;
break;
case SPICE_SDK_TOAST_LEVEL_WARNING:
sev = overlay::notifications::Severity::Warning;
break;
case SPICE_SDK_TOAST_LEVEL_ERROR:
sev = overlay::notifications::Severity::Error;
break;
default:
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
overlay::notifications::add(sev, text);
return SPICE_SDK_STATUS_SUCCESS;
}
} // namespace sdk