ffado-webmixer 1.2.1__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.
Files changed (243) hide show
  1. ffado_webmixer/__init__.py +3 -0
  2. ffado_webmixer/__main__.py +4 -0
  3. ffado_webmixer/access.py +125 -0
  4. ffado_webmixer/api/__init__.py +1 -0
  5. ffado_webmixer/api/events.py +50 -0
  6. ffado_webmixer/api/integrations.py +41 -0
  7. ffado_webmixer/api/mixer.py +729 -0
  8. ffado_webmixer/api/settings.py +238 -0
  9. ffado_webmixer/api/stereo.py +17 -0
  10. ffado_webmixer/assets/icon.svg +13 -0
  11. ffado_webmixer/assets/static/QR-LICENSE.txt +24 -0
  12. ffado_webmixer/assets/static/desktop-integration.css +18 -0
  13. ffado_webmixer/assets/static/desktop-integration.js +41 -0
  14. ffado_webmixer/assets/static/editable-input.js +73 -0
  15. ffado_webmixer/assets/static/frontend-api.js +70 -0
  16. ffado_webmixer/assets/static/index.css +553 -0
  17. ffado_webmixer/assets/static/index.html +294 -0
  18. ffado_webmixer/assets/static/index.js +2133 -0
  19. ffado_webmixer/assets/static/labels.js +53 -0
  20. ffado_webmixer/assets/static/live-sync.js +167 -0
  21. ffado_webmixer/assets/static/loading.css +130 -0
  22. ffado_webmixer/assets/static/loading.html +47 -0
  23. ffado_webmixer/assets/static/loading.js +1 -0
  24. ffado_webmixer/assets/static/native-title.js +22 -0
  25. ffado_webmixer/assets/static/peak-metering.js +290 -0
  26. ffado_webmixer/assets/static/qrcode.js +1276 -0
  27. ffado_webmixer/assets/static/remote-access.js +89 -0
  28. ffado_webmixer/assets/static/routing.css +148 -0
  29. ffado_webmixer/assets/static/routing.js +783 -0
  30. ffado_webmixer/assets/static/themes.js +57 -0
  31. ffado_webmixer/assets/themes/breeze.css +43 -0
  32. ffado_webmixer/assets/themes/catppuccin.css +43 -0
  33. ffado_webmixer/assets/themes/dark.css +43 -0
  34. ffado_webmixer/assets/themes/everforest.css +43 -0
  35. ffado_webmixer/assets/themes/gruvbox.css +43 -0
  36. ffado_webmixer/assets/themes/high-contrast.css +43 -0
  37. ffado_webmixer/assets/themes/icons/breeze.svg +9 -0
  38. ffado_webmixer/assets/themes/icons/catppuccin.svg +9 -0
  39. ffado_webmixer/assets/themes/icons/dark.svg +9 -0
  40. ffado_webmixer/assets/themes/icons/everforest.svg +9 -0
  41. ffado_webmixer/assets/themes/icons/gruvbox.svg +9 -0
  42. ffado_webmixer/assets/themes/icons/high-contrast.svg +9 -0
  43. ffado_webmixer/assets/themes/icons/light.svg +9 -0
  44. ffado_webmixer/assets/themes/icons/midnight.svg +9 -0
  45. ffado_webmixer/assets/themes/icons/nordic.svg +9 -0
  46. ffado_webmixer/assets/themes/icons/tokyonight.svg +9 -0
  47. ffado_webmixer/assets/themes/light.css +43 -0
  48. ffado_webmixer/assets/themes/midnight.css +43 -0
  49. ffado_webmixer/assets/themes/nordic.css +43 -0
  50. ffado_webmixer/assets/themes/tokyonight.css +43 -0
  51. ffado_webmixer/client.py +136 -0
  52. ffado_webmixer/config.py +371 -0
  53. ffado_webmixer/controls.py +18 -0
  54. ffado_webmixer/debug_backend.py +191 -0
  55. ffado_webmixer/demo.py +63 -0
  56. ffado_webmixer/desktop_integration.py +121 -0
  57. ffado_webmixer/device_manager.py +192 -0
  58. ffado_webmixer/device_registry.py +108 -0
  59. ffado_webmixer/gain.py +84 -0
  60. ffado_webmixer/helper.py +94 -0
  61. ffado_webmixer/interrogate.py +251 -0
  62. ffado_webmixer/main.py +303 -0
  63. ffado_webmixer/mixer.py +1081 -0
  64. ffado_webmixer/mixer_outputs.py +202 -0
  65. ffado_webmixer/mixer_routing.py +358 -0
  66. ffado_webmixer/native_icons.py +144 -0
  67. ffado_webmixer/peak_metering.py +82 -0
  68. ffado_webmixer/profile_loader.py +6 -0
  69. ffado_webmixer/profiles/__init__.py +1 -0
  70. ffado_webmixer/profiles/audiofire/audiofire12_001486_0000af12.xml +31 -0
  71. ffado_webmixer/profiles/audiofire/audiofire2_001486_00000af2.xml +31 -0
  72. ffado_webmixer/profiles/audiofire/audiofire4_001486_00000af4.xml +31 -0
  73. ffado_webmixer/profiles/audiofire/audiofire8_001486_00000af8.xml +31 -0
  74. ffado_webmixer/profiles/audiofire/audiofire8a_001486_00000af9.xml +31 -0
  75. ffado_webmixer/profiles/audiofire/common.xml +27 -0
  76. ffado_webmixer/profiles/audiofire/reference.xml +79 -0
  77. ffado_webmixer/profiles/bcoaudio5control/audio_5_0007f5_00010049.xml +31 -0
  78. ffado_webmixer/profiles/bcoaudio5control/common.xml +27 -0
  79. ffado_webmixer/profiles/bcoaudio5control/reference.xml +55 -0
  80. ffado_webmixer/profiles/capabilities.py +90 -0
  81. ffado_webmixer/profiles/driver_only/adc_2_001c6a_00000001.xml +31 -0
  82. ffado_webmixer/profiles/driver_only/afi_1_001c6a_00000004.xml +31 -0
  83. ffado_webmixer/profiles/driver_only/audiofire12hd_001486_000af12d.xml +31 -0
  84. ffado_webmixer/profiles/driver_only/axum_firewire_io_card_16x16_000f64_00000003.xml +31 -0
  85. ffado_webmixer/profiles/driver_only/common.xml +4 -0
  86. ffado_webmixer/profiles/driver_only/dac_202_001c6a_00000007.xml +31 -0
  87. ffado_webmixer/profiles/driver_only/desktop_konnekt_6_000166_00000024.xml +31 -0
  88. ffado_webmixer/profiles/driver_only/diceii_evm_1_000166_00000001.xml +31 -0
  89. ffado_webmixer/profiles/driver_only/diceii_evm_2_000166_00000002.xml +31 -0
  90. ffado_webmixer/profiles/driver_only/diceii_evm_4_000166_00000004.xml +31 -0
  91. ffado_webmixer/profiles/driver_only/duet_0003db_0001dddd.xml +31 -0
  92. ffado_webmixer/profiles/driver_only/ear_master_one_000aac_00000002.xml +31 -0
  93. ffado_webmixer/profiles/driver_only/fca202_001564_0000fc22.xml +31 -0
  94. ffado_webmixer/profiles/driver_only/fireworks_8_001486_000000f8.xml +31 -0
  95. ffado_webmixer/profiles/driver_only/firexon_001a9e_00000001.xml +31 -0
  96. ffado_webmixer/profiles/driver_only/flex_5000_001c2d_00000001.xml +31 -0
  97. ffado_webmixer/profiles/driver_only/fw_hdmi_001486_0000afd1.xml +31 -0
  98. ffado_webmixer/profiles/driver_only/hb_24u_001496_00000000.xml +31 -0
  99. ffado_webmixer/profiles/driver_only/iffwdm_00022e_00010067.xml +31 -0
  100. ffado_webmixer/profiles/driver_only/impacttwin_000166_00000027.xml +31 -0
  101. ffado_webmixer/profiles/driver_only/int_202_001c6a_00000006.xml +31 -0
  102. ffado_webmixer/profiles/driver_only/io_14_000595_00000001.xml +31 -0
  103. ffado_webmixer/profiles/driver_only/konnekt_24d_000166_00000020.xml +31 -0
  104. ffado_webmixer/profiles/driver_only/konnekt_8_000166_00000021.xml +31 -0
  105. ffado_webmixer/profiles/driver_only/konnekt_live_000166_00000023.xml +31 -0
  106. ffado_webmixer/profiles/driver_only/matrix_k_fw_00000a_00030000.xml +31 -0
  107. ffado_webmixer/profiles/driver_only/mbox_2_pro_00a07e_000000a9.xml +31 -0
  108. ffado_webmixer/profiles/driver_only/mbox_3_pro_00a07e_00000004.xml +31 -0
  109. ffado_webmixer/profiles/driver_only/minerva_001c6a_00000003.xml +31 -0
  110. ffado_webmixer/profiles/driver_only/multimix_12_multimix_16_firewire_000595_00000000.xml +31 -0
  111. ffado_webmixer/profiles/driver_only/nrv10_000d6c_00010081.xml +31 -0
  112. ffado_webmixer/profiles/driver_only/onyx_1200f_000ff2_0001200f.xml +31 -0
  113. ffado_webmixer/profiles/driver_only/onyx_1640i_dice_000ff2_00000006.xml +31 -0
  114. ffado_webmixer/profiles/driver_only/onyx_1640i_oxford_000ff2_00001640.xml +31 -0
  115. ffado_webmixer/profiles/driver_only/onyx_400f_000ff2_0000400f.xml +31 -0
  116. ffado_webmixer/profiles/driver_only/onyx_firewire_00000f_00010065.xml +31 -0
  117. ffado_webmixer/profiles/driver_only/onyx_i_000ff2_00081216.xml +31 -0
  118. ffado_webmixer/profiles/driver_only/profire_lightbridge_000d6c_000100a1.xml +31 -0
  119. ffado_webmixer/profiles/driver_only/rd_audio1_0007f5_00010048.xml +31 -0
  120. ffado_webmixer/profiles/driver_only/rosetta_200_0003db_00010048.xml +31 -0
  121. ffado_webmixer/profiles/driver_only/scs_1m_001260_00001000.xml +31 -0
  122. ffado_webmixer/profiles/driver_only/studio_konnekt_48_000166_00000022.xml +31 -0
  123. ffado_webmixer/profiles/driver_only/studiolive_1602_000a92_00000013.xml +31 -0
  124. ffado_webmixer/profiles/driver_only/studiolive_1642_000a92_00000010.xml +31 -0
  125. ffado_webmixer/profiles/driver_only/studiolive_2442_000a92_00000012.xml +31 -0
  126. ffado_webmixer/profiles/driver_only/studiolive_3242_mk2_000a92_00000014.xml +31 -0
  127. ffado_webmixer/profiles/driver_only/tag_dac1_001c6a_00000005.xml +31 -0
  128. ffado_webmixer/profiles/driver_only/ufx_1604_mixer_001564_00001604.xml +31 -0
  129. ffado_webmixer/profiles/driver_only/vesta_001c6a_00000002.xml +31 -0
  130. ffado_webmixer/profiles/driver_only/x32_001564_00000006.xml +31 -0
  131. ffado_webmixer/profiles/edirolfa101control/common.xml +10 -0
  132. ffado_webmixer/profiles/edirolfa101control/fa_101_0040ab_00010048.xml +31 -0
  133. ffado_webmixer/profiles/edirolfa101control/reference.xml +50 -0
  134. ffado_webmixer/profiles/edirolfa66control/common.xml +27 -0
  135. ffado_webmixer/profiles/edirolfa66control/fa_66_0040ab_00010049.xml +31 -0
  136. ffado_webmixer/profiles/edirolfa66control/reference.xml +61 -0
  137. ffado_webmixer/profiles/ffado_configuration +861 -0
  138. ffado_webmixer/profiles/generic_dice_eap/common.xml +15 -0
  139. ffado_webmixer/profiles/generic_dice_eap/firestudio_26x26_000a92_00000008.xml +31 -0
  140. ffado_webmixer/profiles/generic_dice_eap/firestudio_mobile_000a92_00000011.xml +31 -0
  141. ffado_webmixer/profiles/generic_dice_eap/firestudio_project_000a92_0000000b.xml +31 -0
  142. ffado_webmixer/profiles/generic_dice_eap/firestudio_tube_000a92_0000000c.xml +31 -0
  143. ffado_webmixer/profiles/generic_dice_eap/i_onix_fw810s_000fd7_00000001.xml +31 -0
  144. ffado_webmixer/profiles/generic_dice_eap/onyx_blackbird_000ff2_00000007.xml +31 -0
  145. ffado_webmixer/profiles/generic_dice_eap/reference.xml +18 -0
  146. ffado_webmixer/profiles/generic_dice_eap/venice_f32_10c73f_00000001.xml +31 -0
  147. ffado_webmixer/profiles/generic_dice_eap/zed_r16_0004c4_00000000.xml +31 -0
  148. ffado_webmixer/profiles/globalmixer/common.xml +11 -0
  149. ffado_webmixer/profiles/globalmixer/reference.xml +30 -0
  150. ffado_webmixer/profiles/loader.py +142 -0
  151. ffado_webmixer/profiles/mackieonyx/common.xml +4 -0
  152. ffado_webmixer/profiles/mackieonyx/onyx_firewire_00000f_00010067.xml +31 -0
  153. ffado_webmixer/profiles/mackieonyx/reference.xml +15 -0
  154. ffado_webmixer/profiles/maudio_bebob/common.xml +16 -0
  155. ffado_webmixer/profiles/maudio_bebob/fw_1814_000d6c_00010071.xml +31 -0
  156. ffado_webmixer/profiles/maudio_bebob/fw_410_000d6c_00010046.xml +31 -0
  157. ffado_webmixer/profiles/maudio_bebob/fw_audiophile_000d6c_00010060.xml +31 -0
  158. ffado_webmixer/profiles/maudio_bebob/fw_solo_000d6c_00010062.xml +31 -0
  159. ffado_webmixer/profiles/maudio_bebob/ozonic_000d6c_0000000a.xml +31 -0
  160. ffado_webmixer/profiles/maudio_bebob/projectmix_i_o_000d6c_00010091.xml +31 -0
  161. ffado_webmixer/profiles/maudio_bebob/reference.xml +57 -0
  162. ffado_webmixer/profiles/motu/all_pre_mark3_devices_0001f2_00000000.xml +31 -0
  163. ffado_webmixer/profiles/motu/common.xml +529 -0
  164. ffado_webmixer/profiles/motu/motu_model_1.xml +29 -0
  165. ffado_webmixer/profiles/motu/motu_model_2.xml +29 -0
  166. ffado_webmixer/profiles/motu/motu_model_4.xml +29 -0
  167. ffado_webmixer/profiles/motu/motu_model_6.xml +29 -0
  168. ffado_webmixer/profiles/motu/motu_ultralite.xml +39 -0
  169. ffado_webmixer/profiles/motu/reference.xml +1613 -0
  170. ffado_webmixer/profiles/motu_mark3/all_mark3_devices_0001f2_00000001.xml +31 -0
  171. ffado_webmixer/profiles/motu_mark3/common.xml +23 -0
  172. ffado_webmixer/profiles/motu_mark3/reference.xml +31 -0
  173. ffado_webmixer/profiles/phase24control/common.xml +32 -0
  174. ffado_webmixer/profiles/phase24control/phase_x24_fw_model_version_4_000aac_00000004.xml +31 -0
  175. ffado_webmixer/profiles/phase24control/phase_x24_fw_model_version_7_000aac_00000007.xml +31 -0
  176. ffado_webmixer/profiles/phase24control/reference.xml +72 -0
  177. ffado_webmixer/profiles/phase88control/common.xml +35 -0
  178. ffado_webmixer/profiles/phase88control/phase_88_fw_000aac_00000003.xml +31 -0
  179. ffado_webmixer/profiles/phase88control/reference.xml +123 -0
  180. ffado_webmixer/profiles/presonus_firebox/common.xml +14 -0
  181. ffado_webmixer/profiles/presonus_firebox/firebox_000a92_00010000.xml +31 -0
  182. ffado_webmixer/profiles/presonus_firebox/reference.xml +19 -0
  183. ffado_webmixer/profiles/presonus_fp10/common.xml +11 -0
  184. ffado_webmixer/profiles/presonus_fp10/firepod_000a92_00010066.xml +31 -0
  185. ffado_webmixer/profiles/presonus_fp10/reference.xml +14 -0
  186. ffado_webmixer/profiles/presonus_inspire1394/common.xml +13 -0
  187. ffado_webmixer/profiles/presonus_inspire1394/inspire1394_000a92_00010001.xml +31 -0
  188. ffado_webmixer/profiles/presonus_inspire1394/reference.xml +16 -0
  189. ffado_webmixer/profiles/profire2626/common.xml +10 -0
  190. ffado_webmixer/profiles/profire2626/profire_2626_000d6c_00000010.xml +31 -0
  191. ffado_webmixer/profiles/profire2626/profire_610_000d6c_00000011.xml +31 -0
  192. ffado_webmixer/profiles/profire2626/reference.xml +39 -0
  193. ffado_webmixer/profiles/quatafire/common.xml +30 -0
  194. ffado_webmixer/profiles/quatafire/quatafire_610_000f1b_00010064.xml +31 -0
  195. ffado_webmixer/profiles/quatafire/quatafire_610_variant_000f1b_00000210.xml +31 -0
  196. ffado_webmixer/profiles/quatafire/reference.xml +80 -0
  197. ffado_webmixer/profiles/resolver.py +242 -0
  198. ffado_webmixer/profiles/rme/common.xml +154 -0
  199. ffado_webmixer/profiles/rme/fireface400_000a35_00000002.xml +31 -0
  200. ffado_webmixer/profiles/rme/fireface800_000a35_00000001.xml +31 -0
  201. ffado_webmixer/profiles/rme/fireface_802_000a35_00000005.xml +31 -0
  202. ffado_webmixer/profiles/rme/fireface_ucx_000a35_00000004.xml +31 -0
  203. ffado_webmixer/profiles/rme/fireface_ufx_000a35_00000003.xml +31 -0
  204. ffado_webmixer/profiles/rme/reference.xml +355 -0
  205. ffado_webmixer/profiles/saffire/common.xml +290 -0
  206. ffado_webmixer/profiles/saffire/reference.xml +1023 -0
  207. ffado_webmixer/profiles/saffire/saffire_le_00130e_00000000.xml +31 -0
  208. ffado_webmixer/profiles/saffire_base/common.xml +4 -0
  209. ffado_webmixer/profiles/saffire_base/reference.xml +8 -0
  210. ffado_webmixer/profiles/saffire_dice/common.xml +72 -0
  211. ffado_webmixer/profiles/saffire_dice/reference.xml +98 -0
  212. ffado_webmixer/profiles/saffire_dice/saffire_pro_14_00130e_00000009.xml +31 -0
  213. ffado_webmixer/profiles/saffire_dice/saffire_pro_24_00130e_00000007.xml +31 -0
  214. ffado_webmixer/profiles/saffire_dice/saffire_pro_24_dsp_00130e_00000008.xml +31 -0
  215. ffado_webmixer/profiles/saffire_dice/saffire_pro_26_00130e_00000012.xml +31 -0
  216. ffado_webmixer/profiles/saffire_dice/saffire_pro_40_00130e_00000005.xml +31 -0
  217. ffado_webmixer/profiles/saffire_dice/saffire_pro_40_1_00130e_00000013.xml +31 -0
  218. ffado_webmixer/profiles/saffire_dice/saffire_pro_40_1_00130e_000000de.xml +31 -0
  219. ffado_webmixer/profiles/saffirepro/common.xml +261 -0
  220. ffado_webmixer/profiles/saffirepro/reference.xml +803 -0
  221. ffado_webmixer/profiles/saffirepro/saffire_pro10io_00130e_00000006.xml +31 -0
  222. ffado_webmixer/profiles/saffirepro/saffire_pro26io_00130e_00000003.xml +31 -0
  223. ffado_webmixer/profiles/yamahago/common.xml +29 -0
  224. ffado_webmixer/profiles/yamahago/go44_00a0de_0010000b.xml +31 -0
  225. ffado_webmixer/profiles/yamahago/go46_00a0de_0010000c.xml +31 -0
  226. ffado_webmixer/profiles/yamahago/reference.xml +94 -0
  227. ffado_webmixer/qjackctl.py +57 -0
  228. ffado_webmixer/remote_access.py +221 -0
  229. ffado_webmixer/server.py +196 -0
  230. ffado_webmixer/services/__init__.py +1 -0
  231. ffado_webmixer/services/events.py +48 -0
  232. ffado_webmixer/services/peak_stream.py +97 -0
  233. ffado_webmixer/services/stereo.py +121 -0
  234. ffado_webmixer/services/streams.py +46 -0
  235. ffado_webmixer/soft_mute.py +76 -0
  236. ffado_webmixer/theme_icons.py +100 -0
  237. ffado_webmixer/themes.py +19 -0
  238. ffado_webmixer-1.2.1.dist-info/METADATA +116 -0
  239. ffado_webmixer-1.2.1.dist-info/RECORD +243 -0
  240. ffado_webmixer-1.2.1.dist-info/WHEEL +5 -0
  241. ffado_webmixer-1.2.1.dist-info/entry_points.txt +2 -0
  242. ffado_webmixer-1.2.1.dist-info/licenses/LICENSE +338 -0
  243. ffado_webmixer-1.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ """FFADO WebMixer package and distribution version."""
2
+
3
+ __version__ = '1.2.1'
@@ -0,0 +1,4 @@
1
+ from .main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,125 @@
1
+ """Listener policy and browser request checks for local and LAN control."""
2
+ from dataclasses import dataclass, field
3
+ from ipaddress import ip_address
4
+ from typing import TYPE_CHECKING
5
+ from urllib.parse import urlsplit
6
+ from .remote_access import DEFAULT_PORT
7
+
8
+ if TYPE_CHECKING:
9
+ from .remote_access import RemoteAccessConfig
10
+
11
+
12
+ def bind_address(value):
13
+ """Accept explicit IP addresses, with localhost as a loopback shorthand."""
14
+ if value == 'localhost':
15
+ return '127.0.0.1'
16
+ try:
17
+ return str(ip_address(value))
18
+ except ValueError as exc:
19
+ raise ValueError('--host must be an IP address or localhost') from exc
20
+
21
+
22
+ def _loopback(value):
23
+ try:
24
+ address = ip_address(value)
25
+ if address.version == 6 and address.ipv4_mapped:
26
+ address = address.ipv4_mapped
27
+ return address.is_loopback
28
+ except ValueError:
29
+ return False
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class AccessPolicy:
34
+ host: str = '127.0.0.1'
35
+ remote_access: 'RemoteAccessConfig | None' = field(default=None, repr=False)
36
+
37
+ def __post_init__(self):
38
+ object.__setattr__(self, 'host', bind_address(self.host))
39
+ if not _loopback(self.host) and not (
40
+ self.remote_access and self.remote_access.enabled and self.remote_access.configured):
41
+ raise ValueError('LAN access requires a saved username and password; run --enable-remote-access first')
42
+
43
+ def permits_local(self, remote_address, hostname):
44
+ # Requiring a loopback Host as well prevents DNS rebinding from turning
45
+ # a hostile website into a password-free local client. Forwarded headers
46
+ # are deliberately not trusted to grant this exception.
47
+ return _loopback(remote_address or '') and (
48
+ hostname == 'localhost' or _loopback(hostname or ''))
49
+
50
+
51
+ def register(app, policy):
52
+ from flask import jsonify, request
53
+
54
+ app.ffado_host = policy.host
55
+ app.ffado_port = policy.remote_access.port if policy.remote_access else DEFAULT_PORT
56
+
57
+ @app.before_request
58
+ def check_access():
59
+ try:
60
+ hostname = urlsplit(request.host_url).hostname
61
+ except ValueError:
62
+ hostname = None
63
+ local = policy.permits_local(request.remote_addr, hostname)
64
+ if not local:
65
+ auth = request.authorization
66
+ revision = None
67
+ if policy.remote_access and auth and auth.type.lower() == 'basic':
68
+ revision = policy.remote_access.authenticate_revision(auth.username, auth.password or '')
69
+ if revision is None:
70
+ return jsonify(error='Mixer access requires authentication'), 401, {
71
+ 'WWW-Authenticate': 'Basic realm="FFADO WebMixer", charset="UTF-8"',
72
+ }
73
+ request.environ['ffado_webmixer.remote_authorization'] = (policy.remote_access, revision)
74
+
75
+ if request.method not in ('GET', 'HEAD', 'OPTIONS'):
76
+ # Browser credentials (including cached Basic auth) are ambient.
77
+ # Validate the origin even for loopback requests and reject simple
78
+ # form submissions to JSON APIs. Native API clients omit Origin.
79
+ origin = request.headers.get('Origin')
80
+ if (request.headers.get('Sec-Fetch-Site') == 'cross-site'
81
+ or (origin is not None and origin != request.host_url.rstrip('/'))):
82
+ return jsonify(error='Cross-origin mixer writes are forbidden'), 403
83
+ if request.path.startswith('/api/') and not request.is_json:
84
+ return jsonify(error='Mixer writes require application/json'), 415
85
+ return None
86
+
87
+ @app.after_request
88
+ def access_headers(response):
89
+ response.headers['X-Content-Type-Options'] = 'nosniff'
90
+ response.headers['X-Frame-Options'] = 'DENY'
91
+ response.headers['Referrer-Policy'] = 'same-origin'
92
+ return response
93
+
94
+ # Remote users can operate the mixer, but credential management stays local.
95
+ @app.get('/api/remote-access')
96
+ def remote_access_status():
97
+ local = policy.permits_local(request.remote_addr, urlsplit(request.host_url).hostname)
98
+ config = policy.remote_access
99
+ status = config.status() if config else {'enabled': False, 'username': '', 'configured': False,
100
+ 'port': DEFAULT_PORT}
101
+ return jsonify(**status, can_manage=bool(local and config and config.writable),
102
+ listening=not _loopback(policy.host),
103
+ listening_port=app.ffado_port,
104
+ restart_required=(status['enabled'] != (not _loopback(policy.host))
105
+ or status['port'] != app.ffado_port))
106
+
107
+ @app.post('/api/remote-access')
108
+ def update_remote_access():
109
+ if not policy.permits_local(request.remote_addr, urlsplit(request.host_url).hostname):
110
+ return jsonify(error='Manage remote access from the local application or localhost browser'), 403
111
+ config = policy.remote_access
112
+ if config is None or not config.writable:
113
+ return jsonify(error='Remote-access settings cannot be changed in demo or debug mode'), 403
114
+ payload = request.get_json(silent=True)
115
+ if not isinstance(payload, dict):
116
+ return jsonify(error='Remote-access settings must be a JSON object'), 400
117
+ try:
118
+ config.update(enabled=payload.get('enabled'), username=payload.get('username'),
119
+ password=payload.get('password', ''),
120
+ **({'port': payload['port']} if 'port' in payload else {}))
121
+ except ValueError as exc:
122
+ return jsonify(error=str(exc)), 400
123
+ except OSError:
124
+ return jsonify(error='Could not save remote-access settings; previous settings remain active'), 500
125
+ return remote_access_status()
@@ -0,0 +1 @@
1
+ """Flask route modules."""
@@ -0,0 +1,50 @@
1
+ import json
2
+ import queue
3
+ from flask import Response, jsonify, request
4
+ from ..services.streams import StreamClosed, StreamSubscription
5
+
6
+ def register(app, hub, device_manager=None):
7
+ @app.get("/api/events")
8
+ def events():
9
+ """Server-sent events: one long-lived response per open page."""
10
+ # Resolve the selected GUID before streaming; the session proxy must
11
+ # never be accessed from the SSE generator after its request context
12
+ # has finished. Only forward events for this device.
13
+ selected = device_manager.current_guid if device_manager else None
14
+ # EventSource cannot set the device header used by other API requests.
15
+ # Reject an old tab reconnecting after a shared session changes devices.
16
+ expected = request.args.get('device')
17
+ if expected and expected != selected:
18
+ return jsonify(error='Selected mixer changed in another tab; reload this page'), 409
19
+ client = hub.subscribe(selected)
20
+ subscription = StreamSubscription(client, lambda: hub.unsubscribe(client),
21
+ request.environ.get('ffado_webmixer.remote_authorization'))
22
+
23
+ def stream():
24
+ try:
25
+ if subscription.closed.is_set():
26
+ return
27
+ yield "retry: 1000\n\n"
28
+
29
+ while True:
30
+ try:
31
+ event = subscription.get(timeout=15)
32
+ except StreamClosed:
33
+ return
34
+ except queue.Empty:
35
+ # Also lets the server notice pages that have closed.
36
+ yield ": keepalive\n\n"
37
+ continue
38
+
39
+ yield f"data: {json.dumps(event)}\n\n"
40
+ finally:
41
+ subscription.close()
42
+
43
+ response = Response(
44
+ stream(),
45
+ mimetype="text/event-stream",
46
+ headers={"X-Accel-Buffering": "no", "Cache-Control": "no-store"},
47
+ )
48
+ # Closing a response before iteration also needs to remove its client.
49
+ response.call_on_close(subscription.close)
50
+ return response
@@ -0,0 +1,41 @@
1
+ """Optional QjackCtl actions and external-browser launches for the desktop window."""
2
+ import webbrowser
3
+ from flask import jsonify, request
4
+ from .. import qjackctl
5
+
6
+
7
+ def register(app, config, mixer_url, debug=False):
8
+ @app.get("/api/qjackctl/status")
9
+ def qjackctl_status():
10
+ if debug or not config.settings.get("enable_qjackctl", False):
11
+ return jsonify(enabled=False, running=False)
12
+ return jsonify(enabled=True, running=qjackctl.is_running())
13
+
14
+ @app.post("/api/qjackctl")
15
+ def open_qjackctl():
16
+ if debug or not config.settings.get("enable_qjackctl", False):
17
+ return jsonify(error="QjackCtl support is disabled in General settings"), 403
18
+ payload = request.get_json(force=True)
19
+ if not isinstance(payload, dict) or not isinstance(payload.get('window'), str):
20
+ return jsonify(error='QjackCtl window must be a string'), 400
21
+ window = payload['window']
22
+ try:
23
+ qjackctl.open_window(window)
24
+ except ValueError as exc:
25
+ return jsonify(error=str(exc)), 400
26
+ except qjackctl.QjackCtlError as exc:
27
+ return jsonify(error=str(exc)), 503
28
+ return jsonify(ok=True)
29
+
30
+ @app.post("/api/open-github")
31
+ def open_github():
32
+ if not webbrowser.open("https://github.com/Sokoloft/ffado-webmixer"):
33
+ return jsonify(error="Could not launch the default browser"), 503
34
+ return jsonify(ok=True)
35
+
36
+ @app.post("/api/open-mixer")
37
+ def open_mixer():
38
+ # The URL is built by the server, never accepted from the browser.
39
+ if not webbrowser.open(mixer_url()):
40
+ return jsonify(error="Could not launch the default browser"), 503
41
+ return jsonify(ok=True)