Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2026-08-24

### Added

- **Reactive Watch API** — subscribe to a kind of device information and receive
a callback whenever the value changes, instead of polling a getter yourself.
The current value is delivered immediately, then on every change.
- `watchBattery`, `watchNetwork`, `watchStorage`, `watchDisplay`, and
`watchDevice`, each returning a `Promise<UnwatchFn>`.
- `WatchOptions` (`intervalMs`) and `UnwatchFn` types, exported for consumers.
- `start_watching` / `stop_watching` commands, with matching
`allow-start-watching` and `allow-stop-watching` permissions (included in
`device-info:default`).
- **Native, event-driven monitors on macOS** — battery via IOKit power
notifications, display via Core Graphics reconfiguration callbacks, and
network via SystemConfiguration reachability. The CPU stays idle between
changes and updates arrive the instant the OS reports them.
- **Change-detecting polling fallback** for kinds/platforms without a native
event source (`storage` and `device` everywhere; all kinds off macOS). Events
are emitted only when the value actually changes.
- Reference-counted monitors: subscribers to the same kind share a single
monitor that starts on the first subscriber and is torn down on the last.
- Documentation for the watch API (API reference page, guide section, examples)
and unit tests covering the monitor lifecycle for every kind.

### Performance

- Per-kind polling defaults and floors so expensive getters are not polled
aggressively: `device` defaults to 60000 ms (min 10000 ms), `storage` to
10000 ms (min 1000 ms), others to 2000 ms (min 250 ms). Notably, `device`
(which shells out to `system_profiler` on macOS and rarely changes) is no
longer read every couple of seconds.
- The poller now sleeps for the full interval on a condition variable and wakes
immediately on stop, instead of busy-waking every 100 ms — letting the CPU
idle and cooperating with power-saving.
- Change events are emitted by reference, avoiding a deep clone of each snapshot.

### Fixed

- Removed a run-loop teardown race on macOS that could hang `stop_watching`
indefinitely when a watcher was stopped just after starting; stop is now
signalled through the run loop itself and is race-free.
- `stop_watching` no longer holds the shared watcher lock while tearing down a
monitor, so a slow teardown can't block subscribe/unsubscribe for other kinds.
- Contained panics inside the macOS OS callbacks so they can no longer unwind
across the C ABI boundary and abort the process.
- The example app surfaces watcher start failures instead of leaving an
unhandled promise rejection.

## [1.0.1]

- Documentation updates, crates.io metadata, and a Tests CI badge.

## [1.0.0]

- Initial release: `getDeviceInfo`, `getBatteryInfo`, `getNetworkInfo`,
`getStorageInfo`, and `getDisplayInfo` across Windows, macOS, Linux, iOS, and
Android.

[1.1.0]: https://github.com/edisdev/tauri-plugin-device-info/releases/tag/v1.1.0
[1.0.1]: https://github.com/edisdev/tauri-plugin-device-info/releases/tag/v1.0.1
[1.0.0]: https://github.com/edisdev/tauri-plugin-device-info/releases/tag/v1.0.0
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-device-info"
version = "1.0.1"
version = "1.1.0"
authors = ["edisdev <edisdev14@gmail.com>"]
description = "A Tauri plugin to access device information."
license = "MIT"
Expand Down Expand Up @@ -38,3 +38,10 @@ battery = "0.7.8"

[build-dependencies]
tauri-plugin = { version = "2.5.2", features = ["build"] }

# Tauri's `test` feature (the mock runtime) fails to load on Windows CI with
# STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139), so enable it only off Windows. The
# mock-based watcher tests are gated to match; the pure-logic tests still run
# on every platform.
[target.'cfg(not(target_os = "windows"))'.dev-dependencies]
tauri = { version = "2.7.0", features = ["test"] }
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ A comprehensive Tauri plugin to access device information including Battery, Net
- 💾 **Storage** — total/free space and storage type
- 🖥️ **Display** — resolution, scale factor, and refresh rate
- 🆔 **Device** — manufacturer, model, serial, and device name
- 🔔 **Reactive** — `watch*` APIs push updates on change (native event-driven on macOS, polling fallback elsewhere)
- 📱 **Truly cross-platform** — Windows, macOS, Linux, iOS, and Android
- 🛡️ **Type-safe** — full TypeScript types and granular Tauri permissions
- ✅ **Tested** — unit-tested core with CI across all desktop platforms
Expand Down Expand Up @@ -193,6 +194,49 @@ console.log(display);
}
```

### Reactive Watch API

Subscribe to a kind and get a callback whenever the value changes, instead of
polling a getter yourself. The current value is delivered immediately, then on
every change. On macOS these are native and event-driven (IOKit / Core Graphics
/ SystemConfiguration); elsewhere they fall back to change-detecting polling.

```typescript
import {
watchBattery,
watchNetwork,
watchStorage,
watchDisplay,
watchDevice,
} from "tauri-plugin-device-info-api";
import type { UnwatchFn } from "tauri-plugin-device-info-api";

// Called immediately with the current value, then on every change.
const unwatch = await watchBattery((battery) => {
console.log(`Battery: ${battery.level}% (charging: ${battery.isCharging})`);
});

// Stop watching and remove the listener when you're done.
await unwatch();
```

Cleaning up several watchers at once (e.g. on component teardown):

```typescript
const unwatchers: UnwatchFn[] = [];
unwatchers.push(await watchNetwork((info) => console.log(info.networkType)));
unwatchers.push(await watchDisplay((info) => console.log(info.width, info.height)));

// Later:
await Promise.all(unwatchers.map((unwatch) => unwatch()));
```

`watch*` accepts an optional `{ intervalMs }` that applies **only to polled
kinds** (native kinds ignore it). Defaults and minimums are per kind: `device`
defaults to 60000 ms (floored at 10000 ms), `storage` to 10000 ms (floored at
1000 ms), and others to 2000 ms (floored at 250 ms). Subscribers to the same
kind share one monitor, so only the first subscriber's interval takes effect.

## API Reference

### getDeviceInfo()
Expand Down Expand Up @@ -249,6 +293,26 @@ Returns display/screen information.
| `scaleFactor` | `number?` | Display scale factor (e.g., 2.0 for Retina) |
| `refreshRate` | `number?` | Screen refresh rate in Hz |

### watch\*(callback, options?)

Subscribes to a kind and invokes `callback` with the current value immediately,
then on every change. Returns a `Promise<UnwatchFn>`; call the returned function
to stop watching and remove the listener.

| Function | Callback payload |
| ----------------- | -------------------- |
| `watchDevice()` | `DeviceInfoResponse` |
| `watchBattery()` | `BatteryInfo` |
| `watchNetwork()` | `NetworkInfo` |
| `watchStorage()` | `StorageInfo` |
| `watchDisplay()` | `DisplayInfo` |

**Options**

| Field | Type | Description |
| ------------ | --------- | -------------------------------------------------------------------------------------------- |
| `intervalMs` | `number?` | Polling interval for polled kinds only (native kinds ignore it). Per-kind defaults/minimums. |

## TypeScript Types

All types are exported and can be imported:
Expand All @@ -260,6 +324,8 @@ import type {
NetworkInfo,
StorageInfo,
DisplayInfo,
WatchOptions,
UnwatchFn,
} from "tauri-plugin-device-info-api";
```

Expand Down Expand Up @@ -289,6 +355,8 @@ Add the required permissions in your `capabilities` configuration.
| `device-info:allow-get-network-info` | Allows getting network details |
| `device-info:allow-get-storage-info` | Allows getting storage info |
| `device-info:allow-get-display-info` | Allows getting display info |
| `device-info:allow-start-watching` | Allows starting a reactive watch |
| `device-info:allow-stop-watching` | Allows stopping a reactive watch |

### Individual Permissions Example

Expand Down Expand Up @@ -352,6 +420,10 @@ cargo clippy -- -D warnings
yarn tsc --noEmit
```

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for the release history and notable changes.

## Contributing

Contributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md)
Expand Down
2 changes: 2 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const COMMANDS: &[&str] = &[
"get_network_info",
"get_storage_info",
"get_display_info",
"start_watching",
"stop_watching",
];

fn main() {
Expand Down
3 changes: 2 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export default defineConfig({
{ text: 'Battery Info', link: '/api/battery-info' },
{ text: 'Network Info', link: '/api/network-info' },
{ text: 'Storage Info', link: '/api/storage-info' },
{ text: 'Display Info', link: '/api/display-info' }
{ text: 'Display Info', link: '/api/display-info' },
{ text: 'Reactive Watch', link: '/api/watch' }
]
},
{
Expand Down
17 changes: 14 additions & 3 deletions docs/api/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# API Reference

This plugin provides 5 main functions to access device information.
This plugin provides 5 getters to read device information, plus a
[reactive watch API](/api/watch) to subscribe to changes.

## Functions Overview

Expand All @@ -11,6 +12,7 @@ This plugin provides 5 main functions to access device information.
| [`getNetworkInfo()`](/api/network-info) | Network connection details |
| [`getStorageInfo()`](/api/storage-info) | Storage capacity information |
| [`getDisplayInfo()`](/api/display-info) | Display properties and capabilities |
| [`watch*()`](/api/watch) | Subscribe to changes for any of the above kinds |

## Quick Reference

Expand All @@ -20,7 +22,14 @@ import {
getBatteryInfo, // → BatteryInfo
getNetworkInfo, // → NetworkInfo
getStorageInfo, // → StorageInfo
getDisplayInfo // → DisplayInfo
getDisplayInfo, // → DisplayInfo

// Reactive watch API → Promise<UnwatchFn>
watchDevice,
watchBattery,
watchNetwork,
watchStorage,
watchDisplay
} from 'tauri-plugin-device-info-api';
```

Expand All @@ -34,7 +43,9 @@ import type {
BatteryInfo,
NetworkInfo,
StorageInfo,
DisplayInfo
DisplayInfo,
WatchOptions,
UnwatchFn
} from 'tauri-plugin-device-info-api';
```

Expand Down
Loading
Loading