Skip to content

Commit

Permalink
MSIX: initial implement notifications library
Browse files Browse the repository at this point in the history
  • Loading branch information
yuyoyuppe committed Feb 4, 2020
1 parent b41d2d6 commit e9e993e
Show file tree
Hide file tree
Showing 28 changed files with 793 additions and 0 deletions.
13 changes: 13 additions & 0 deletions PowerToys.sln
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUWPUI", "src\mod
{0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "notifications", "src\common\notifications_winrt\notifications.vcxproj", "{0B593A6C-4143-4337-860E-DB5710FB87DB}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "notifications_dll", "src\common\notifications\notifications_dll.vcxproj", "{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "windowwalker", "windowwalker", "{8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Window Walker", "src\modules\windowwalker\app\Window Walker\Window Walker.csproj", "{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB}"
Expand Down Expand Up @@ -168,6 +171,14 @@ Global
{0485F45C-EA7A-4BB5-804B-3E8D14699387}.Debug|x64.Build.0 = Debug|x64
{0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x64.ActiveCfg = Release|x64
{0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x64.Build.0 = Release|x64
{0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.ActiveCfg = Debug|x64
{0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.Build.0 = Debug|x64
{0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.ActiveCfg = Release|x64
{0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.Build.0 = Release|x64
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x64.ActiveCfg = Debug|x64
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x64.Build.0 = Debug|x64
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x64.ActiveCfg = Release|x64
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x64.Build.0 = Release|x64
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB}.Debug|x64.ActiveCfg = Debug|x64
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB}.Debug|x64.Build.0 = Debug|x64
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB}.Release|x64.ActiveCfg = Release|x64
Expand Down Expand Up @@ -199,6 +210,8 @@ Global
{2151F984-E006-4A9F-92EF-C6DDE3DC8413} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
{64A80062-4D8B-4229-8A38-DFA1D7497749} = {BEEAB7F2-FFF6-45AB-9CDB-B04CC0734B88}
{0485F45C-EA7A-4BB5-804B-3E8D14699387} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
{0B593A6C-4143-4337-860E-DB5710FB87DB} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB} = {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03}
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6} = {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03}
Expand Down
64 changes: 64 additions & 0 deletions doc/devdocs/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,67 @@ Helper methods for the settings.

#### Start visible helper: [header](/src/common/start_visible.h) [source](/src/common/start_visible.cpp)
Contains function to test if the Start menu is visible.

# Toast Notifications

#### Notifications API [header](/src/common/notifications.h) [source](/src/common/notifications.cpp)
To use UWP-style toast notifications, simply include the header and call one of these functions:

```cpp
void show_toast(std::wstring_view message); // #1

void show_toast_background_activated( // #2
std::wstring_view message,
std::wstring_view background_handler_id,
std::vector<std::wstring_view> button_labels);
```
We might add more functions in the future if the need arises, e.g. `show_toast_xml` which will accept raw XML for rich customization.
Description:
- `#1` is for sending simple notifications without any callbacks or buttons
- `#2` is capable of showing a toast with multiple buttons and background activation
- `message` is a plain-text argument
Implement a toast activation handler/callback as a function in [handler_functions.cpp](/src/common/notifications_winrt/handler_functions.cpp) and register its `background_handler_id` via `handlers_map`, e.g.:
```cpp
// Your .cpp where you'd like to show a toast
#include <common/notifications.h>
void some_func() {
// ...
notifications::show_toast_background_activated(
L"Toast message!", // text displayed in a toast
L"awesome_toast", // activation handler id
{L"Press me!", L"Also could press me!", L"I'm here to be pressed!"} // buttons in a toast
);
```

```cpp
// handler_functions.cpp
void awesome_toast_handler(IBackgroundTaskInstance, const size_t button_id)
{
switch(button_id)
{
case 0:
// handle "Press me!" button click
case 1:
// handle "Also could press me!" button click
case 2:
// handle "I'm here to be pressed!" button click
}
}

namespace
{
const std::unordered_map<std::wstring_view, handler_function_t> handlers_map = {
// ...other handlers...
{L"awesome_toast", awesome_toast_handler}
};}

```
Note: since _background activation_ implies that your toast handler will be invoked in a separate process, you can't share data directly from within a handler and your PT process. Also, since PT is currently a Desktop Bridge app, _foreground activation_ is [handled the same as background](https://docs.microsoft.com/en-US/windows/uwp/design/shell/tiles-and-notifications/send-local-toast-desktop-cpp-wrl#foreground-vs-background-activation), therefore we don't make a dedicated API for it. You can read more on the rationale of the current design [here](https://github.com/microsoft/PowerToys/pull/1178#issue-368768337).
1 change: 1 addition & 0 deletions installer/MSIX/PackagingLayout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<File DestinationPath="modules\PowerRenameExt.dll" SourcePath="..\..\x64\Release\modules\PowerRenameExt.dll"/>
<File DestinationPath="modules\shortcut_guide.dll" SourcePath="..\..\x64\Release\modules\shortcut_guide.dll"/>
<File DestinationPath="modules\PowerRenameUWPUI.exe" SourcePath="..\..\x64\Release\PowerRenameUWPUI.exe"/>
<File DestinationPath="Notifications.dll" SourcePath="..\..\x64\Release\Notifications.dll"/>

<File DestinationPath="svgs\*" SourcePath="..\..\x64\Release\svgs\*"/>
<File DestinationPath="settings-html\**" SourcePath="..\..\x64\Release\settings-html\**"/>
Expand Down
13 changes: 13 additions & 0 deletions installer/MSIX/appxmanifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,20 @@
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<Extension Category="windows.backgroundTasks" EntryPoint="PowerToysNotifications.BackgroundHandler">
<BackgroundTasks>
<Task Type="general" />
</BackgroundTasks>
</Extension>
</Extensions>
</Application>
</Applications>
<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>Notifications.dll</Path>
<ActivatableClass ActivatableClassId="PowerToysNotifications.BackgroundHandler" ThreadingModel="both"/>
</InProcessServer>
</Extension>
</Extensions>
</Package>
2 changes: 2 additions & 0 deletions src/common/common.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<ClInclude Include="d2d_text.h" />
<ClInclude Include="d2d_window.h" />
<ClInclude Include="dpi_aware.h" />
<ClInclude Include="notifications.h" />
<ClInclude Include="window_helpers.h" />
<ClInclude Include="icon_helpers.h" />
<ClInclude Include="hwnd_data_cache.h" />
Expand Down Expand Up @@ -127,6 +128,7 @@
<ClCompile Include="hwnd_data_cache.cpp" />
<ClCompile Include="json.cpp" />
<ClCompile Include="monitors.cpp" />
<ClCompile Include="notifications.cpp" />
<ClCompile Include="on_thread_executor.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
Expand Down
6 changes: 6 additions & 0 deletions src/common/common.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
<ClInclude Include="window_helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="notifications.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="d2d_svg.cpp">
Expand Down Expand Up @@ -144,5 +147,8 @@
<ClCompile Include="window_helpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="notifications.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
89 changes: 89 additions & 0 deletions src/common/notifications.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "pch.h"
#include "notifications.h"

#include <unknwn.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.ApplicationModel.Background.h>

using namespace winrt::Windows::ApplicationModel::Background;
using winrt::Windows::Data::Xml::Dom::XmlDocument;
using winrt::Windows::UI::Notifications::ToastNotification;
using winrt::Windows::UI::Notifications::ToastNotificationManager;

namespace
{
constexpr std::wstring_view TASK_NAME = L"PowerToysBackgroundNotificationsHandler";
constexpr std::wstring_view TASK_ENTRYPOINT = L"PowerToysNotifications.BackgroundHandler";
constexpr std::wstring_view APPLICATION_ID = L"PowerToys";
}

void notifications::register_background_toast_handler()
{
try
{
// Re-request access to clean up from previous PowerToys installations
BackgroundExecutionManager::RemoveAccess();
BackgroundExecutionManager::RequestAccessAsync().get();

BackgroundTaskBuilder builder;
ToastNotificationActionTrigger trigger{ APPLICATION_ID };
builder.SetTrigger(trigger);
builder.TaskEntryPoint(TASK_ENTRYPOINT);
builder.Name(TASK_NAME);

const auto tasks = BackgroundTaskRegistration::AllTasks();
const bool already_registered = std::any_of(begin(tasks), end(tasks), [=](const auto& task) {
return task.Value().Name() == TASK_NAME;
});
if (already_registered)
{
return;
}
(void)builder.Register();
}
catch (...)
{
// Couldn't register the background task, nothing we can do
}
}

void notifications::show_toast(std::wstring_view message)
{
// The toast won't be actually activated in the background, since it doesn't have any buttons
show_toast_background_activated(message, {}, {});
}

void notifications::show_toast_background_activated(std::wstring_view message, std::wstring_view background_handler_id, std::vector<std::wstring_view> button_labels)
{
// DO NOT LOCALIZE any string in this function, because they're XML tags and a subject to
// https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-xml-schema

std::wstring toast_xml;
toast_xml.reserve(1024);
toast_xml += LR"(<?xml version="1.0"?><toast><visual><binding template="ToastGeneric"><text>PowerToys</text><text>)";
toast_xml += message;
toast_xml += L"</text></binding></visual><actions>";

for (size_t i = 0; i < size(button_labels); ++i)
{
toast_xml += LR"(<action activationType="background" arguments=")";
toast_xml += L"button_id=" + std::to_wstring(i); // pass the button ID
toast_xml += L"&amp;handler=";
toast_xml += background_handler_id;
toast_xml += LR"(" content=")";
toast_xml += button_labels[i];
toast_xml += LR"("/>)";
}
toast_xml += L"</actions></toast>";

XmlDocument toast_xml_doc;
toast_xml_doc.LoadXml(toast_xml);
ToastNotification notification{ toast_xml_doc };

const auto notifier = ToastNotificationManager::ToastNotificationManager::CreateToastNotifier();
notifier.Show(notification);
}
13 changes: 13 additions & 0 deletions src/common/notifications.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <string_view>
#include <vector>

namespace notifications
{
void register_background_toast_handler();

// Make sure your plaintext_message argument is properly XML-escaped
void show_toast(std::wstring_view plaintext_message);
void show_toast_background_activated(std::wstring_view plaintext_message, std::wstring_view background_handler_id, std::vector<std::wstring_view> plaintext_button_labels);
}
2 changes: 2 additions & 0 deletions src/common/notifications/cpp.hint
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#define NOTIFICATIONSDLL_API __declspec(dllexport)
#define NOTIFICATIONSDLL_API __declspec(dllimport)
26 changes: 26 additions & 0 deletions src/common/notifications/dllmain.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "pch.h"

STDAPI DllRegisterServer()
{
return S_OK;
}

STDAPI DllUnregisterServer()
{
return S_OK;
}

BOOL APIENTRY DllMain(HMODULE,
DWORD ul_reason_for_call,
LPVOID)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
8 changes: 8 additions & 0 deletions src/common/notifications/framework.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>

#include <winrt/Windows.ApplicationModel.Background.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Notifications.h>
5 changes: 5 additions & 0 deletions src/common/notifications/notifications.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
Loading

0 comments on commit e9e993e

Please sign in to comment.