policy-endpoint-device.lua 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. -- WirePlumber
  2. --
  3. -- Copyright © 2021 Collabora Ltd.
  4. -- @author Julian Bouzas <julian.bouzas@collabora.com>
  5. --
  6. -- SPDX-License-Identifier: MIT
  7. -- Receive script arguments from config.lua
  8. local config = ... or {}
  9. -- ensure config.move and config.follow are not nil
  10. config.move = config.move or false
  11. config.follow = config.follow or false
  12. local self = {}
  13. self.scanning = false
  14. self.pending_rescan = false
  15. function rescan ()
  16. -- check endpoints and register new links
  17. for si_ep in endpoints_om:iterate() do
  18. handleEndpoint (si_ep)
  19. end
  20. end
  21. function scheduleRescan ()
  22. if self.scanning then
  23. self.pending_rescan = true
  24. return
  25. end
  26. self.scanning = true
  27. rescan ()
  28. self.scanning = false
  29. if self.pending_rescan then
  30. self.pending_rescan = false
  31. Core.sync(function ()
  32. scheduleRescan ()
  33. end)
  34. end
  35. end
  36. function findTargetByDefaultNode (target_media_class)
  37. local def_id = default_nodes:call("get-default-node", target_media_class)
  38. if def_id ~= Id.INVALID then
  39. for si_target in linkables_om:iterate() do
  40. local target_node = si_target:get_associated_proxy ("node")
  41. if target_node["bound-id"] == def_id then
  42. return si_target
  43. end
  44. end
  45. end
  46. return nil
  47. end
  48. function findTargetByFirstAvailable (target_media_class)
  49. for si_target in linkables_om:iterate() do
  50. local target_node = si_target:get_associated_proxy ("node")
  51. if target_node.properties["media.class"] == target_media_class then
  52. return si_target
  53. end
  54. end
  55. return nil
  56. end
  57. function findUndefinedTarget (si_ep)
  58. local media_class = si_ep.properties["media.class"]
  59. local target_class_assoc = {
  60. ["Audio/Source"] = "Audio/Source",
  61. ["Audio/Sink"] = "Audio/Sink",
  62. ["Video/Source"] = "Video/Source",
  63. }
  64. local target_media_class = target_class_assoc[media_class]
  65. if not target_media_class then
  66. return nil
  67. end
  68. local si_target = findTargetByDefaultNode (target_media_class)
  69. if not si_target then
  70. si_target = findTargetByFirstAvailable (target_media_class)
  71. end
  72. return si_target
  73. end
  74. function createLink (si_ep, si_target)
  75. local out_item = nil
  76. local in_item = nil
  77. local ep_props = si_ep.properties
  78. local target_props = si_target.properties
  79. if target_props["item.node.direction"] == "input" then
  80. -- playback
  81. out_item = si_ep
  82. in_item = si_target
  83. else
  84. -- capture
  85. in_item = si_ep
  86. out_item = si_target
  87. end
  88. Log.info (string.format("link %s <-> %s",
  89. ep_props["name"],
  90. target_props["node.name"]))
  91. -- create and configure link
  92. local si_link = SessionItem ( "si-standard-link" )
  93. if not si_link:configure {
  94. ["out.item"] = out_item,
  95. ["in.item"] = in_item,
  96. ["out.item.port.context"] = "output",
  97. ["in.item.port.context"] = "input",
  98. ["passive"] = true,
  99. ["is.policy.endpoint.device.link"] = true,
  100. } then
  101. Log.warning (si_link, "failed to configure si-standard-link")
  102. return
  103. end
  104. -- register
  105. si_link:register ()
  106. -- activate
  107. si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
  108. if e then
  109. Log.warning (l, "failed to activate si-standard-link: " .. tostring(e))
  110. l:remove ()
  111. else
  112. Log.info (l, "activated si-standard-link")
  113. end
  114. end)
  115. end
  116. function handleEndpoint (si_ep)
  117. Log.info (si_ep, "handling endpoint " .. si_ep.properties["name"])
  118. -- find proper target item
  119. local si_target = findUndefinedTarget (si_ep)
  120. if not si_target then
  121. Log.info (si_ep, "... target item not found")
  122. return
  123. end
  124. -- Check if item is linked to proper target, otherwise re-link
  125. for link in links_om:iterate() do
  126. local out_id = tonumber(link.properties["out.item.id"])
  127. local in_id = tonumber(link.properties["in.item.id"])
  128. if out_id == si_ep.id or in_id == si_ep.id then
  129. local is_out = out_id == si_ep.id and true or false
  130. for peer in linkables_om:iterate() do
  131. if peer.id == (is_out and in_id or out_id) then
  132. if peer.id == si_target.id then
  133. Log.info (si_ep, "... already linked to proper target")
  134. return
  135. end
  136. -- remove old link if active, otherwise schedule rescan
  137. if ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
  138. link:remove ()
  139. Log.info (si_ep, "... moving to new target")
  140. else
  141. scheduleRescan ()
  142. Log.info (si_ep, "... scheduled rescan")
  143. return
  144. end
  145. end
  146. end
  147. end
  148. end
  149. -- create new link
  150. createLink (si_ep, si_target)
  151. end
  152. function unhandleLinkable (si)
  153. si_props = si.properties
  154. Log.info (si, string.format("unhandling item: %s (%s)",
  155. tostring(si_props["node.name"]), tostring(si_props["node.id"])))
  156. -- remove any links associated with this item
  157. for silink in links_om:iterate() do
  158. local out_id = tonumber (silink.properties["out.item.id"])
  159. local in_id = tonumber (silink.properties["in.item.id"])
  160. if out_id == si.id or in_id == si.id then
  161. silink:remove ()
  162. Log.info (silink, "... link removed")
  163. end
  164. end
  165. end
  166. default_nodes = Plugin.find("default-nodes-api")
  167. endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
  168. linkables_om = ObjectManager {
  169. Interest {
  170. type = "SiLinkable",
  171. -- only handle device si-audio-adapter items
  172. Constraint { "item.factory.name", "=", "si-audio-adapter", type = "pw-global" },
  173. Constraint { "item.node.type", "=", "device", type = "pw-global" },
  174. Constraint { "active-features", "!", 0, type = "gobject" },
  175. }
  176. }
  177. links_om = ObjectManager {
  178. Interest {
  179. type = "SiLink",
  180. -- only handle links created by this policy
  181. Constraint { "is.policy.endpoint.device.link", "=", true, type = "pw-global" },
  182. }
  183. }
  184. -- listen for default node changes if config.follow is enabled
  185. if config.follow then
  186. default_nodes:connect("changed", function (p)
  187. scheduleRescan ()
  188. end)
  189. end
  190. linkables_om:connect("objects-changed", function (om)
  191. scheduleRescan ()
  192. end)
  193. endpoints_om:connect("object-added", function (om)
  194. scheduleRescan ()
  195. end)
  196. linkables_om:connect("object-removed", function (om, si)
  197. unhandleLinkable (si)
  198. end)
  199. endpoints_om:activate()
  200. linkables_om:activate()
  201. links_om:activate()