Skip to content

Commit bf8e4d3

Browse files
committed
Extend Windows desktop APIs with dialogs, pickers and notifications
1 parent 67663fe commit bf8e4d3

55 files changed

Lines changed: 2151 additions & 25 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_subdirectory(examples/tray_icon_c_example)
3131
add_subdirectory(examples/url_opener_c_example)
3232
add_subdirectory(examples/window_c_example)
3333
add_subdirectory(examples/window_example)
34+
add_subdirectory(examples/desktop_features_example)
3435

3536
if(BUILD_TESTING)
3637
add_subdirectory(tests)

‎README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,6 @@ These bindings provide native system API access while preserving the library's c
8484
## Optional Windows UI backend
8585

8686
Build with `NATIVEAPI_ENABLE_WINUI3=ON` to use WinUI 3 menus and message dialogs
87-
automatically. With the option OFF, Windows uses the existing Win32 backend. See [build and usage instructions](docs/winui3.md).
87+
automatically, enable AppWindow title-bar customization and system notifications,
88+
and select the WinRT file/folder picker backend. With the option OFF, Windows uses
89+
the Win32 menu/dialog and IFileDialog picker backends. See [build and usage instructions](docs/winui3.md).

‎cmake/WinUI3.cmake‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ add_custom_target(nativeapi_winui3_projection DEPENDS "${_projection}/projection
4444
add_dependencies(nativeapi nativeapi_winui3_projection)
4545
target_sources(nativeapi PRIVATE platform/windows/menu_winui3_windows.cpp
4646
platform/windows/message_dialog_winui3_windows.cpp
47-
platform/windows/winui3_runtime_windows.cpp)
47+
platform/windows/winui3_runtime_windows.cpp
48+
platform/windows/window_winui3_windows.cpp)
4849
target_include_directories(nativeapi PRIVATE "${_projection}" "${NATIVEAPI_WINAPPSDK_DIR}/include")
4950
target_compile_definitions(nativeapi PRIVATE NATIVEAPI_ENABLE_WINUI3 NOMINMAX)
5051
target_link_libraries(nativeapi PRIVATE windowsapp)

‎docs/winui3.md‎

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Optional WinUI 3 backend on Windows
22

33
`NATIVEAPI_ENABLE_WINUI3=ON` selects WinUI 3 for new Windows menus and
4-
message dialogs. Menus use `Microsoft.UI.Xaml.Controls.MenuFlyout`; message
4+
message dialogs, plus Windows App SDK title-bar and notification integration. Menus use `Microsoft.UI.Xaml.Controls.MenuFlyout`; message
55
dialogs use `Microsoft.UI.Xaml.Controls.ContentDialog`. Both share a lazy XAML
66
runtime and use private XAML Islands, including in tray-only applications.
77
Future WinUI components should use this same option and runtime.
8+
File dialogs use the Windows system picker; they are not XAML controls.
89

910
With the option OFF (the default), menus and dialogs retain their Win32
1011
implementations. Public C++ and C interfaces stay platform independent.
@@ -95,8 +96,8 @@ No runtime switch or new API is required.
9596
message loop (normally `Application::Run()`). `Close()` dismisses it.
9697
- `Application` pumps messages until dismissal and temporarily disables the
9798
application's currently visible, enabled top-level windows.
98-
- `Window` disables the calling thread's active visible window. The public API
99-
has no explicit parent parameter yet; if no active window exists, the dialog
99+
- `Window` disables the window supplied by `SetParentWindow()`, or the calling
100+
thread's active visible window if no parent was supplied. Without either, it
100101
opens without an owner. It never blocks other applications.
101102
- `SetTitle` and `SetMessage` update displayed content. Long messages scroll.
102103
The OK button, title-bar close, and `Close()` dismiss the dialog. Previously
@@ -156,3 +157,99 @@ destruction while open, and sharing the runtime with a menu:
156157
```powershell
157158
./build/menu-modern/tests/Debug/winui3_dialog_test.exe
158159
```
160+
161+
## Extended desktop features
162+
163+
Build `desktop_features_example` and run it with one of these arguments:
164+
165+
| Argument | Example |
166+
|---|---|
167+
| `dialog` (default) | Save/Discard/Cancel buttons, editable input, checkbox, progress |
168+
| `window` | Existing HWND with AppWindow title-bar colors and DWM Mica |
169+
| `open` / `multiple` | Select one or several files |
170+
| `save` | Select a save destination |
171+
| `folder` | Select a folder |
172+
| `notify` | Deliver a system notification; clicking it prints activation arguments and exits |
173+
174+
```powershell
175+
cmake --build build/menu-modern --config Debug --target desktop_features_example desktop_features_test
176+
./build/menu-modern/examples/desktop_features_example/Debug/desktop_features_example.exe dialog
177+
```
178+
179+
`MessageDialog::IsExtendedSupported()` reports whether the extended controls are
180+
compiled in. Configure buttons, default button and parent before opening. Input,
181+
checkbox and progress can be updated while open. `SetProgress(-2)` hides progress,
182+
`-1` is indeterminate, and values from 0 to 1 are determinate. Invalid values are
183+
rejected. After dismissal, read `GetResult()`, `GetInputText()` and
184+
`IsCheckboxChecked()`. Escape, close button and programmatic close return `Close`;
185+
`None` means no result yet. On a modeless dialog, wait until `IsOpen()` is false.
186+
Use all operations, including destruction, on the UI thread.
187+
188+
`Window::SetTitleBarColors()` and `ResetTitleBarColors()` operate on the existing
189+
HWND through AppWindow; they do not replace an embedding framework's content.
190+
Title-bar visibility uses OverlappedPresenter. Mica/Acrylic continue to use DWM,
191+
so availability depends on the Windows version. Opaque content painted by a host
192+
framework can cover the backdrop. The native HWND lifetime now controls removal
193+
from WindowRegistry; destroying a temporary wrapper does not unregister the HWND.
194+
195+
`FileDialog` supports `OpenFile`, `OpenFiles`, `SaveFile`, and `SelectFolder`.
196+
`Open()` blocks with message dispatch and returns true for acceptance **or user
197+
cancellation**; distinguish them with `GetResult()`. `GetPaths()` returns UTF-8
198+
filesystem paths. Failures return false with `GetLastError()`. Configure an explicit
199+
parent with `SetParentWindow()` when one exists. Without a visible parent, a small
200+
reusable owner is hosted on the calling STA. Keep the FileDialog alive until Open
201+
returns. Only Window modality is supported; other modality values fail explicitly.
202+
203+
With WinUI3 enabled, the implementation uses the OS
204+
[Windows.Storage.Pickers APIs with HWND interop](https://learn.microsoft.com/en-us/windows/apps/develop/ui/display-ui-objects),
205+
which work with the pinned App SDK 1.6. It does **not** use the newer
206+
Microsoft.Windows.Storage.Pickers namespace introduced in App SDK 1.8. WinRT file
207+
pickers inherit the OS restrictions (including elevated-process restrictions).
208+
The Win32 build uses IFileDialog, including native multiselect and folder picking.
209+
SaveFile initially filters `.txt`; supply extensions explicitly for other formats.
210+
Clearing the extension list uses `.txt` in the WinRT backend and all files in Win32. The WinRT save picker can create an empty file
211+
when the selection is confirmed; callers should then write its contents.
212+
213+
`NotificationManager` uses Microsoft.Windows.AppNotifications, with optional
214+
notification buttons and `NotificationActivatedEvent`. Subscribe before
215+
`Initialize()` at every app startup, including notification-triggered launches.
216+
Pump the normal application UI loop for activation callbacks. Initialize, Show,
217+
Remove and Shutdown belong on the main STA. Call Shutdown before stopping the loop;
218+
it unregisters this process but preserves the OS registration needed to relaunch
219+
it from delivered notifications. Registration is owned by this manager: applications
220+
already managing AppNotificationManager themselves should not initialize a second
221+
registration through this library. Tags must be 1–16 ASCII letters/digits/`_`/`-`.
222+
Show replaces notifications with the same tag in the library's `nativeapi` group.
223+
Success means the OS accepted the notification, not that a banner was visible:
224+
Windows notification settings and Do Not Disturb may suppress it. Both the runtime
225+
framework and its notification/Singleton components must be installed.
226+
227+
### Platform support
228+
229+
| Feature | Windows, WinUI3 ON | Windows, OFF | Other platforms |
230+
|---|---|---|---|
231+
| Extended MessageDialog | Supported | Setters return false; basic dialog retained | Setters return false; existing basic implementation retained |
232+
| Title-bar colors | AppWindow | Returns false | Returns false |
233+
| FileDialog | System WinRT picker | IFileDialog | IsSupported returns false |
234+
| NotificationManager | App SDK notifications | IsSupported returns false | IsSupported returns false |
235+
236+
All six platform directories define the new module entry points. C ABI files are
237+
generated; the generator's header catalog in the sibling workspace now includes
238+
`file_dialog.h` and `notification_manager.h`. This checkout is outside workspace/core,
239+
so generation is driven through the workspace codegen wrapper with CORE pointing
240+
at this checkout; downstream submodules are uninitialized and are not synced here.
241+
242+
### Additional verification
243+
244+
The default `desktop_features_test` checks option validation and C handle lifetime
245+
without displaying UI. Explicit desktop integration modes are:
246+
247+
```powershell
248+
./build/menu-modern/tests/Debug/desktop_features_test.exe --ui
249+
./build/menu-modern/tests/Debug/desktop_features_test.exe --pickers
250+
./build/menu-modern/tests/Debug/desktop_features_test.exe --notify
251+
```
252+
253+
These exercise extended dialog state and live updates, HWND registration lifetime,
254+
AppWindow title-bar changes, Mica, cancellation of all four picker modes, and
255+
notification submission/removal. `--notify` briefly sends a test notification.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
add_executable(desktop_features_example main.cpp)
2+
target_link_libraries(desktop_features_example PRIVATE nativeapi)
3+
if(NATIVEAPI_ENABLE_WINUI3)
4+
target_sources(desktop_features_example PRIVATE ../../cmake/winui3-example.manifest)
5+
nativeapi_deploy_winui3(desktop_features_example)
6+
endif()
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include <iostream>
2+
#include <string>
3+
#include "nativeapi.h"
4+
5+
using namespace nativeapi;
6+
int main(int argc, char** argv) {
7+
auto& app = Application::GetInstance();
8+
const std::string mode = argc > 1 ? argv[1] : "dialog";
9+
if (mode == "notify") {
10+
auto& notifications = NotificationManager::GetInstance();
11+
notifications.AddListener<NotificationActivatedEvent>([&](const auto& event) {
12+
std::cout << "Activated: " << event.GetArgument() << std::endl;
13+
notifications.Remove("demo");
14+
app.Quit();
15+
});
16+
if (!notifications.Initialize() ||
17+
!notifications.Show("nativeapi", "System notification example", "demo", "Open")) {
18+
std::cerr << notifications.GetLastError() << std::endl;
19+
notifications.Shutdown();
20+
return 1;
21+
}
22+
std::cout << "Click the notification or its Open button to finish." << std::endl;
23+
const int result = app.Run();
24+
notifications.Shutdown();
25+
return result;
26+
}
27+
auto window = std::make_shared<Window>();
28+
window->SetTitle("nativeapi desktop features");
29+
window->SetSize({720, 560}, false);
30+
window->SetVisualEffect(VisualEffect::Mica);
31+
window->SetTitleBarColors(Color::FromRGBA(30, 65, 110), Color::White);
32+
window->Show();
33+
if (mode == "window") return app.Run();
34+
if (mode == "dialog") {
35+
if (!MessageDialog::IsExtendedSupported()) {
36+
std::cerr << "Build with NATIVEAPI_ENABLE_WINUI3=ON for extended dialogs.\n";
37+
return 1;
38+
}
39+
MessageDialog dialog("Extended MessageDialog", "Enter a name and choose an action.");
40+
dialog.SetParentWindow(window);
41+
dialog.SetModality(DialogModality::Window);
42+
dialog.SetButtons("Save", "Discard", "Cancel");
43+
dialog.SetDefaultButton(MessageDialogResult::Primary);
44+
dialog.SetInputEnabled(true);
45+
dialog.SetInputText("nativeapi");
46+
dialog.SetCheckbox("Remember my choice", true);
47+
dialog.SetProgress(0.65);
48+
if (!dialog.Open()) return 1;
49+
std::cout << "Result=" << static_cast<int>(dialog.GetResult())
50+
<< " input=" << dialog.GetInputText()
51+
<< " checked=" << dialog.IsCheckboxChecked() << std::endl;
52+
return 0;
53+
}
54+
FileDialogMode picker_mode;
55+
if (mode == "open") picker_mode = FileDialogMode::OpenFile;
56+
else if (mode == "multiple") picker_mode = FileDialogMode::OpenFiles;
57+
else if (mode == "save") picker_mode = FileDialogMode::SaveFile;
58+
else if (mode == "folder") picker_mode = FileDialogMode::SelectFolder;
59+
else { std::cerr << "Modes: dialog, window, open, multiple, save, folder, notify\n"; return 1; }
60+
FileDialog picker(picker_mode);
61+
picker.SetParentWindow(window);
62+
if (picker_mode != FileDialogMode::SelectFolder) picker.SetFileTypes({".txt", ".md"});
63+
picker.SetSuggestedFileName("nativeapi.txt");
64+
if (!picker.Open()) { std::cerr << picker.GetLastError() << std::endl; return 1; }
65+
std::cout << "Picker result=" << static_cast<int>(picker.GetResult()) << '\n';
66+
for (const auto& path : picker.GetPaths()) std::cout << path << '\n';
67+
}

‎include/nativeapi.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "../src/dialog.h"
1313
#include "../src/display.h"
1414
#include "../src/display_manager.h"
15+
#include "../src/file_dialog.h"
1516
#include "../src/foundation/color.h"
1617
#include "../src/foundation/geometry.h"
1718
#include "../src/foundation/keyboard.h"
@@ -20,6 +21,7 @@
2021
#include "../src/launch_at_login.h"
2122
#include "../src/menu.h"
2223
#include "../src/message_dialog.h"
24+
#include "../src/notification_manager.h"
2325
#include "../src/placement.h"
2426
#include "../src/positioning_strategy.h"
2527
#include "../src/preferences.h"
@@ -43,13 +45,15 @@
4345
#include "../src/capi/dialog_c.h"
4446
#include "../src/capi/display_c.h"
4547
#include "../src/capi/display_manager_c.h"
48+
#include "../src/capi/file_dialog_c.h"
4649
#include "../src/capi/geometry_c.h"
4750
#include "../src/capi/image_c.h"
4851
#include "../src/capi/keyboard_c.h"
4952
#include "../src/capi/keyboard_monitor_c.h"
5053
#include "../src/capi/launch_at_login_c.h"
5154
#include "../src/capi/menu_c.h"
5255
#include "../src/capi/message_dialog_c.h"
56+
#include "../src/capi/notification_manager_c.h"
5357
#include "../src/capi/placement_c.h"
5458
#include "../src/capi/positioning_strategy_c.h"
5559
#include "../src/capi/preferences_c.h"

‎src/CMakeLists.txt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "OHOS")
4141
file(GLOB PLATFORM_SOURCES "platform/ohos/*.cpp")
4242
elseif(WIN32)
4343
file(GLOB PLATFORM_SOURCES "platform/windows/*.cpp")
44-
list(FILTER PLATFORM_SOURCES EXCLUDE REGEX "(menu_winui3|message_dialog_winui3|winui3_runtime)_windows\\.cpp$")
44+
list(FILTER PLATFORM_SOURCES EXCLUDE REGEX "(menu_winui3|message_dialog_winui3|window_winui3|winui3_runtime)_windows\\.cpp$")
4545
if(NATIVEAPI_ENABLE_WINUI3)
4646
list(FILTER PLATFORM_SOURCES EXCLUDE REGEX "message_dialog_windows\\.cpp$")
4747
endif()
@@ -100,5 +100,5 @@ elseif(APPLE)
100100
elseif(CMAKE_SYSTEM_NAME STREQUAL "OHOS")
101101
target_link_libraries(nativeapi PUBLIC hilog_ndk.z)
102102
elseif(WIN32)
103-
target_link_libraries(nativeapi PUBLIC user32 shell32 dwmapi gdiplus crypt32 advapi32 version)
103+
target_link_libraries(nativeapi PUBLIC user32 shell32 comctl32 dwmapi gdiplus crypt32 advapi32 version)
104104
endif ()

0 commit comments

Comments
 (0)