@domicile-desktop/system-bluetooth 0.0.0-alpha-e3866c607353 → 0.0.0-alpha-1fcbde657613

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # @domicile-desktop/system-bluetooth
2
2
 
3
- BlueZ for a Domicile shell: each adapter and whether it is powered, the
4
- connected devices, and turning an adapter on or off. Built on
3
+ BlueZ for a Domicile shell: each adapter and whether it is powered or
4
+ scanning, the devices it knows, and the requests that change them. Built on
5
5
  [`@domicile-desktop/sdk/system`](../chrome-sdk/README.md).
6
6
 
7
7
  ## Usage
@@ -14,14 +14,19 @@ const host = system(domicile);
14
14
  const stop = watchBluetooth(host, (bluetooth) => {
15
15
  bluetooth.match({
16
16
  Err: () => hide(),
17
- Ok: ({ adapters, connected }) => show(adapters, connected),
17
+ Ok: ({ adapters, devices }) => show(adapters, devices),
18
18
  });
19
19
  });
20
20
  await setPowered(host, "/org/bluez/hci0", false);
21
21
  ```
22
22
 
23
23
  - Reads `GetManagedObjects` once, then again when an object comes or goes or
24
- an adapter's `Powered` or a device's `Connected` or `Alias` changes.
24
+ one of these changes: an adapter's `Powered` or `Discovering`, a device's
25
+ `Alias`, `Name`, `Connected` or `Paired`, or its battery's `Percentage`.
26
+ - `devices` holds paired devices and, from a scan, those with a name.
27
+ - Requests: `setPowered`, `startDiscovery`, `stopDiscovery`, `connect`,
28
+ `disconnect`, `pair` (then trusts the device) and `forget`.
29
+ - `pair` registers no agent, so only devices that need no code pair.
25
30
  - An `Err` is a D-Bus failure, such as BlueZ not running or an adapter
26
31
  blocked by rfkill.
27
32
  - A bug or an unexpected reply shape throws, logged to the console.
@@ -4,15 +4,28 @@ import type { System, SystemError } from "@domicile-desktop/sdk/system";
4
4
  export type Adapter = {
5
5
  path: string;
6
6
  powered: boolean;
7
+ discovering: boolean;
7
8
  };
8
- /** A connected device. `name` is its alias, which BlueZ fills from its name. */
9
+ /**
10
+ * A device BlueZ knows. `name` is its alias, which BlueZ fills from its name.
11
+ * `battery` is 0 through 100, for a device that reports it.
12
+ */
9
13
  export type Device = {
10
14
  path: string;
15
+ adapter: string;
16
+ address: string;
11
17
  name: string;
18
+ paired: boolean;
19
+ connected: boolean;
20
+ battery: number | undefined;
12
21
  };
22
+ /**
23
+ * Every adapter, and each device that is paired or, found by a scan, has a
24
+ * name. A scan finds many nameless devices, such as beacons.
25
+ */
13
26
  export type Bluetooth = {
14
27
  adapters: Adapter[];
15
- connected: Device[];
28
+ devices: Device[];
16
29
  };
17
30
  /** The calls this library makes. */
18
31
  export type BluetoothSystem = Pick<System, "dbusCall" | "dbusMatch">;
@@ -26,3 +39,16 @@ export type BluetoothSystem = Pick<System, "dbusCall" | "dbusMatch">;
26
39
  export declare const watchBluetooth: (system: BluetoothSystem, onBluetooth: (bluetooth: Result<Bluetooth, SystemError>) => void) => (() => void);
27
40
  /** Turn the adapter at `adapter` on or off. */
28
41
  export declare const setPowered: (system: Pick<System, "dbusCall">, adapter: string, powered: boolean) => Promise<Result<"set", SystemError>>;
42
+ /** Start scanning for devices. BlueZ stops when every scanner has asked. */
43
+ export declare const startDiscovery: (system: Pick<System, "dbusCall">, adapter: string) => Promise<Result<"done", SystemError>>;
44
+ export declare const stopDiscovery: (system: Pick<System, "dbusCall">, adapter: string) => Promise<Result<"done", SystemError>>;
45
+ /** Connect a paired device's profiles. */
46
+ export declare const connect: (system: Pick<System, "dbusCall">, device: string) => Promise<Result<"done", SystemError>>;
47
+ export declare const disconnect: (system: Pick<System, "dbusCall">, device: string) => Promise<Result<"done", SystemError>>;
48
+ /**
49
+ * Pair with a device, then trust it so it can reconnect on its own. With no
50
+ * agent registered, BlueZ pairs only devices that need no code.
51
+ */
52
+ export declare const pair: (system: Pick<System, "dbusCall">, device: string) => Promise<Result<"done", SystemError>>;
53
+ /** Remove a device and its pairing. */
54
+ export declare const forget: (system: Pick<System, "dbusCall">, device: Pick<Device, "adapter" | "path">) => Promise<Result<"done", SystemError>>;
package/dist/bluetooth.js CHANGED
@@ -1,15 +1,17 @@
1
- // BlueZ over the system bus: adapters and connected devices, kept current.
2
- // See docs/SHELL-SYSTEM-ACCESS.md.
1
+ // BlueZ over the system bus: adapters and devices, kept current, and the
2
+ // requests that change them. See docs/SHELL-SYSTEM-ACCESS.md.
3
3
  import { Err } from "@cprussin/option-result";
4
4
  import { Bus } from "@domicile-desktop/sdk/system";
5
5
  import { z } from "zod";
6
6
  const SERVICE = "org.bluez";
7
7
  const ADAPTER = "org.bluez.Adapter1";
8
8
  const DEVICE = "org.bluez.Device1";
9
+ const BATTERY = "org.bluez.Battery1";
9
10
  /** The properties whose changes alter a {@link Bluetooth}, by interface. */
10
11
  const WATCHED = {
11
- [ADAPTER]: ["Powered"],
12
- [DEVICE]: ["Alias", "Connected"],
12
+ [ADAPTER]: ["Discovering", "Powered"],
13
+ [BATTERY]: ["Percentage"],
14
+ [DEVICE]: ["Alias", "Connected", "Name", "Paired"],
13
15
  };
14
16
  /**
15
17
  * Calls `onBluetooth` with the adapters and connected devices now and after
@@ -44,6 +46,43 @@ export const setPowered = async (system, adapter, powered) => (await system.dbus
44
46
  path: adapter,
45
47
  signature: "ssv",
46
48
  })).map(() => "set");
49
+ /** Start scanning for devices. BlueZ stops when every scanner has asked. */
50
+ export const startDiscovery = (system, adapter) => call(system, adapter, ADAPTER, "StartDiscovery");
51
+ export const stopDiscovery = (system, adapter) => call(system, adapter, ADAPTER, "StopDiscovery");
52
+ /** Connect a paired device's profiles. */
53
+ export const connect = (system, device) => call(system, device, DEVICE, "Connect");
54
+ export const disconnect = (system, device) => call(system, device, DEVICE, "Disconnect");
55
+ /**
56
+ * Pair with a device, then trust it so it can reconnect on its own. With no
57
+ * agent registered, BlueZ pairs only devices that need no code.
58
+ */
59
+ export const pair = async (system, device) => (await call(system, device, DEVICE, "Pair")).andThenAsync(async () => (await system.dbusCall({
60
+ body: [DEVICE, "Trusted", { signature: "b", value: true }],
61
+ bus: Bus.System,
62
+ destination: SERVICE,
63
+ interface: "org.freedesktop.DBus.Properties",
64
+ member: "Set",
65
+ path: device,
66
+ signature: "ssv",
67
+ })).map(() => "done"));
68
+ /** Remove a device and its pairing. */
69
+ export const forget = async (system, device) => (await system.dbusCall({
70
+ body: [device.path],
71
+ bus: Bus.System,
72
+ destination: SERVICE,
73
+ interface: ADAPTER,
74
+ member: "RemoveDevice",
75
+ path: device.adapter,
76
+ signature: "o",
77
+ })).map(() => "done");
78
+ /** A BlueZ method that takes no arguments. */
79
+ const call = async (system, path, iface, member) => (await system.dbusCall({
80
+ bus: Bus.System,
81
+ destination: SERVICE,
82
+ interface: iface,
83
+ member,
84
+ path,
85
+ })).map(() => "done");
47
86
  /** Listen, read, then read again on each change until the match ends. */
48
87
  const follow = async (system, watch, report) => {
49
88
  const matched = await system.dbusMatch({ bus: Bus.System, sender: SERVICE });
@@ -112,21 +151,51 @@ const bluetoothOf = (objects) => {
112
151
  return {
113
152
  adapters: entries.flatMap(([path, held]) => held[ADAPTER] === undefined
114
153
  ? []
115
- : [{ path, powered: held[ADAPTER].Powered }]),
116
- connected: entries.flatMap(([path, held]) => held[DEVICE]?.Connected === true
117
- ? [{ name: held[DEVICE].Alias, path }]
118
- : []),
154
+ : [
155
+ {
156
+ discovering: held[ADAPTER].Discovering,
157
+ path,
158
+ powered: held[ADAPTER].Powered,
159
+ },
160
+ ]),
161
+ devices: entries.flatMap(([path, held]) => {
162
+ const device = held[DEVICE];
163
+ return device === undefined ||
164
+ (!device.Paired && device.Name === undefined)
165
+ ? []
166
+ : [
167
+ {
168
+ adapter: device.Adapter,
169
+ address: device.Address,
170
+ battery: held[BATTERY]?.Percentage,
171
+ connected: device.Connected,
172
+ name: device.Alias,
173
+ paired: device.Paired,
174
+ path,
175
+ },
176
+ ];
177
+ }),
119
178
  };
120
179
  };
121
180
  /** A `v`, as `domicile_host::dbus_json` writes it, read as its value. */
122
181
  const variant = (value) => z.object({ value }).transform(({ value: held }) => held);
123
182
  const managedObjectsSchema = z.tuple([
124
183
  z.record(z.string(), z.object({
125
- [ADAPTER]: z.object({ Powered: variant(z.boolean()) }).optional(),
184
+ [ADAPTER]: z
185
+ .object({
186
+ Discovering: variant(z.boolean()),
187
+ Powered: variant(z.boolean()),
188
+ })
189
+ .optional(),
190
+ [BATTERY]: z.object({ Percentage: variant(z.number()) }).optional(),
126
191
  [DEVICE]: z
127
192
  .object({
193
+ Adapter: variant(z.string()),
194
+ Address: variant(z.string()),
128
195
  Alias: variant(z.string()),
129
196
  Connected: variant(z.boolean()),
197
+ Name: variant(z.string()).optional(),
198
+ Paired: variant(z.boolean()),
130
199
  })
131
200
  .optional(),
132
201
  })),
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-e3866c607353",
4
+ "@domicile-desktop/sdk": "0.0.0-alpha-1fcbde657613",
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.",
@@ -36,5 +36,5 @@
36
36
  "test:unit": "bun test"
37
37
  },
38
38
  "type": "module",
39
- "version": "0.0.0-alpha-e3866c607353"
39
+ "version": "0.0.0-alpha-1fcbde657613"
40
40
  }