dbus2mqtt 0.1.2__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of dbus2mqtt might be problematic. Click here for more details.

@@ -15,8 +15,9 @@ class SignalConfig:
15
15
  filter: str | None = None
16
16
 
17
17
  def matches_filter(self, template_engine: TemplateEngine, *args) -> bool:
18
- res = template_engine.render_template(self.filter, str, { "args": args })
19
- return res == "True"
18
+ if self.filter:
19
+ return template_engine.render_template(self.filter, bool, { "args": args })
20
+ return True
20
21
 
21
22
  @dataclass
22
23
  class MethodConfig:
@@ -29,13 +30,15 @@ class PropertyConfig:
29
30
  @dataclass
30
31
  class InterfaceConfig:
31
32
  interface: str
32
- mqtt_call_method_topic: str | None = None
33
+ mqtt_command_topic: str | None = None
33
34
  signals: list[SignalConfig] = field(default_factory=list)
34
35
  methods: list[MethodConfig] = field(default_factory=list)
35
36
  properties: list[PropertyConfig] = field(default_factory=list)
36
37
 
37
- def render_mqtt_call_method_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> Any:
38
- return template_engine.render_template(self.mqtt_call_method_topic, str, context)
38
+ def render_mqtt_command_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> Any:
39
+ if self.mqtt_command_topic:
40
+ return template_engine.render_template(self.mqtt_command_topic, str, context)
41
+ return None
39
42
 
40
43
  @dataclass
41
44
  class FlowTriggerMqttConfig:
@@ -47,8 +50,8 @@ class FlowTriggerMqttConfig:
47
50
  class FlowTriggerScheduleConfig:
48
51
  type: Literal["schedule"] = "schedule"
49
52
  id: str = field(default_factory=lambda: uuid.uuid4().hex)
50
- cron: dict[str, Any] | None = None
51
- interval: dict[str, Any] | None = None
53
+ cron: dict[str, object] | None = None
54
+ interval: dict[str, object] | None = None
52
55
 
53
56
  @dataclass
54
57
  class FlowTriggerDbusSignalConfig:
@@ -57,17 +60,17 @@ class FlowTriggerDbusSignalConfig:
57
60
  type: Literal["dbus_signal"] = "dbus_signal"
58
61
  bus_name: str | None = None
59
62
  path: str | None = None
60
- filter: str | None = None
63
+ # filter: str | None = None
61
64
 
62
65
  @dataclass
63
66
  class FlowTriggerBusNameAddedConfig:
64
67
  type: Literal["bus_name_added"] = "bus_name_added"
65
- filter: str | None = None
68
+ # filter: str | None = None
66
69
 
67
70
  @dataclass
68
71
  class FlowTriggerBusNameRemovedConfig:
69
72
  type: Literal["bus_name_removed"] = "bus_name_removed"
70
- filter: str | None = None
73
+ # filter: str | None = None
71
74
 
72
75
  FlowTriggerConfig = Annotated[
73
76
  FlowTriggerMqttConfig | FlowTriggerScheduleConfig | FlowTriggerDbusSignalConfig | FlowTriggerBusNameAddedConfig | FlowTriggerBusNameRemovedConfig,
@@ -77,17 +80,17 @@ FlowTriggerConfig = Annotated[
77
80
  @dataclass
78
81
  class FlowActionContextSetConfig:
79
82
  type: Literal["context_set"] = "context_set"
80
- context: dict[str, Any] | None = None
81
- global_context: dict[str, Any] | None = None
83
+ context: dict[str, object] | None = None
84
+ """Per flow execution context"""
85
+ global_context: dict[str, object] | None = None
86
+ """Global context, shared between multiple flow executions, over all subscriptions"""
82
87
 
83
88
  @dataclass
84
89
  class FlowActionMqttPublishConfig:
85
90
  topic: str
86
91
  payload_template: str | dict[str, Any]
87
- """should be a dict if payload_type is json/yaml
88
- or a string if payload_type is text"""
89
92
  type: Literal["mqtt_publish"] = "mqtt_publish"
90
- payload_type: Literal["json", "yaml", "text"] = "json"
93
+ payload_type: Literal["json", "yaml", "text", "binary"] = "json"
91
94
 
92
95
  FlowActionConfig = Annotated[
93
96
  FlowActionMqttPublishConfig | FlowActionContextSetConfig,
@@ -0,0 +1,31 @@
1
+ import jsonargparse
2
+
3
+
4
+ def _custom_yaml_load(stream):
5
+ if isinstance(stream, str):
6
+ v = stream.strip()
7
+
8
+ # jsonargparse tries to parse yaml 1.1 boolean like values
9
+ # Without this, str:"{'PlaybackStatus': 'Off'}" would become dict:{'PlaybackStatus': False}
10
+ if v in ['on', 'On', 'off', 'Off', 'TRUE', 'FALSE', 'True', 'False']:
11
+ return stream
12
+
13
+ # Anoyingly, values starting with {{ and ending with }} are working with the default yaml_loader
14
+ # from jsonargparse. Somehow its not when we use the custom yaml loader.
15
+ # This fixes it
16
+ if v.startswith("{{") or v.startswith("{%"):
17
+ return stream
18
+
19
+ # Delegate to default yaml loader from jsonargparse
20
+ yaml_loader = jsonargparse.get_loader("yaml")
21
+ return yaml_loader(stream)
22
+
23
+ def new_argument_parser() -> jsonargparse.ArgumentParser:
24
+
25
+ # register out custom yaml loader for jsonargparse
26
+ jsonargparse.set_loader("yaml_custom", _custom_yaml_load)
27
+
28
+ # unless specified otherwise, load config from config.yaml
29
+ parser = jsonargparse.ArgumentParser(default_config_files=["config.yaml"], default_env=True, env_prefix=False, parser_mode="yaml_custom")
30
+
31
+ return parser
@@ -1,13 +1,11 @@
1
1
  import json
2
2
  import logging
3
- import re
4
3
 
5
4
  from datetime import datetime
6
5
  from typing import Any
7
6
 
8
- import dbus_next
9
- import dbus_next.aio as dbus_aio
10
- import dbus_next.introspection as dbus_introspection
7
+ import dbus_fast.aio as dbus_aio
8
+ import dbus_fast.introspection as dbus_introspection
11
9
 
12
10
  from dbus2mqtt import AppContext
13
11
  from dbus2mqtt.config import InterfaceConfig, SubscriptionConfig
@@ -17,17 +15,15 @@ from dbus2mqtt.dbus.dbus_util import (
17
15
  unwrap_dbus_object,
18
16
  unwrap_dbus_objects,
19
17
  )
18
+ from dbus2mqtt.dbus.introspection_patches.mpris_playerctl import (
19
+ mpris_introspection_playerctl,
20
+ )
21
+ from dbus2mqtt.dbus.introspection_patches.mpris_vlc import mpris_introspection_vlc
20
22
  from dbus2mqtt.event_broker import DbusSignalWithState, MqttMessage
21
23
  from dbus2mqtt.flow.flow_processor import FlowScheduler, FlowTriggerMessage
22
24
 
23
25
  logger = logging.getLogger(__name__)
24
26
 
25
- # dbus_next path to support https://docs.flatpak.org/en/latest/portal-api-reference.html
26
- # Although not correct, flatpak exposes properties with names containing '-'
27
- # This is failing assertions in dbus_next
28
- # original: r'^[A-Za-z_][A-Za-z0-9_]*$'
29
- # patched: r'^[A-Za-z_][A-Za-z0-9_-]*$'
30
- dbus_next.validators._element_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_-]*$')
31
27
 
32
28
  class DbusClient:
33
29
 
@@ -90,23 +86,23 @@ class DbusClient:
90
86
 
91
87
  return proxy_object, bus_name_subscriptions
92
88
 
93
- def _dbus_next_signal_publisher(self, dbus_signal_state: dict[str, Any], *args):
89
+ def _dbus_fast_signal_publisher(self, dbus_signal_state: dict[str, Any], *args):
94
90
  unwrapped_args = unwrap_dbus_objects(args)
95
91
  self.event_broker.on_dbus_signal(
96
92
  DbusSignalWithState(**dbus_signal_state, args=unwrapped_args)
97
93
  )
98
94
 
99
- def _dbus_next_signal_handler(self, signal: dbus_introspection.Signal, state: dict[str, Any]) -> Any:
95
+ def _dbus_fast_signal_handler(self, signal: dbus_introspection.Signal, state: dict[str, Any]) -> Any:
100
96
  expected_args = len(signal.args)
101
97
 
102
98
  if expected_args == 1:
103
- return lambda a: self._dbus_next_signal_publisher(state, a)
99
+ return lambda a: self._dbus_fast_signal_publisher(state, a)
104
100
  elif expected_args == 2:
105
- return lambda a, b: self._dbus_next_signal_publisher(state, a, b)
101
+ return lambda a, b: self._dbus_fast_signal_publisher(state, a, b)
106
102
  elif expected_args == 3:
107
- return lambda a, b, c: self._dbus_next_signal_publisher(state, a, b, c)
103
+ return lambda a, b, c: self._dbus_fast_signal_publisher(state, a, b, c)
108
104
  elif expected_args == 4:
109
- return lambda a, b, c, d: self._dbus_next_signal_publisher(state, a, b, c, d)
105
+ return lambda a, b, c, d: self._dbus_fast_signal_publisher(state, a, b, c, d)
110
106
  raise ValueError("Unsupported nr of arguments")
111
107
 
112
108
  async def _subscribe_interface(self, bus_name: str, path: str, introspection: dbus_introspection.Node, interface: dbus_introspection.Interface, subscription_config: SubscriptionConfig, si: InterfaceConfig) -> SubscribedInterface:
@@ -131,7 +127,7 @@ class DbusClient:
131
127
  "signal_config": signal_config,
132
128
  }
133
129
 
134
- handler = self._dbus_next_signal_handler(interface_signal, dbus_signal_state)
130
+ handler = self._dbus_fast_signal_handler(interface_signal, dbus_signal_state)
135
131
  obj_interface.__getattribute__(on_signal_method_name)(handler)
136
132
  logger.info(f"subscribed with signal_handler: signal={signal_config.signal}, bus_name={bus_name}, path={path}, interface={interface.name}")
137
133
 
@@ -162,12 +158,28 @@ class DbusClient:
162
158
 
163
159
  return new_subscriptions
164
160
 
161
+ async def _introspect(self, bus_name: str, path: str) -> dbus_introspection.Node:
162
+
163
+ if path == "/org/mpris/MediaPlayer2" and bus_name.startswith("org.mpris.MediaPlayer2.vlc"):
164
+ # vlc 3.x branch contains an incomplete dbus introspection
165
+ # https://github.com/videolan/vlc/commit/48e593f164d2bf09b0ca096d88c86d78ec1a2ca0
166
+ # Until vlc 4.x is out we use the official specification instead
167
+ introspection = mpris_introspection_vlc
168
+ else:
169
+ introspection = await self.bus.introspect(bus_name, path)
170
+
171
+ # MPRIS: If no introspection data is available, load a default
172
+ if path == "/org/mpris/MediaPlayer2" and bus_name.startswith("org.mpris.MediaPlayer2.") and len(introspection.interfaces) == 0:
173
+ introspection = mpris_introspection_playerctl
174
+
175
+ return introspection
176
+
165
177
  async def _visit_bus_name_path(self, bus_name: str, path: str) -> list[SubscribedInterface]:
166
178
 
167
179
  new_subscriptions: list[SubscribedInterface] = []
168
180
 
169
181
  try:
170
- introspection = await self.bus.introspect(bus_name, path)
182
+ introspection = await self._introspect(bus_name, path)
171
183
  except TypeError as e:
172
184
  logger.warning(f"bus.introspect failed, bus_name={bus_name}, path={path}: {e}")
173
185
  return new_subscriptions
@@ -223,21 +235,27 @@ class DbusClient:
223
235
  self.flow_scheduler.start_flow_set(subscription_config.flows)
224
236
 
225
237
  # Trigger flows that have a bus_name_added trigger configured
226
- await self._trigger_bus_name_added(subscription_config, subscribed_interface.bus_name)
238
+ await self._trigger_bus_name_added(subscription_config, subscribed_interface.bus_name, subscribed_interface.path)
227
239
 
228
240
  processed_new_subscriptions.add(subscription_config.id)
229
241
 
230
242
  return new_subscriped_interfaces
231
243
 
232
- async def _trigger_bus_name_added(self, subscription_config: SubscriptionConfig, bus_name: str):
244
+ async def _trigger_bus_name_added(self, subscription_config: SubscriptionConfig, bus_name: str, path: str):
233
245
 
234
246
  for flow in subscription_config.flows:
235
247
  for trigger in flow.triggers:
236
248
  if trigger.type == "bus_name_added":
237
- context = {
249
+ trigger_context = {
238
250
  "bus_name": bus_name,
251
+ "path": path
239
252
  }
240
- trigger_message = FlowTriggerMessage(flow, trigger, datetime.now(), context)
253
+ trigger_message = FlowTriggerMessage(
254
+ flow,
255
+ trigger,
256
+ datetime.now(),
257
+ trigger_context=trigger_context
258
+ )
241
259
  await self.event_broker.flow_trigger_queue.async_q.put(trigger_message)
242
260
 
243
261
  async def _handle_bus_name_removed(self, bus_name: str):
@@ -251,7 +269,7 @@ class DbusClient:
251
269
  for subscription_config in subscription_configs:
252
270
 
253
271
  # Trigger flows that have a bus_name_added trigger configured
254
- await self._trigger_bus_name_removed(subscription_config, bus_name)
272
+ await self._trigger_bus_name_removed(subscription_config, bus_name, path)
255
273
 
256
274
  # Stop schedule triggers
257
275
  self.flow_scheduler.stop_flow_set(subscription_config.flows)
@@ -275,16 +293,22 @@ class DbusClient:
275
293
 
276
294
  del self.subscriptions[bus_name]
277
295
 
278
- async def _trigger_bus_name_removed(self, subscription_config: SubscriptionConfig, bus_name: str):
296
+ async def _trigger_bus_name_removed(self, subscription_config: SubscriptionConfig, bus_name: str, path: str):
279
297
 
280
298
  # Trigger flows that have a bus_name_removed trigger configured
281
299
  for flow in subscription_config.flows:
282
300
  for trigger in flow.triggers:
283
301
  if trigger.type == "bus_name_removed":
284
- context = {
302
+ trigger_context = {
285
303
  "bus_name": bus_name,
304
+ "path": path
286
305
  }
287
- trigger_message = FlowTriggerMessage(flow, trigger, datetime.now(), context)
306
+ trigger_message = FlowTriggerMessage(
307
+ flow,
308
+ trigger,
309
+ datetime.now(),
310
+ trigger_context=trigger_context
311
+ )
288
312
  await self.event_broker.flow_trigger_queue.async_q.put(trigger_message)
289
313
 
290
314
  async def _dbus_name_owner_changed_callback(self, name, old_owner, new_owner):
@@ -310,7 +334,7 @@ class DbusClient:
310
334
 
311
335
  return res
312
336
 
313
- async def get_dbus_interface_property(self, interface: dbus_aio.proxy_object.ProxyInterface, property: str):
337
+ async def get_dbus_interface_property(self, interface: dbus_aio.proxy_object.ProxyInterface, property: str) -> Any:
314
338
 
315
339
  call_method_name = "get_" + camel_to_snake(property)
316
340
  res = await interface.__getattribute__(call_method_name)()
@@ -322,17 +346,12 @@ class DbusClient:
322
346
 
323
347
  return res
324
348
 
325
- async def set_dbus_interface_property(self, interface: dbus_aio.proxy_object.ProxyInterface, property: str, value):
349
+ async def set_dbus_interface_property(self, interface: dbus_aio.proxy_object.ProxyInterface, property: str, value: Any) -> None:
326
350
 
327
351
  call_method_name = "set_" + camel_to_snake(property)
328
- res = await interface.__getattribute__(call_method_name)(value)
329
-
330
- if res:
331
- res = unwrap_dbus_object(res)
332
-
333
- logger.info(f"set_dbus_interface_property: bus_name={interface.bus_name}, interface={interface.introspection.name}, property={property}, res={res}")
352
+ await interface.__getattribute__(call_method_name)(value)
334
353
 
335
- return res
354
+ logger.info(f"set_dbus_interface_property: bus_name={interface.bus_name}, interface={interface.introspection.name}, property={property}, value={value}")
336
355
 
337
356
  async def mqtt_receive_queue_processor_task(self):
338
357
  """Continuously processes messages from the async queue."""
@@ -367,13 +386,18 @@ class DbusClient:
367
386
  matches_filter = signal.signal_config.matches_filter(self.app_context.templating, *signal.args)
368
387
 
369
388
  if matches_filter:
370
- context = {
389
+ trigger_context = {
371
390
  "bus_name": signal.bus_name,
372
391
  "path": signal.path,
373
392
  "interface": signal.interface_name,
374
393
  "args": signal.args
375
394
  }
376
- trigger_message = FlowTriggerMessage(flow, trigger, datetime.now(), context)
395
+ trigger_message = FlowTriggerMessage(
396
+ flow,
397
+ trigger,
398
+ datetime.now(),
399
+ trigger_context=trigger_context
400
+ )
377
401
 
378
402
  await self.event_broker.flow_trigger_queue.async_q.put(trigger_message)
379
403
  except Exception as e:
@@ -389,7 +413,7 @@ class DbusClient:
389
413
  for subscription_configs in self.config.subscriptions:
390
414
  for interface_config in subscription_configs.interfaces:
391
415
  # TODO, performance improvement
392
- mqtt_topic = interface_config.render_mqtt_call_method_topic(self.templating, {})
416
+ mqtt_topic = interface_config.render_mqtt_command_topic(self.templating, {})
393
417
  found_matching_topic |= mqtt_topic == msg.topic
394
418
 
395
419
  if not found_matching_topic:
@@ -2,14 +2,14 @@
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
- import dbus_next.aio as dbus_aio
5
+ import dbus_fast.aio as dbus_aio
6
6
 
7
7
  from dbus2mqtt.config import InterfaceConfig, SubscriptionConfig
8
8
 
9
9
 
10
10
  class BusNameSubscriptions:
11
11
 
12
- def __init__(self, bus_name: str):
12
+ def __init__(self, bus_name: str):
13
13
  self.bus_name = bus_name
14
14
  self.path_objects: dict[str, dbus_aio.proxy_object.ProxyObject] = {}
15
15
 
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import re
3
3
 
4
- import dbus_next.signature as dbus_signature
4
+ import dbus_fast.signature as dbus_signature
5
5
 
6
6
 
7
7
  def _variant_serializer(obj):
@@ -10,7 +10,7 @@ def _variant_serializer(obj):
10
10
  return obj
11
11
 
12
12
  def unwrap_dbus_object(o):
13
- # an easy way to get rid of dbus_next.signature.Variant types
13
+ # an easy way to get rid of dbus_fast.signature.Variant types
14
14
  res = json.dumps(o, default=_variant_serializer)
15
15
  json_obj = json.loads(res)
16
16
  return json_obj
@@ -0,0 +1,151 @@
1
+ import dbus_fast.introspection as dbus_introspection
2
+
3
+ # taken from https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-daemon.c#L578
4
+ mpris_introspection_playerctl = dbus_introspection.Node.parse("""\
5
+ <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
6
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
7
+ <node>
8
+ <interface name="org.freedesktop.DBus.Introspectable">
9
+ <method name="Introspect">
10
+ <arg name="data" direction="out" type="s"/>
11
+ </method>
12
+ </interface>
13
+
14
+ <interface name="org.freedesktop.DBus.Properties">
15
+ <method name="Get">
16
+ <arg direction="in" type="s"/>
17
+ <arg direction="in" type="s"/>
18
+ <arg direction="out" type="v"/>
19
+ </method>
20
+ <method name="Set">
21
+ <arg direction="in" type="s"/>
22
+ <arg direction="in" type="s"/>
23
+ <arg direction="in" type="v"/>
24
+ </method>
25
+ <method name="GetAll">
26
+ <arg direction="in" type="s"/>
27
+ <arg direction="out" type="a{sv}"/>
28
+ </method>
29
+ <signal name="PropertiesChanged">
30
+ <arg type="s"/>
31
+ <arg type="a{sv}"/>
32
+ <arg type="as"/>
33
+ </signal>
34
+ </interface>
35
+
36
+ <interface name="org.mpris.MediaPlayer2">
37
+ <property name="CanQuit" type="b" access="read"/>
38
+ <property name="Fullscreen" type="b" access="readwrite"/>
39
+ <property name="CanSetFullscreen" type="b" access="read"/>
40
+ <property name="CanRaise" type="b" access="read"/>
41
+ <property name="HasTrackList" type="b" access="read"/>
42
+ <property name="Identity" type="s" access="read"/>
43
+ <property name="DesktopEntry" type="s" access="read"/>
44
+ <property name="SupportedUriSchemes" type="as" access="read"/>
45
+ <property name="SupportedMimeTypes" type="as" access="read"/>
46
+ <method name="Raise"/>
47
+ <method name="Quit"/>
48
+ </interface>
49
+
50
+ <interface name="org.mpris.MediaPlayer2.Player">
51
+ <property name="PlaybackStatus" type="s" access="read"/>
52
+ <property name="LoopStatus" type="s" access="readwrite"/>
53
+ <property name="Rate" type="d" access="readwrite"/>
54
+ <property name="Shuffle" type="b" access="readwrite"/>
55
+ <property name="Metadata" type="a{sv}" access="read"/>
56
+ <property name="Volume" type="d" access="readwrite"/>
57
+ <property name="Position" type="x" access="read"/>
58
+ <property name="MinimumRate" type="d" access="read"/>
59
+ <property name="MaximumRate" type="d" access="read"/>
60
+ <property name="CanGoNext" type="b" access="read"/>
61
+ <property name="CanGoPrevious" type="b" access="read"/>
62
+ <property name="CanPlay" type="b" access="read"/>
63
+ <property name="CanPause" type="b" access="read"/>
64
+ <property name="CanSeek" type="b" access="read"/>
65
+ <property name="CanControl" type="b" access="read"/>
66
+ <method name="Next"/>
67
+ <method name="Previous"/>
68
+ <method name="Pause"/>
69
+ <method name="PlayPause"/>
70
+ <method name="Stop"/>
71
+ <method name="Play"/>
72
+ <method name="Seek">
73
+ <arg type="x" name="Offset" direction="in"/>
74
+ </method>
75
+ <method name="SetPosition">
76
+ <arg type="o" name="TrackId" direction="in"/>
77
+ <arg type="x" name="Offset" direction="in"/>
78
+ </method>
79
+ <method name="OpenUri">
80
+ <arg type="s" name="Uri" direction="in"/>
81
+ </method>
82
+ <signal name="Seeked">
83
+ <arg type="x" name="Position" direction="out"/>
84
+ </signal>
85
+ </interface>
86
+
87
+ <interface name="org.mpris.MediaPlayer2.TrackList">
88
+ <property name="Tracks" type="ao" access="read">
89
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="invalidates"/>
90
+ </property>
91
+ <property name="CanEditTracks" type="b" access="read">
92
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
93
+ </property>
94
+ <method name="GetTracksMetadata">
95
+ <arg direction="in" name="TrackIds" type="ao"/>
96
+ <arg direction="out" name="Metadata" type="aa{sv}"/>
97
+ </method>
98
+ <method name="AddTrack">
99
+ <arg direction="in" name="Uri" type="s"/>
100
+ <arg direction="in" name="AfterTrack" type="o"/>
101
+ <arg direction="in" name="SetAsCurrent" type="b"/>
102
+ </method>
103
+ <method name="RemoveTrack">
104
+ <arg direction="in" name="TrackId" type="o"/>
105
+ </method>
106
+ <method name="GoTo">
107
+ <arg direction="in" name="TrackId" type="o"/>
108
+ </method>
109
+ <signal name="TrackListReplaced">
110
+ <arg name="Tracks" type="ao"/>
111
+ <arg name="CurrentTrack" type="o"/>
112
+ </signal>
113
+ <signal name="TrackAdded">
114
+ <arg name="Metadata" type="a{sv}"/>
115
+ <arg name="AfterTrack" type="o"/>
116
+ </signal>
117
+ <signal name="TrackRemoved">
118
+ <arg name="TrackId" type="o"/>
119
+ </signal>
120
+ <signal name="TrackMetadataChanged">
121
+ <arg name="TrackId" type="o"/>
122
+ <arg name="Metadata" type="a{sv}"/>
123
+ </signal>
124
+ </interface>
125
+
126
+ <interface name="org.mpris.MediaPlayer2.Playlists">
127
+ <method name="ActivatePlaylist">
128
+ <arg direction="in" name="PlaylistId" type="o"/>
129
+ </method>
130
+ <method name="GetPlaylists">
131
+ <arg direction="in" name="Index" type="u"/>
132
+ <arg direction="in" name="MaxCount" type="u"/>
133
+ <arg direction="in" name="Order" type="s"/>
134
+ <arg direction="in" name="ReverseOrder" type="b"/>
135
+ <arg direction="out" name="Playlists" type="a(oss)"/>
136
+ </method>
137
+ <property name="PlaylistCount" type="u" access="read">
138
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
139
+ </property>
140
+ <property name="Orderings" type="as" access="read">
141
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
142
+ </property>
143
+ <property name="ActivePlaylist" type="(b(oss))" access="read">
144
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
145
+ </property>
146
+ <signal name="PlaylistChanged">
147
+ <arg name="Playlist" type="(oss)"/>
148
+ </signal>
149
+ </interface>
150
+ </node>
151
+ """)
@@ -0,0 +1,122 @@
1
+ import dbus_fast.introspection as dbus_introspection
2
+
3
+ # taken from https://code.videolan.org/videolan/vlc/-/blob/master/modules/control/dbus/dbus_introspect.h
4
+ mpris_introspection_vlc = dbus_introspection.Node.parse("""\
5
+ <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
6
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
7
+ <node>
8
+ <interface name="org.freedesktop.DBus.Introspectable">
9
+ <method name="Introspect">
10
+ <arg name="data" direction="out" type="s"/>
11
+ </method>
12
+ </interface>
13
+
14
+ <interface name="org.freedesktop.DBus.Properties">
15
+ <method name="Get">
16
+ <arg direction="in" type="s"/>
17
+ <arg direction="in" type="s"/>
18
+ <arg direction="out" type="v"/>
19
+ </method>
20
+ <method name="Set">
21
+ <arg direction="in" type="s"/>
22
+ <arg direction="in" type="s"/>
23
+ <arg direction="in" type="v"/>
24
+ </method>
25
+ <method name="GetAll">
26
+ <arg direction="in" type="s"/>
27
+ <arg direction="out" type="a{sv}"/>
28
+ </method>
29
+ <signal name="PropertiesChanged">
30
+ <arg type="s"/>
31
+ <arg type="a{sv}"/>
32
+ <arg type="as"/>
33
+ </signal>
34
+ </interface>
35
+
36
+ <interface name="org.mpris.MediaPlayer2">
37
+ <property name="CanQuit" type="b" access="read"/>
38
+ <property name="Fullscreen" type="b" access="readwrite"/>
39
+ <property name="CanSetFullscreen" type="b" access="read"/>
40
+ <property name="CanRaise" type="b" access="read"/>
41
+ <property name="HasTrackList" type="b" access="read"/>
42
+ <property name="Identity" type="s" access="read"/>
43
+ <property name="DesktopEntry" type="s" access="read"/>
44
+ <property name="SupportedUriSchemes" type="as" access="read"/>
45
+ <property name="SupportedMimeTypes" type="as" access="read"/>
46
+ <method name="Raise"/>
47
+ <method name="Quit"/>
48
+ </interface>
49
+
50
+ <interface name="org.mpris.MediaPlayer2.Player">
51
+ <property name="PlaybackStatus" type="s" access="read"/>
52
+ <property name="LoopStatus" type="s" access="readwrite"/>
53
+ <property name="Rate" type="d" access="readwrite"/>
54
+ <property name="Shuffle" type="b" access="readwrite"/>
55
+ <property name="Metadata" type="a{sv}" access="read"/>
56
+ <property name="Volume" type="d" access="readwrite"/>
57
+ <property name="Position" type="x" access="read"/>
58
+ <property name="MinimumRate" type="d" access="read"/>
59
+ <property name="MaximumRate" type="d" access="read"/>
60
+ <property name="CanGoNext" type="b" access="read"/>
61
+ <property name="CanGoPrevious" type="b" access="read"/>
62
+ <property name="CanPlay" type="b" access="read"/>
63
+ <property name="CanPause" type="b" access="read"/>
64
+ <property name="CanSeek" type="b" access="read"/>
65
+ <property name="CanControl" type="b" access="read"/>
66
+ <method name="Next"/>
67
+ <method name="Previous"/>
68
+ <method name="Pause"/>
69
+ <method name="PlayPause"/>
70
+ <method name="Stop"/>
71
+ <method name="Play"/>
72
+ <method name="Seek">
73
+ <arg type="x" direction="in"/>
74
+ </method>
75
+ <method name="SetPosition">
76
+ <arg type="o" direction="in"/>
77
+ <arg type="x" direction="in"/>
78
+ </method>
79
+ <method name="OpenUri">
80
+ <arg type="s" direction="in"/>
81
+ </method>
82
+ <signal name="Seeked">
83
+ <arg type="x"/>
84
+ </signal>
85
+ </interface>
86
+
87
+ <interface name="org.mpris.MediaPlayer2.TrackList">
88
+ <property name="Tracks" type="ao" access="read"/>
89
+ <property name="CanEditTracks" type="b" access="read"/>
90
+ <method name="GetTracksMetadata">
91
+ <arg type="ao" direction="in"/>
92
+ <arg type="aa{sv}" direction="out"/>
93
+ </method>
94
+ <method name="AddTrack">
95
+ <arg type="s" direction="in"/>
96
+ <arg type="o" direction="in"/>
97
+ <arg type="b" direction="in"/>
98
+ </method>
99
+ <method name="RemoveTrack">
100
+ <arg type="o" direction="in"/>
101
+ </method>
102
+ <method name="GoTo">
103
+ <arg type="o" direction="in"/>
104
+ </method>
105
+ <signal name="TrackListReplaced">
106
+ <arg type="ao"/>
107
+ <arg type="o"/>
108
+ </signal>
109
+ <signal name="TrackAdded">
110
+ <arg type="a{sv}"/>
111
+ <arg type="o"/>
112
+ </signal>
113
+ <signal name="TrackRemoved">
114
+ <arg type="o"/>
115
+ </signal>
116
+ <signal name="TrackMetadataChanged">
117
+ <arg type="o"/>
118
+ <arg type="a{sv}"/>
119
+ </signal>
120
+ </interface>
121
+ </node>
122
+ """)