Compare commits

...

1 Commits

Author SHA1 Message Date
Shelley Vohr
4877498e8d feat: allow actions on toasts 2025-08-11 22:34:10 +02:00
3 changed files with 103 additions and 3 deletions

View File

@@ -144,6 +144,7 @@ auto_filenames = {
"docs/api/structures/service-worker-info.md",
"docs/api/structures/shared-dictionary-info.md",
"docs/api/structures/shared-dictionary-usage-info.md",
"docs/api/structures/shared-texture-handle.md",
"docs/api/structures/shared-worker-info.md",
"docs/api/structures/sharing-item.md",
"docs/api/structures/shortcut-details.md",

View File

@@ -209,11 +209,79 @@ HRESULT WindowsToastNotification::ShowInternal(
REPORT_AND_RETURN_IF_FAILED(SetupCallbacks(toast_notification_.Get()),
"WinAPI: SetupCallbacks failed");
// Append actions after creating notification (XML still mutable before show)
if (options.actions.size() > 0 && options.toast_xml.empty()) {
AppendActionsToXml(toast_xml.Get(), options);
}
REPORT_AND_RETURN_IF_FAILED(toast_notifier_->Show(toast_notification_.Get()),
"WinAPI: Show failed");
return S_OK;
}
HRESULT WindowsToastNotification::AppendActionsToXml(
IXmlDocument* doc, const NotificationOptions& options) {
if (!doc || options.actions.empty())
return S_OK;
// Acquire root <toast>
ScopedHString toast_tag(L"toast");
ComPtr<IXmlNodeList> toast_nodes;
RETURN_IF_FAILED(doc->GetElementsByTagName(toast_tag, &toast_nodes));
ComPtr<IXmlNode> toast_root;
RETURN_IF_FAILED(toast_nodes->Item(0, &toast_root));
// Create <actions>
ScopedHString actions_tag(L"actions");
ComPtr<IXmlElement> actions_el;
RETURN_IF_FAILED(doc->CreateElement(actions_tag, &actions_el));
ComPtr<IXmlNode> actions_node_tmp;
RETURN_IF_FAILED(actions_el.As(&actions_node_tmp));
ComPtr<IXmlNode> actions_node;
RETURN_IF_FAILED(toast_root->AppendChild(actions_node_tmp.Get(), &actions_node));
int idx = 0;
for (const auto& act : options.actions) {
if (idx >= 5)
break; // practical button limit
ScopedHString action_tag(L"action");
ComPtr<IXmlElement> action_el;
RETURN_IF_FAILED(doc->CreateElement(action_tag, &action_el));
ComPtr<IXmlNode> action_node_tmp;
RETURN_IF_FAILED(action_el.As(&action_node_tmp));
ComPtr<IXmlNode> action_node;
RETURN_IF_FAILED(actions_node->AppendChild(action_node_tmp.Get(), &action_node));
ComPtr<IXmlNamedNodeMap> attr_map;
RETURN_IF_FAILED(action_node->get_Attributes(&attr_map));
auto add_attr = [&](const wchar_t* name, const std::wstring& value) {
ComPtr<IXmlAttribute> attr;
ScopedHString name_h(name);
RETURN_IF_FAILED(doc->CreateAttribute(name_h, &attr));
ComPtr<IXmlNode> attr_node;
RETURN_IF_FAILED(attr.As(&attr_node));
ScopedHString value_h(value.c_str());
ComPtr<IXmlText> txt;
RETURN_IF_FAILED(doc->CreateTextNode(value_h, &txt));
ComPtr<IXmlNode> txt_node;
RETURN_IF_FAILED(txt.As(&txt_node));
ComPtr<IXmlNode> unused_child;
RETURN_IF_FAILED(attr_node->AppendChild(txt_node.Get(), &unused_child));
ComPtr<IXmlNode> unused_attr;
RETURN_IF_FAILED(attr_map->SetNamedItem(attr_node.Get(), &unused_attr));
return S_OK;
};
std::u16string content16 = act.text;
add_attr(L"content", base::UTF16ToWide(content16));
add_attr(L"arguments", L"action=" + std::to_wstring(idx));
add_attr(L"activationType", L"foreground");
idx++;
}
return S_OK;
}
HRESULT WindowsToastNotification::GetToastXml(
winui::Notifications::IToastNotificationManagerStatics* toastManager,
const std::u16string& title,
@@ -641,9 +709,34 @@ ToastEventHandler::~ToastEventHandler() = default;
IFACEMETHODIMP ToastEventHandler::Invoke(
winui::Notifications::IToastNotification* sender,
IInspectable* args) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&Notification::NotificationClicked, notification_));
bool dispatched = false;
if (args) {
ComPtr<ABI::Windows::UI::Notifications::IToastActivatedEventArgs> activated_args;
if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&activated_args)))) {
HString h_args;
if (SUCCEEDED(activated_args->get_Arguments(h_args.GetAddressOf()))) {
std::wstring arguments(h_args.GetRawBuffer(nullptr));
constexpr std::wstring_view prefix = L"action=";
if (arguments.rfind(prefix, 0) == 0) {
try {
int index = std::stoi(arguments.substr(prefix.size()));
if (notification_) {
dispatched = true;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&Notification::NotificationAction,
notification_, index));
}
} catch (...) {
}
}
}
}
}
if (!dispatched && notification_) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&Notification::NotificationClicked,
notification_));
}
DebugLog("Notification clicked");
return S_OK;

View File

@@ -68,6 +68,12 @@ class WindowsToastNotification : public Notification {
const bool silent,
ABI::Windows::Data::Xml::Dom::IXmlDocument** toast_xml);
HRESULT SetXmlAudioSilent(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc);
// Appends <actions> / <action> elements for each custom action button
// provided in the NotificationOptions. Uses arguments in the form
// "action=<index>" so the activation handler can dispatch the correct
// NotificationAction(index) event back to JS.
HRESULT AppendActionsToXml(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,
const NotificationOptions& options);
HRESULT SetXmlScenarioReminder(
ABI::Windows::Data::Xml::Dom::IXmlDocument* doc);
HRESULT SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,