mirror of
https://github.com/LANCommander/Notify.NET.git
synced 2026-08-01 03:08:24 -04:00
Update README for docs
This commit is contained in:
parent
8d412ede18
commit
1e81bcc7d9
1 changed files with 294 additions and 0 deletions
294
README.md
294
README.md
|
|
@ -276,6 +276,268 @@ cleanup on macOS).
|
|||
|
||||
---
|
||||
|
||||
## Taskbar progress
|
||||
|
||||
`ITaskbarProgressService` drives the progress indicator on the application's taskbar button
|
||||
(Windows), launcher entry (Linux) or Dock tile (macOS) — the same green/red bar Windows
|
||||
Explorer shows during a file copy. Use it to surface the progress of a long-running
|
||||
operation without a custom UI.
|
||||
|
||||
| Platform | Backend | Requirement |
|
||||
|----------|---------|-------------|
|
||||
| Windows | `ITaskbarList3` | A top-level window handle (defaults to the console window) |
|
||||
| Linux | Unity LauncherEntry D-Bus API (KDE Plasma, Unity, Dash-to-Dock, Plank, Latte) | A `.desktop` file whose id is supplied via `DesktopFileId` |
|
||||
| macOS | `NSProgressIndicator` drawn on the Dock tile | A bundled GUI app that owns a Dock tile |
|
||||
|
||||
If the indicator is unavailable on the current platform, `IsSupported` is `false` and all
|
||||
methods are silent no-ops.
|
||||
|
||||
### Creating the service
|
||||
|
||||
```csharp
|
||||
// Direct (no DI container)
|
||||
using var progress = ServiceCollectionExtensions.CreateTaskbarProgressService(opts =>
|
||||
{
|
||||
opts.DesktopFileId = "com.example.MyApp"; // Linux: the app's .desktop file id
|
||||
});
|
||||
|
||||
// With Microsoft.Extensions.DependencyInjection
|
||||
services.AddTaskbarProgress(opts =>
|
||||
{
|
||||
opts.DesktopFileId = "com.example.MyApp";
|
||||
});
|
||||
```
|
||||
|
||||
### Reporting progress
|
||||
|
||||
```csharp
|
||||
if (!progress.IsSupported)
|
||||
return;
|
||||
|
||||
// Determinate progress, by fraction (0.0–1.0, clamped)…
|
||||
progress.SetProgress(0.25);
|
||||
|
||||
// …or by completed / total counts.
|
||||
for (ulong i = 0; i <= total; i++)
|
||||
{
|
||||
DoWork(i);
|
||||
progress.SetProgress(i, total); // total must be greater than zero
|
||||
}
|
||||
|
||||
// Clear the indicator when finished.
|
||||
progress.SetState(TaskbarProgressState.None);
|
||||
```
|
||||
|
||||
Calling either `SetProgress` overload switches the indicator to the `Normal` state, unless
|
||||
it is currently in the `Error` or `Paused` state (those are preserved so a paused/failed
|
||||
operation keeps its colour while its value updates).
|
||||
|
||||
### States
|
||||
|
||||
```csharp
|
||||
progress.SetState(TaskbarProgressState.Indeterminate); // work of unknown length
|
||||
progress.SetState(TaskbarProgressState.Paused); // operation paused
|
||||
progress.SetState(TaskbarProgressState.Error); // operation failed
|
||||
progress.SetState(TaskbarProgressState.None); // clear the indicator
|
||||
```
|
||||
|
||||
| State | Windows | Linux | macOS |
|
||||
|-------|---------|-------|-------|
|
||||
| `None` | No bar | No bar | No bar |
|
||||
| `Indeterminate` | Pulsing marquee bar | Falls back to a 0% bar | Animated bar |
|
||||
| `Normal` | Green bar | Bar at the current value | Bar at the current value |
|
||||
| `Paused` | Yellow bar | Same as `Normal` | Same as `Normal` |
|
||||
| `Error` | Red bar | Launcher entry flagged "urgent" | Same as `Normal` |
|
||||
|
||||
### Targeting a window (Windows)
|
||||
|
||||
By default the Windows backend targets the console window (`GetConsoleWindow()`). For a
|
||||
WPF/WinForms app, point it at your main window's HWND so the bar appears on the right
|
||||
taskbar button. This is a no-op on Linux and macOS.
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
var hwnd = new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle;
|
||||
progress.SetWindow(hwnd);
|
||||
|
||||
// WinForms
|
||||
progress.SetWindow(form.Handle);
|
||||
|
||||
// Revert to the console window
|
||||
progress.SetWindow(IntPtr.Zero);
|
||||
```
|
||||
|
||||
### ITaskbarProgressService interface
|
||||
|
||||
```csharp
|
||||
public interface ITaskbarProgressService : IDisposable
|
||||
{
|
||||
// False if a progress indicator is unavailable on this platform.
|
||||
bool IsSupported { get; }
|
||||
|
||||
// Sets the visual state without changing the value (None clears it).
|
||||
void SetState(TaskbarProgressState state);
|
||||
|
||||
// Sets the value and switches to Normal (Error/Paused are preserved).
|
||||
void SetProgress(ulong completed, ulong total); // total must be > 0
|
||||
void SetProgress(double fraction); // 0.0–1.0, clamped
|
||||
|
||||
// Windows only: target a specific top-level window (Zero reverts to the console window).
|
||||
void SetWindow(IntPtr windowHandle);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Jump lists
|
||||
|
||||
A *jump list* is the menu of quick action shortcuts attached to an application's taskbar
|
||||
button (Windows), launcher icon (Linux) or Dock icon (macOS). Notify.NET exposes this
|
||||
through `IJumpListService`, which presents a single, uniform live-callback API across all
|
||||
three platforms: when the user clicks a task, your already-running process receives an
|
||||
`IJumpListHandler.OnTaskActivated(taskId)` call.
|
||||
|
||||
| Platform | Backend | Activation model |
|
||||
|----------|---------|------------------|
|
||||
| Windows | Shell `ICustomDestinationList` "user tasks" (Windows 7+) | Relaunch + single-instance forwarding |
|
||||
| Linux | freedesktop.org Desktop Actions in the app's `.desktop` file (GNOME, KDE, Unity, …) | Relaunch + single-instance forwarding |
|
||||
| macOS | Dock menu via the application delegate (bundled GUI app only) | Live, in-process — no relaunch |
|
||||
|
||||
On Windows and Linux a clicked task fundamentally relaunches the executable with a hidden
|
||||
`--notify-jumplist <id>` argument. Notify.NET bundles a single-instance channel (a named
|
||||
mutex plus a named pipe) that forwards the id to the running primary instance, so the
|
||||
handler always fires live — uniform with macOS's natively-live Dock menu.
|
||||
|
||||
Nothing is registered and no mutex, pipe or OS entry is created until you call `SetTasks`
|
||||
or `SetHandler`, so applications that do not use jump lists incur zero overhead.
|
||||
|
||||
### Creating the service
|
||||
|
||||
```csharp
|
||||
// Direct (no DI container)
|
||||
using var jumpList = ServiceCollectionExtensions.CreateJumpListService(opts =>
|
||||
{
|
||||
opts.AppName = "My App";
|
||||
opts.AppUserModelId = "MyCompany.MyApp"; // Windows: must match the notification AUMI
|
||||
opts.DesktopFileId = "com.example.MyApp"; // Linux: the app's .desktop file id
|
||||
});
|
||||
|
||||
// With Microsoft.Extensions.DependencyInjection
|
||||
services.AddJumpList(opts =>
|
||||
{
|
||||
opts.AppName = "My App";
|
||||
opts.AppUserModelId = "MyCompany.MyApp";
|
||||
opts.DesktopFileId = "com.example.MyApp";
|
||||
});
|
||||
```
|
||||
|
||||
`CreateJumpListService` / `AddJumpList` select the correct backend for the current OS.
|
||||
On unsupported platforms they return a no-op service where `IsSupported` is `false`.
|
||||
|
||||
### Wiring up activation
|
||||
|
||||
On Windows and Linux, call `TryHandleActivation` at the very top of `Main`, before any UI
|
||||
is shown. If this launch is a forwarded jump-list click, it returns `true` and the process
|
||||
should exit immediately. Then attach a handler and register the tasks — the first call to
|
||||
`SetTasks` / `SetHandler` makes this process the primary instance and starts the listener.
|
||||
|
||||
```csharp
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
using var jumpList = ServiceCollectionExtensions.CreateJumpListService(opts =>
|
||||
{
|
||||
opts.AppName = "My App";
|
||||
opts.AppUserModelId = "MyCompany.MyApp";
|
||||
opts.DesktopFileId = "com.example.MyApp";
|
||||
});
|
||||
|
||||
// Forward a jump-list click to the already-running instance, then exit.
|
||||
if (jumpList.TryHandleActivation(args))
|
||||
return 0;
|
||||
|
||||
jumpList.SetHandler(new MyJumpListHandler());
|
||||
jumpList.SetTasks(new[]
|
||||
{
|
||||
new JumpListTask("new-doc", "New Document"),
|
||||
new JumpListTask("open-last","Open Last File", description: "Reopen the most recent file"),
|
||||
new JumpListTask("settings", "Settings", iconPath: @"C:\Apps\MyApp\settings.ico"),
|
||||
});
|
||||
|
||||
RunApplication(); // your normal startup / message loop
|
||||
return 0;
|
||||
}
|
||||
|
||||
public sealed class MyJumpListHandler : IJumpListHandler
|
||||
{
|
||||
public void OnTaskActivated(string taskId)
|
||||
{
|
||||
// Fired on a background thread — marshal to your UI thread before touching UI.
|
||||
switch (taskId)
|
||||
{
|
||||
case "new-doc": CreateDocument(); break;
|
||||
case "open-last": OpenLastFile(); break;
|
||||
case "settings": ShowSettings(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the app was launched cold by a jump-list click (no primary instance was running), the
|
||||
activation is captured and replayed to the handler once one is set.
|
||||
|
||||
### JumpListTask
|
||||
|
||||
```csharp
|
||||
new JumpListTask(
|
||||
id: "open-last", // stable id passed back to OnTaskActivated (no whitespace)
|
||||
title: "Open Last File", // label shown in the menu
|
||||
description: "Reopen the most recent file", // tooltip (Windows); optional
|
||||
iconPath: @"C:\Apps\MyApp\recent.ico", // optional; defaults to the host exe icon
|
||||
iconIndex: 0); // icon index within iconPath (Windows)
|
||||
```
|
||||
|
||||
### Managing tasks
|
||||
|
||||
```csharp
|
||||
jumpList.SetTasks(tasks); // replace the current task set (empty sequence == ClearTasks)
|
||||
jumpList.ClearTasks(); // remove all tasks registered by this app
|
||||
jumpList.SetHandler(null); // detach the handler
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Purpose |
|
||||
|--------|---------|
|
||||
| `AppName` | Human-readable name; used if a minimal Linux `.desktop` file must be created. |
|
||||
| `AppUserModelId` | Windows — must match the AUMI used for notifications so the list attaches to the right taskbar button. |
|
||||
| `DesktopFileId` | Linux — the app's `.desktop` file id (with or without the `.desktop` suffix). Defaults to the process name. |
|
||||
| `ExecutablePath` | Windows/Linux — absolute path to relaunch on click. When null, the current process executable is used; pass an explicit path for framework-dependent `dotnet` apps where the auto-detected path may be the shared host. Ignored on macOS. |
|
||||
|
||||
### IJumpListService interface
|
||||
|
||||
```csharp
|
||||
public interface IJumpListService : IDisposable
|
||||
{
|
||||
// False if jump lists are unavailable on this platform; all methods become no-ops.
|
||||
bool IsSupported { get; }
|
||||
|
||||
// Registers the handler for OnTaskActivated events (also starts the listener).
|
||||
void SetHandler(IJumpListHandler? handler);
|
||||
|
||||
// Replaces the application's jump-list tasks (empty sequence clears them).
|
||||
void SetTasks(IEnumerable<JumpListTask> tasks);
|
||||
|
||||
// Removes all tasks registered by this application.
|
||||
void ClearTasks();
|
||||
|
||||
// Call once at the start of Main. Returns true if the launch was a forwarded
|
||||
// activation and the caller should exit immediately.
|
||||
bool TryHandleActivation(string[] args);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform notes
|
||||
|
||||
### Windows
|
||||
|
|
@ -290,6 +552,14 @@ cleanup on macOS).
|
|||
published alongside the executable.
|
||||
- Toast callbacks are delivered on a WinRT thread-pool thread, not the STA thread. The
|
||||
library handles this internally.
|
||||
- Jump lists use the shell `ICustomDestinationList` "user tasks" API (Windows 7+) — pure
|
||||
managed COM interop, no native DLL required. The jump list attaches to the taskbar button
|
||||
matching `AppUserModelId`, so it must be the same id used for notifications. The COM work
|
||||
runs on a dedicated STA thread the library creates lazily on first use.
|
||||
- Taskbar progress uses `ITaskbarList3` and needs a top-level window handle. It defaults to
|
||||
the console window (`GetConsoleWindow()`); call `SetWindow` with your WPF/WinForms main
|
||||
window HWND to move the bar onto that taskbar button. The COM work runs on its own lazily
|
||||
created STA thread.
|
||||
|
||||
### Linux
|
||||
|
||||
|
|
@ -314,6 +584,18 @@ is present.
|
|||
Image support via `gdk-pixbuf` requires `libgdk-pixbuf-2.0` to be installed, which is
|
||||
typically included as a dependency of `libnotify4`.
|
||||
|
||||
Taskbar progress uses the Unity LauncherEntry D-Bus API, honoured by KDE Plasma, Unity,
|
||||
Dash-to-Dock, Plank and Latte. It requires the app to ship (or have created) a `.desktop`
|
||||
file whose id is supplied via `DesktopFileId`; the launcher matches the entry by that id.
|
||||
Desktop environments without LauncherEntry support simply show no bar.
|
||||
|
||||
Jump lists are written as `Actions` into the application's `.desktop` file. If no installed
|
||||
`.desktop` file is found for `DesktopFileId`, a minimal one is created under
|
||||
`$XDG_DATA_HOME/applications` (default `~/.local/share/applications`). Writing the file is
|
||||
best-effort — a read-only or absent home directory will not crash the application. Each
|
||||
action's `Exec` relaunches the executable with the activation argument, which the bundled
|
||||
single-instance layer forwards to the running primary instance.
|
||||
|
||||
### macOS
|
||||
|
||||
- Requires macOS 10.14 (Mojave) or later.
|
||||
|
|
@ -335,6 +617,18 @@ typically included as a dependency of `libnotify4`.
|
|||
`OnDismissed` callback is not fired after the user activates a notification or clicks
|
||||
a button (unlike Windows, where WinToastLib always fires the dismissed event after any
|
||||
interaction).
|
||||
- Taskbar progress draws an `NSProgressIndicator` along the bottom of the **Dock tile**.
|
||||
This is only visible for a regular bundled GUI application that owns a Dock tile and has a
|
||||
running main loop; a bare console process has none, so the calls are harmless no-ops. The
|
||||
Dock cannot tint the bar, so `Paused` and `Error` render the same as `Normal`.
|
||||
- Jump-list tasks appear in the **Dock menu** (right-click / click-and-hold of the Dock
|
||||
icon) and fire `OnTaskActivated` live in-process — there is no relaunch, so
|
||||
`TryHandleActivation` always returns `false` on macOS. This is only effective for a
|
||||
regular bundled GUI application with a running main loop; a bare console process has no
|
||||
Dock menu and the calls are harmless no-ops. The wrapper supplies the menu via the
|
||||
application delegate's `applicationDockMenu:`, installing its own delegate if the app has
|
||||
none, or adding the method to the existing delegate without clobbering a Dock menu the app
|
||||
already provides.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue