Line data Source code
1 : import 'dart:async';
2 : import 'dart:io';
3 :
4 : import 'package:dbus/dbus.dart';
5 :
6 : /// D-Bus interface names
7 : const _managerInterfaceName = 'org.freedesktop.NetworkManager';
8 :
9 : /// Overall networking states.
10 : enum NetworkManagerState {
11 : unknown,
12 : asleep,
13 : disconnected,
14 : disconnecting,
15 : connecting,
16 : connectedLocal,
17 : connectedSite,
18 : connectedGlobal,
19 : }
20 :
21 0 : NetworkManagerState _decodeState(int value) {
22 : switch (value) {
23 0 : case 10:
24 : return NetworkManagerState.asleep;
25 0 : case 20:
26 : return NetworkManagerState.disconnected;
27 0 : case 30:
28 : return NetworkManagerState.disconnecting;
29 0 : case 40:
30 : return NetworkManagerState.connecting;
31 0 : case 50:
32 : return NetworkManagerState.connectedLocal;
33 0 : case 60:
34 : return NetworkManagerState.connectedSite;
35 0 : case 70:
36 : return NetworkManagerState.connectedGlobal;
37 : default:
38 : return NetworkManagerState.unknown;
39 : }
40 : }
41 :
42 : /// Internet connectivity states.
43 : enum NetworkManagerConnectivityState { unknown, none, portal, limited, full }
44 :
45 0 : NetworkManagerConnectivityState _decodeConnectivityState(int value) {
46 : switch (value) {
47 0 : case 1:
48 : return NetworkManagerConnectivityState.none;
49 0 : case 2:
50 : return NetworkManagerConnectivityState.portal;
51 0 : case 3:
52 : return NetworkManagerConnectivityState.limited;
53 0 : case 4:
54 : return NetworkManagerConnectivityState.full;
55 : default:
56 : return NetworkManagerConnectivityState.unknown;
57 : }
58 : }
59 :
60 : class _NetworkManagerInterface {
61 : final Map<String, DBusValue> properties;
62 : final propertiesChangedStreamController = StreamController<List<String>>.broadcast();
63 :
64 : /// Stream of property names as their values change.
65 0 : Stream<List<String>> get propertiesChanged => propertiesChangedStreamController.stream;
66 :
67 0 : _NetworkManagerInterface(this.properties);
68 :
69 0 : void updateProperties(Map<String, DBusValue> changedProperties) {
70 0 : properties.addAll(changedProperties);
71 0 : propertiesChangedStreamController.add(changedProperties.keys.toList());
72 : }
73 : }
74 :
75 : class _NetworkManagerObject extends DBusRemoteObject {
76 : final interfaces = <String, _NetworkManagerInterface>{};
77 :
78 0 : void updateInterfaces(Map<String, Map<String, DBusValue>> interfacesAndProperties) {
79 0 : interfacesAndProperties.forEach((interfaceName, properties) {
80 0 : interfaces[interfaceName] = _NetworkManagerInterface(properties);
81 : });
82 : }
83 :
84 : /// Returns true if removing [interfaceNames] would remove all interfaces on this object.
85 0 : bool wouldRemoveAllInterfaces(List<String> interfaceNames) {
86 0 : for (var interface in interfaces.keys) {
87 0 : if (!interfaceNames.contains(interface)) {
88 : return false;
89 : }
90 : }
91 : return true;
92 : }
93 :
94 0 : void removeInterfaces(List<String> interfaceNames) {
95 0 : for (var interfaceName in interfaceNames) {
96 0 : interfaces.remove(interfaceName);
97 : }
98 : }
99 :
100 0 : void updateProperties(String interfaceName, Map<String, DBusValue> changedProperties) {
101 0 : var interface = interfaces[interfaceName];
102 : if (interface != null) {
103 0 : interface.updateProperties(changedProperties);
104 : }
105 : }
106 :
107 : /// Gets a cached property.
108 0 : DBusValue? getCachedProperty(String interfaceName, String name) {
109 0 : var interface = interfaces[interfaceName];
110 : if (interface == null) {
111 : return null;
112 : }
113 0 : return interface.properties[name];
114 : }
115 :
116 : /// Gets a cached boolean property, or returns null if not present or not the correct type.
117 0 : bool? getBooleanProperty(String interface, String name) {
118 0 : var value = getCachedProperty(interface, name);
119 : if (value == null) {
120 : return null;
121 : }
122 0 : if (value.signature != DBusSignature('b')) {
123 : return null;
124 : }
125 0 : return (value as DBusBoolean).value;
126 : }
127 :
128 : /// Gets a cached unsigned 8 bit integer property, or returns null if not present or not the correct type.
129 0 : int? getByteProperty(String interface, String name) {
130 0 : var value = getCachedProperty(interface, name);
131 : if (value == null) {
132 : return null;
133 : }
134 0 : if (value.signature != DBusSignature('y')) {
135 : return null;
136 : }
137 0 : return (value as DBusByte).value;
138 : }
139 :
140 : /// Gets a cached signed 32 bit integer property, or returns null if not present or not the correct type.
141 0 : int? getInt32Property(String interface, String name) {
142 0 : var value = getCachedProperty(interface, name);
143 : if (value == null) {
144 : return null;
145 : }
146 0 : if (value.signature != DBusSignature('i')) {
147 : return null;
148 : }
149 0 : return (value as DBusInt32).value;
150 : }
151 :
152 : /// Gets a cached unsigned 32 bit integer property, or returns null if not present or not the correct type.
153 0 : int? getUint32Property(String interface, String name) {
154 0 : var value = getCachedProperty(interface, name);
155 : if (value == null) {
156 : return null;
157 : }
158 0 : if (value.signature != DBusSignature('u')) {
159 : return null;
160 : }
161 0 : return (value as DBusUint32).value;
162 : }
163 :
164 : /// Gets a cached signed 64 bit integer property, or returns null if not present or not the correct type.
165 0 : int? getInt64Property(String interface, String name) {
166 0 : var value = getCachedProperty(interface, name);
167 : if (value == null) {
168 : return null;
169 : }
170 0 : if (value.signature != DBusSignature('x')) {
171 : return null;
172 : }
173 0 : return (value as DBusInt64).value;
174 : }
175 :
176 : /// Gets a cached unsigned 64 bit integer property, or returns null if not present or not the correct type.
177 0 : int? getUint64Property(String interface, String name) {
178 0 : var value = getCachedProperty(interface, name);
179 : if (value == null) {
180 : return null;
181 : }
182 0 : if (value.signature != DBusSignature('t')) {
183 : return null;
184 : }
185 0 : return (value as DBusUint64).value;
186 : }
187 :
188 : /// Gets a cached string property, or returns null if not present or not the correct type.
189 0 : String? getStringProperty(String interface, String name) {
190 0 : var value = getCachedProperty(interface, name);
191 : if (value == null) {
192 : return null;
193 : }
194 0 : if (value.signature != DBusSignature('s')) {
195 : return null;
196 : }
197 0 : return (value as DBusString).value;
198 : }
199 :
200 : /// Gets a cached string array property, or returns null if not present or not the correct type.
201 0 : List<String>? getStringArrayProperty(String interface, String name) {
202 0 : var value = getCachedProperty(interface, name);
203 : if (value == null) {
204 : return null;
205 : }
206 0 : if (value.signature != DBusSignature('as')) {
207 : return null;
208 : }
209 0 : return (value as DBusArray).children.map((e) => (e as DBusString).value).toList();
210 : }
211 :
212 : /// Gets a cached object path property, or returns null if not present or not the correct type.
213 0 : DBusObjectPath? getObjectPathProperty(String interface, String name) {
214 0 : var value = getCachedProperty(interface, name);
215 : if (value == null) {
216 : return null;
217 : }
218 0 : if (value.signature != DBusSignature('o')) {
219 : return null;
220 : }
221 : return (value as DBusObjectPath);
222 : }
223 :
224 : /// Gets a cached object path array property, or returns null if not present or not the correct type.
225 0 : List<DBusObjectPath>? getObjectPathArrayProperty(String interface, String name) {
226 0 : var value = getCachedProperty(interface, name);
227 : if (value == null) {
228 : return null;
229 : }
230 0 : if (value.signature != DBusSignature('ao')) {
231 : return null;
232 : }
233 0 : return (value as DBusArray).children.map((e) => (e as DBusObjectPath)).toList();
234 : }
235 :
236 : /// Gets a cached list of data property, or returns null if not present or not the correct type.
237 0 : List<Map<String, dynamic>>? getDataListProperty(String interface, String name) {
238 0 : var value = getCachedProperty(interface, name);
239 : if (value == null) {
240 : return null;
241 : }
242 0 : if (value.signature != DBusSignature('aa{sv}')) {
243 : return null;
244 : }
245 0 : Map<String, dynamic> convertData(DBusValue value) {
246 0 : return (value as DBusDict).children.map((key, value) => MapEntry(
247 0 : (key as DBusString).value,
248 0 : (value as DBusVariant).value.toNative(),
249 : ));
250 : }
251 :
252 0 : return (value as DBusArray).children.map((value) => convertData(value)).toList();
253 : }
254 :
255 0 : _NetworkManagerObject(DBusClient client, DBusObjectPath path, Map<String, Map<String, DBusValue>> interfacesAndProperties) : super(client, name: 'org.freedesktop.NetworkManager', path: path) {
256 0 : updateInterfaces(interfacesAndProperties);
257 : }
258 : }
259 :
260 : /// A client that connects to NetworkManager.
261 : class NetworkManagerClient {
262 : /// The bus this client is connected to.
263 : final DBusClient _bus;
264 : final bool _closeBus;
265 :
266 : /// The root D-Bus NetworkManager object at path '/org/freedesktop'.
267 : late final DBusRemoteObjectManager _root;
268 :
269 : // Objects exported on the bus.
270 : final _objects = <DBusObjectPath, _NetworkManagerObject>{};
271 :
272 : // Subscription to object manager signals.
273 : StreamSubscription? _objectManagerSubscription;
274 :
275 : /// Creates a new NetworkManager client connected to the system D-Bus.
276 0 : NetworkManagerClient({DBusClient? bus})
277 0 : : _bus = bus ?? DBusClient.system(),
278 : _closeBus = bus == null {
279 0 : _root = DBusRemoteObjectManager(
280 0 : _bus,
281 : name: 'org.freedesktop.NetworkManager',
282 0 : path: DBusObjectPath('/org/freedesktop'),
283 : );
284 : }
285 :
286 : /// Stream of property names as their values change.
287 0 : Stream<List<String>> get propertiesChanged => _manager?.interfaces[_managerInterfaceName]?.propertiesChangedStreamController.stream ?? Stream<List<String>>.empty();
288 :
289 : /// Connects to the NetworkManager D-Bus objects.
290 : /// Must be called before accessing methods and properties.
291 0 : Future<void> connect() async {
292 : // Already connected
293 0 : if (_objectManagerSubscription != null) {
294 : return;
295 : }
296 :
297 : // Big old grody Hack
298 : // DBus/nm doesnt seem to offer a way to deter ine if dbus is available on system
299 : // worse the first connections get triggered in dbus_client onListen an isn't a catchable exception so crashes the app
300 : // this is a hacky way to force an exception on thread if dbus isn't available and bail with out crashing
301 : try {
302 0 : await _root.client.getNameOwner(_root.name);
303 0 : } on SocketException catch (e) {
304 0 : print("nm dbus connect/emit test threw exception, dbus likely unavailable on system, aborting connect: $e");
305 : return;
306 : }
307 :
308 : // Subscribe to changes
309 0 : _objectManagerSubscription = _root.signals.listen((signal) {
310 0 : if (signal is DBusObjectManagerInterfacesAddedSignal) {
311 0 : var object = _objects[signal.changedPath];
312 : if (object != null) {
313 0 : object.updateInterfaces(signal.interfacesAndProperties);
314 : } else {
315 0 : object = _NetworkManagerObject(_bus, signal.changedPath, signal.interfacesAndProperties);
316 0 : _objects[signal.changedPath] = object;
317 : }
318 0 : } else if (signal is DBusObjectManagerInterfacesRemovedSignal) {
319 0 : var object = _objects[signal.changedPath];
320 : if (object != null) {
321 : // If all the interface are removed, then this object has been removed.
322 : // Keep the previous values around for the client to use.
323 0 : if (object.wouldRemoveAllInterfaces(signal.interfaces)) {
324 0 : _objects.remove(signal.changedPath);
325 : } else {
326 0 : object.removeInterfaces(signal.interfaces);
327 : }
328 : }
329 0 : } else if (signal is DBusPropertiesChangedSignal) {
330 0 : var object = _objects[signal.path];
331 : if (object != null) {
332 0 : object.updateProperties(signal.propertiesInterface, signal.changedProperties);
333 : }
334 : }
335 : });
336 :
337 : // Find all the objects exported.
338 0 : var objects = await _root.getManagedObjects();
339 0 : objects.forEach((objectPath, interfacesAndProperties) {
340 0 : _objects[objectPath] = _NetworkManagerObject(_bus, objectPath, interfacesAndProperties);
341 : });
342 : }
343 :
344 : /// The type of connection being used to access the network.
345 0 : String get primaryConnectionType {
346 0 : return _manager?.getStringProperty(
347 : _managerInterfaceName,
348 : 'PrimaryConnectionType',
349 : ) ??
350 : '';
351 : }
352 :
353 : /// True is NetworkManager is still starting up.
354 0 : bool get startup {
355 0 : return _manager?.getBooleanProperty(_managerInterfaceName, 'Startup') ?? false;
356 : }
357 :
358 : /// The version of NetworkManager running.
359 0 : String get version {
360 0 : return _manager?.getStringProperty(_managerInterfaceName, 'Version') ?? '';
361 : }
362 :
363 : /// The result of the last connectivity check.
364 0 : NetworkManagerConnectivityState get connectivity {
365 0 : var value = _manager?.getUint32Property(_managerInterfaceName, 'Connectivity') ?? 0;
366 0 : return _decodeConnectivityState(value);
367 : }
368 :
369 : /// The overall networking state.
370 0 : NetworkManagerState get state {
371 0 : var value = _manager?.getUint32Property(_managerInterfaceName, 'State') ?? 0;
372 0 : return _decodeState(value);
373 : }
374 :
375 0 : _NetworkManagerObject? get _manager => _objects[DBusObjectPath('/org/freedesktop/NetworkManager')];
376 :
377 : /// Terminates all active connections. If a client remains unclosed, the Dart process may not terminate.
378 0 : Future<void> close() async {
379 0 : if (_objectManagerSubscription != null) {
380 0 : await _objectManagerSubscription!.cancel();
381 0 : _objectManagerSubscription = null;
382 : }
383 0 : if (_closeBus) {
384 0 : await _bus.close();
385 : }
386 : }
387 : }
|