@domicile-desktop/system-bluetooth 0.0.0-alpha-44d0983a58ce → 0.0.0-alpha-56dd6c187f4e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/dist/bluetooth.d.ts +27 -10
- package/dist/bluetooth.js +20 -17
- package/dist/hardware.d.ts +6 -3
- package/dist/hardware.js +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,22 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
BlueZ for a Domicile shell: each adapter and whether it is powered or
|
|
4
4
|
scanning, the devices it knows, and the requests that change them. Built on
|
|
5
|
-
[`@domicile-desktop/sdk
|
|
5
|
+
the desktop's `dbus` namespace ([`@domicile-desktop/sdk`](../chrome-sdk/README.md)).
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import { system } from "@domicile-desktop/sdk/system";
|
|
11
10
|
import { setPowered, watchBluetooth } from "@domicile-desktop/system-bluetooth/bluetooth";
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
const stop = watchBluetooth(host, (bluetooth) => {
|
|
12
|
+
const stop = watchBluetooth(domicile, (bluetooth) => {
|
|
15
13
|
bluetooth.match({
|
|
16
14
|
Err: () => hide(),
|
|
17
15
|
Ok: ({ adapters, devices }) => show(adapters, devices),
|
|
18
16
|
});
|
|
19
17
|
});
|
|
20
|
-
await setPowered(
|
|
18
|
+
await setPowered(domicile, "/org/bluez/hci0", false);
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
- Reads `GetManagedObjects` once, then again when an object comes or goes or
|
package/dist/bluetooth.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Result } from "@cprussin/option-result";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Dbus } from "@domicile-desktop/sdk/desktop/dbus";
|
|
3
|
+
import type { SystemError } from "@domicile-desktop/sdk/system";
|
|
3
4
|
/** A controller, such as `/org/bluez/hci0`. */
|
|
4
5
|
export type Adapter = {
|
|
5
6
|
path: string;
|
|
@@ -28,7 +29,9 @@ export type Bluetooth = {
|
|
|
28
29
|
devices: Device[];
|
|
29
30
|
};
|
|
30
31
|
/** The calls this library makes. */
|
|
31
|
-
export type
|
|
32
|
+
export type BluetoothDesktop = {
|
|
33
|
+
readonly dbus: Pick<Dbus, "call" | "match">;
|
|
34
|
+
};
|
|
32
35
|
/**
|
|
33
36
|
* Calls `onBluetooth` with the adapters and connected devices now and after
|
|
34
37
|
* each change, and returns a function that stops watching.
|
|
@@ -36,19 +39,33 @@ export type BluetoothSystem = Pick<System, "dbusCall" | "dbusMatch">;
|
|
|
36
39
|
* An `Err` is a D-Bus failure, such as BlueZ not running. The watch keeps
|
|
37
40
|
* listening after one, so a later change reads again.
|
|
38
41
|
*/
|
|
39
|
-
export declare const watchBluetooth: (
|
|
42
|
+
export declare const watchBluetooth: (domicile: BluetoothDesktop, onBluetooth: (bluetooth: Result<Bluetooth, SystemError>) => void) => (() => void);
|
|
40
43
|
/** Turn the adapter at `adapter` on or off. */
|
|
41
|
-
export declare const setPowered: (
|
|
44
|
+
export declare const setPowered: (domicile: {
|
|
45
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
46
|
+
}, adapter: string, powered: boolean) => Promise<Result<"set", SystemError>>;
|
|
42
47
|
/** Start scanning for devices. BlueZ stops when every scanner has asked. */
|
|
43
|
-
export declare const startDiscovery: (
|
|
44
|
-
|
|
48
|
+
export declare const startDiscovery: (domicile: {
|
|
49
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
50
|
+
}, adapter: string) => Promise<Result<"done", SystemError>>;
|
|
51
|
+
export declare const stopDiscovery: (domicile: {
|
|
52
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
53
|
+
}, adapter: string) => Promise<Result<"done", SystemError>>;
|
|
45
54
|
/** Connect a paired device's profiles. */
|
|
46
|
-
export declare const connect: (
|
|
47
|
-
|
|
55
|
+
export declare const connect: (domicile: {
|
|
56
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
57
|
+
}, device: string) => Promise<Result<"done", SystemError>>;
|
|
58
|
+
export declare const disconnect: (domicile: {
|
|
59
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
60
|
+
}, device: string) => Promise<Result<"done", SystemError>>;
|
|
48
61
|
/**
|
|
49
62
|
* Pair with a device, then trust it so it can reconnect on its own. With no
|
|
50
63
|
* agent registered, BlueZ pairs only devices that need no code.
|
|
51
64
|
*/
|
|
52
|
-
export declare const pair: (
|
|
65
|
+
export declare const pair: (domicile: {
|
|
66
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
67
|
+
}, device: string) => Promise<Result<"done", SystemError>>;
|
|
53
68
|
/** Remove a device and its pairing. */
|
|
54
|
-
export declare const forget: (
|
|
69
|
+
export declare const forget: (domicile: {
|
|
70
|
+
readonly dbus: Pick<Dbus, "call">;
|
|
71
|
+
}, device: Pick<Device, "adapter" | "path">) => Promise<Result<"done", SystemError>>;
|
package/dist/bluetooth.js
CHANGED
|
@@ -20,14 +20,14 @@ const WATCHED = {
|
|
|
20
20
|
* An `Err` is a D-Bus failure, such as BlueZ not running. The watch keeps
|
|
21
21
|
* listening after one, so a later change reads again.
|
|
22
22
|
*/
|
|
23
|
-
export const watchBluetooth = (
|
|
23
|
+
export const watchBluetooth = (domicile, onBluetooth) => {
|
|
24
24
|
const watch = { listening: undefined, stopped: false };
|
|
25
25
|
const report = (bluetooth) => {
|
|
26
26
|
if (!watch.stopped) {
|
|
27
27
|
onBluetooth(bluetooth);
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
-
follow(
|
|
30
|
+
follow(domicile, watch, report).catch((error) => {
|
|
31
31
|
// biome-ignore lint/suspicious/noConsole: surfacing a background failure
|
|
32
32
|
console.error("Failed to watch Bluetooth", error);
|
|
33
33
|
});
|
|
@@ -37,7 +37,7 @@ export const watchBluetooth = (system, onBluetooth) => {
|
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
39
|
/** Turn the adapter at `adapter` on or off. */
|
|
40
|
-
export const setPowered = async (
|
|
40
|
+
export const setPowered = async (domicile, adapter, powered) => (await domicile.dbus.call({
|
|
41
41
|
body: [ADAPTER, "Powered", { signature: "b", value: powered }],
|
|
42
42
|
bus: Bus.System,
|
|
43
43
|
destination: SERVICE,
|
|
@@ -47,16 +47,16 @@ export const setPowered = async (system, adapter, powered) => (await system.dbus
|
|
|
47
47
|
signature: "ssv",
|
|
48
48
|
})).map(() => "set");
|
|
49
49
|
/** Start scanning for devices. BlueZ stops when every scanner has asked. */
|
|
50
|
-
export const startDiscovery = (
|
|
51
|
-
export const stopDiscovery = (
|
|
50
|
+
export const startDiscovery = (domicile, adapter) => call(domicile, adapter, ADAPTER, "StartDiscovery");
|
|
51
|
+
export const stopDiscovery = (domicile, adapter) => call(domicile, adapter, ADAPTER, "StopDiscovery");
|
|
52
52
|
/** Connect a paired device's profiles. */
|
|
53
|
-
export const connect = (
|
|
54
|
-
export const disconnect = (
|
|
53
|
+
export const connect = (domicile, device) => call(domicile, device, DEVICE, "Connect");
|
|
54
|
+
export const disconnect = (domicile, device) => call(domicile, device, DEVICE, "Disconnect");
|
|
55
55
|
/**
|
|
56
56
|
* Pair with a device, then trust it so it can reconnect on its own. With no
|
|
57
57
|
* agent registered, BlueZ pairs only devices that need no code.
|
|
58
58
|
*/
|
|
59
|
-
export const pair = async (
|
|
59
|
+
export const pair = async (domicile, device) => (await call(domicile, device, DEVICE, "Pair")).andThenAsync(async () => (await domicile.dbus.call({
|
|
60
60
|
body: [DEVICE, "Trusted", { signature: "b", value: true }],
|
|
61
61
|
bus: Bus.System,
|
|
62
62
|
destination: SERVICE,
|
|
@@ -66,7 +66,7 @@ export const pair = async (system, device) => (await call(system, device, DEVICE
|
|
|
66
66
|
signature: "ssv",
|
|
67
67
|
})).map(() => "done"));
|
|
68
68
|
/** Remove a device and its pairing. */
|
|
69
|
-
export const forget = async (
|
|
69
|
+
export const forget = async (domicile, device) => (await domicile.dbus.call({
|
|
70
70
|
body: [device.path],
|
|
71
71
|
bus: Bus.System,
|
|
72
72
|
destination: SERVICE,
|
|
@@ -76,7 +76,7 @@ export const forget = async (system, device) => (await system.dbusCall({
|
|
|
76
76
|
signature: "o",
|
|
77
77
|
})).map(() => "done");
|
|
78
78
|
/** A BlueZ method that takes no arguments. */
|
|
79
|
-
const call = async (
|
|
79
|
+
const call = async (domicile, path, iface, member) => (await domicile.dbus.call({
|
|
80
80
|
bus: Bus.System,
|
|
81
81
|
destination: SERVICE,
|
|
82
82
|
interface: iface,
|
|
@@ -84,8 +84,11 @@ const call = async (system, path, iface, member) => (await system.dbusCall({
|
|
|
84
84
|
path,
|
|
85
85
|
})).map(() => "done");
|
|
86
86
|
/** Listen, read, then read again on each change until the match ends. */
|
|
87
|
-
const follow = async (
|
|
88
|
-
const matched = await
|
|
87
|
+
const follow = async (domicile, watch, report) => {
|
|
88
|
+
const matched = await domicile.dbus.match({
|
|
89
|
+
bus: Bus.System,
|
|
90
|
+
sender: SERVICE,
|
|
91
|
+
});
|
|
89
92
|
await matched.match({
|
|
90
93
|
Err: (error) => {
|
|
91
94
|
report(Err(error));
|
|
@@ -96,7 +99,7 @@ const follow = async (system, watch, report) => {
|
|
|
96
99
|
if (watch.stopped) {
|
|
97
100
|
listening.stop();
|
|
98
101
|
}
|
|
99
|
-
await changes(
|
|
102
|
+
await changes(domicile, listening, report);
|
|
100
103
|
const ended = await listening.ended;
|
|
101
104
|
ended.match({
|
|
102
105
|
Err: (error) => {
|
|
@@ -113,12 +116,12 @@ const follow = async (system, watch, report) => {
|
|
|
113
116
|
* Read now, and again on each signal that changes what was read and after a
|
|
114
117
|
* compositor restart.
|
|
115
118
|
*/
|
|
116
|
-
const changes = async (
|
|
119
|
+
const changes = async (domicile, listening, report) => {
|
|
117
120
|
const reader = listening.items.getReader();
|
|
118
|
-
report(await read(
|
|
121
|
+
report(await read(domicile));
|
|
119
122
|
for (let next = await reader.read(); !next.done; next = await reader.read()) {
|
|
120
123
|
if (next.value.kind === ListenedKind.Missed || matters(next.value.item)) {
|
|
121
|
-
report(await read(
|
|
124
|
+
report(await read(domicile));
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
};
|
|
@@ -142,7 +145,7 @@ const matters = (signal) => {
|
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
};
|
|
145
|
-
const read = async (
|
|
148
|
+
const read = async (domicile) => (await domicile.dbus.call({
|
|
146
149
|
bus: Bus.System,
|
|
147
150
|
destination: SERVICE,
|
|
148
151
|
interface: "org.freedesktop.DBus.ObjectManager",
|
package/dist/hardware.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Result } from "@cprussin/option-result";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Files } from "@domicile-desktop/sdk/desktop/files";
|
|
3
|
+
import type { SystemError } from "@domicile-desktop/sdk/system";
|
|
3
4
|
/** The calls the check makes. Each works while the desktop is locked. */
|
|
4
|
-
export type
|
|
5
|
+
export type SysfsDesktop = {
|
|
6
|
+
readonly files: Pick<Files, "readDir" | "readText">;
|
|
7
|
+
};
|
|
5
8
|
/**
|
|
6
9
|
* Whether there is a Bluetooth controller, or an rfkill switch for one. A
|
|
7
10
|
* radio rfkill blocks can take its controller away with it, and its switch
|
|
8
11
|
* stays.
|
|
9
12
|
*/
|
|
10
|
-
export declare const hasBluetoothHardware: (
|
|
13
|
+
export declare const hasBluetoothHardware: (domicile: SysfsDesktop) => Promise<Result<boolean, SystemError>>;
|
package/dist/hardware.js
CHANGED
|
@@ -7,20 +7,20 @@ import { SystemErrorKind } from "@domicile-desktop/sdk/system";
|
|
|
7
7
|
* radio rfkill blocks can take its controller away with it, and its switch
|
|
8
8
|
* stays.
|
|
9
9
|
*/
|
|
10
|
-
export const hasBluetoothHardware = async (
|
|
10
|
+
export const hasBluetoothHardware = async (domicile) => (await entriesOf(domicile, "/sys/class/bluetooth")).match({
|
|
11
11
|
Err: (error) => Promise.resolve(Err(error)),
|
|
12
12
|
Ok: async (controllers) => controllers.length > 0
|
|
13
13
|
? Ok(true)
|
|
14
|
-
: (await entriesOf(
|
|
14
|
+
: (await entriesOf(domicile, "/sys/class/rfkill")).match({
|
|
15
15
|
Err: (error) => Promise.resolve(Err(error)),
|
|
16
|
-
Ok: (switches) => anyOf(switches, async (name) => (await lineOf(
|
|
16
|
+
Ok: (switches) => anyOf(switches, async (name) => (await lineOf(domicile, `/sys/class/rfkill/${name}/type`)).map((type) => type.match({
|
|
17
17
|
None: () => false,
|
|
18
18
|
Some: (is) => is === "bluetooth",
|
|
19
19
|
}))),
|
|
20
20
|
}),
|
|
21
21
|
});
|
|
22
22
|
/** The names in `dir`; none when it does not exist, as when no driver made it. */
|
|
23
|
-
const entriesOf = async (
|
|
23
|
+
const entriesOf = async (domicile, dir) => (await domicile.files.readDir(dir)).match({
|
|
24
24
|
Err: (error) => error.kind === SystemErrorKind.NotFound ? Ok([]) : Err(error),
|
|
25
25
|
Ok: (entries) => Ok(entries.map(({ name }) => name)),
|
|
26
26
|
});
|
|
@@ -38,7 +38,7 @@ const anyOf = async (names, test) => {
|
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
/** A one-line file under `/sys`, without its newline; `None` when missing. */
|
|
41
|
-
const lineOf = async (
|
|
41
|
+
const lineOf = async (domicile, path) => (await domicile.files.readText(path)).match({
|
|
42
42
|
Err: (error) => error.kind === SystemErrorKind.NotFound ? Ok(None()) : Err(error),
|
|
43
43
|
Ok: (text) => Ok(Some(text.trim())),
|
|
44
44
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@cprussin/option-result": "^2.0.1",
|
|
4
|
-
"@domicile-desktop/sdk": "0.0.0-alpha-
|
|
4
|
+
"@domicile-desktop/sdk": "0.0.0-alpha-56dd6c187f4e",
|
|
5
5
|
"zod": "^4.5.4"
|
|
6
6
|
},
|
|
7
7
|
"description": "BlueZ for a Domicile shell: whether each adapter is powered and which devices are connected, kept current, and turning adapters on and off.",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"test:unit": "bun test"
|
|
46
46
|
},
|
|
47
47
|
"type": "module",
|
|
48
|
-
"version": "0.0.0-alpha-
|
|
48
|
+
"version": "0.0.0-alpha-56dd6c187f4e"
|
|
49
49
|
}
|