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.
- ffado_webmixer/__init__.py +3 -0
- ffado_webmixer/__main__.py +4 -0
- ffado_webmixer/access.py +125 -0
- ffado_webmixer/api/__init__.py +1 -0
- ffado_webmixer/api/events.py +50 -0
- ffado_webmixer/api/integrations.py +41 -0
- ffado_webmixer/api/mixer.py +729 -0
- ffado_webmixer/api/settings.py +238 -0
- ffado_webmixer/api/stereo.py +17 -0
- ffado_webmixer/assets/icon.svg +13 -0
- ffado_webmixer/assets/static/QR-LICENSE.txt +24 -0
- ffado_webmixer/assets/static/desktop-integration.css +18 -0
- ffado_webmixer/assets/static/desktop-integration.js +41 -0
- ffado_webmixer/assets/static/editable-input.js +73 -0
- ffado_webmixer/assets/static/frontend-api.js +70 -0
- ffado_webmixer/assets/static/index.css +553 -0
- ffado_webmixer/assets/static/index.html +294 -0
- ffado_webmixer/assets/static/index.js +2133 -0
- ffado_webmixer/assets/static/labels.js +53 -0
- ffado_webmixer/assets/static/live-sync.js +167 -0
- ffado_webmixer/assets/static/loading.css +130 -0
- ffado_webmixer/assets/static/loading.html +47 -0
- ffado_webmixer/assets/static/loading.js +1 -0
- ffado_webmixer/assets/static/native-title.js +22 -0
- ffado_webmixer/assets/static/peak-metering.js +290 -0
- ffado_webmixer/assets/static/qrcode.js +1276 -0
- ffado_webmixer/assets/static/remote-access.js +89 -0
- ffado_webmixer/assets/static/routing.css +148 -0
- ffado_webmixer/assets/static/routing.js +783 -0
- ffado_webmixer/assets/static/themes.js +57 -0
- ffado_webmixer/assets/themes/breeze.css +43 -0
- ffado_webmixer/assets/themes/catppuccin.css +43 -0
- ffado_webmixer/assets/themes/dark.css +43 -0
- ffado_webmixer/assets/themes/everforest.css +43 -0
- ffado_webmixer/assets/themes/gruvbox.css +43 -0
- ffado_webmixer/assets/themes/high-contrast.css +43 -0
- ffado_webmixer/assets/themes/icons/breeze.svg +9 -0
- ffado_webmixer/assets/themes/icons/catppuccin.svg +9 -0
- ffado_webmixer/assets/themes/icons/dark.svg +9 -0
- ffado_webmixer/assets/themes/icons/everforest.svg +9 -0
- ffado_webmixer/assets/themes/icons/gruvbox.svg +9 -0
- ffado_webmixer/assets/themes/icons/high-contrast.svg +9 -0
- ffado_webmixer/assets/themes/icons/light.svg +9 -0
- ffado_webmixer/assets/themes/icons/midnight.svg +9 -0
- ffado_webmixer/assets/themes/icons/nordic.svg +9 -0
- ffado_webmixer/assets/themes/icons/tokyonight.svg +9 -0
- ffado_webmixer/assets/themes/light.css +43 -0
- ffado_webmixer/assets/themes/midnight.css +43 -0
- ffado_webmixer/assets/themes/nordic.css +43 -0
- ffado_webmixer/assets/themes/tokyonight.css +43 -0
- ffado_webmixer/client.py +136 -0
- ffado_webmixer/config.py +371 -0
- ffado_webmixer/controls.py +18 -0
- ffado_webmixer/debug_backend.py +191 -0
- ffado_webmixer/demo.py +63 -0
- ffado_webmixer/desktop_integration.py +121 -0
- ffado_webmixer/device_manager.py +192 -0
- ffado_webmixer/device_registry.py +108 -0
- ffado_webmixer/gain.py +84 -0
- ffado_webmixer/helper.py +94 -0
- ffado_webmixer/interrogate.py +251 -0
- ffado_webmixer/main.py +303 -0
- ffado_webmixer/mixer.py +1081 -0
- ffado_webmixer/mixer_outputs.py +202 -0
- ffado_webmixer/mixer_routing.py +358 -0
- ffado_webmixer/native_icons.py +144 -0
- ffado_webmixer/peak_metering.py +82 -0
- ffado_webmixer/profile_loader.py +6 -0
- ffado_webmixer/profiles/__init__.py +1 -0
- ffado_webmixer/profiles/audiofire/audiofire12_001486_0000af12.xml +31 -0
- ffado_webmixer/profiles/audiofire/audiofire2_001486_00000af2.xml +31 -0
- ffado_webmixer/profiles/audiofire/audiofire4_001486_00000af4.xml +31 -0
- ffado_webmixer/profiles/audiofire/audiofire8_001486_00000af8.xml +31 -0
- ffado_webmixer/profiles/audiofire/audiofire8a_001486_00000af9.xml +31 -0
- ffado_webmixer/profiles/audiofire/common.xml +27 -0
- ffado_webmixer/profiles/audiofire/reference.xml +79 -0
- ffado_webmixer/profiles/bcoaudio5control/audio_5_0007f5_00010049.xml +31 -0
- ffado_webmixer/profiles/bcoaudio5control/common.xml +27 -0
- ffado_webmixer/profiles/bcoaudio5control/reference.xml +55 -0
- ffado_webmixer/profiles/capabilities.py +90 -0
- ffado_webmixer/profiles/driver_only/adc_2_001c6a_00000001.xml +31 -0
- ffado_webmixer/profiles/driver_only/afi_1_001c6a_00000004.xml +31 -0
- ffado_webmixer/profiles/driver_only/audiofire12hd_001486_000af12d.xml +31 -0
- ffado_webmixer/profiles/driver_only/axum_firewire_io_card_16x16_000f64_00000003.xml +31 -0
- ffado_webmixer/profiles/driver_only/common.xml +4 -0
- ffado_webmixer/profiles/driver_only/dac_202_001c6a_00000007.xml +31 -0
- ffado_webmixer/profiles/driver_only/desktop_konnekt_6_000166_00000024.xml +31 -0
- ffado_webmixer/profiles/driver_only/diceii_evm_1_000166_00000001.xml +31 -0
- ffado_webmixer/profiles/driver_only/diceii_evm_2_000166_00000002.xml +31 -0
- ffado_webmixer/profiles/driver_only/diceii_evm_4_000166_00000004.xml +31 -0
- ffado_webmixer/profiles/driver_only/duet_0003db_0001dddd.xml +31 -0
- ffado_webmixer/profiles/driver_only/ear_master_one_000aac_00000002.xml +31 -0
- ffado_webmixer/profiles/driver_only/fca202_001564_0000fc22.xml +31 -0
- ffado_webmixer/profiles/driver_only/fireworks_8_001486_000000f8.xml +31 -0
- ffado_webmixer/profiles/driver_only/firexon_001a9e_00000001.xml +31 -0
- ffado_webmixer/profiles/driver_only/flex_5000_001c2d_00000001.xml +31 -0
- ffado_webmixer/profiles/driver_only/fw_hdmi_001486_0000afd1.xml +31 -0
- ffado_webmixer/profiles/driver_only/hb_24u_001496_00000000.xml +31 -0
- ffado_webmixer/profiles/driver_only/iffwdm_00022e_00010067.xml +31 -0
- ffado_webmixer/profiles/driver_only/impacttwin_000166_00000027.xml +31 -0
- ffado_webmixer/profiles/driver_only/int_202_001c6a_00000006.xml +31 -0
- ffado_webmixer/profiles/driver_only/io_14_000595_00000001.xml +31 -0
- ffado_webmixer/profiles/driver_only/konnekt_24d_000166_00000020.xml +31 -0
- ffado_webmixer/profiles/driver_only/konnekt_8_000166_00000021.xml +31 -0
- ffado_webmixer/profiles/driver_only/konnekt_live_000166_00000023.xml +31 -0
- ffado_webmixer/profiles/driver_only/matrix_k_fw_00000a_00030000.xml +31 -0
- ffado_webmixer/profiles/driver_only/mbox_2_pro_00a07e_000000a9.xml +31 -0
- ffado_webmixer/profiles/driver_only/mbox_3_pro_00a07e_00000004.xml +31 -0
- ffado_webmixer/profiles/driver_only/minerva_001c6a_00000003.xml +31 -0
- ffado_webmixer/profiles/driver_only/multimix_12_multimix_16_firewire_000595_00000000.xml +31 -0
- ffado_webmixer/profiles/driver_only/nrv10_000d6c_00010081.xml +31 -0
- ffado_webmixer/profiles/driver_only/onyx_1200f_000ff2_0001200f.xml +31 -0
- ffado_webmixer/profiles/driver_only/onyx_1640i_dice_000ff2_00000006.xml +31 -0
- ffado_webmixer/profiles/driver_only/onyx_1640i_oxford_000ff2_00001640.xml +31 -0
- ffado_webmixer/profiles/driver_only/onyx_400f_000ff2_0000400f.xml +31 -0
- ffado_webmixer/profiles/driver_only/onyx_firewire_00000f_00010065.xml +31 -0
- ffado_webmixer/profiles/driver_only/onyx_i_000ff2_00081216.xml +31 -0
- ffado_webmixer/profiles/driver_only/profire_lightbridge_000d6c_000100a1.xml +31 -0
- ffado_webmixer/profiles/driver_only/rd_audio1_0007f5_00010048.xml +31 -0
- ffado_webmixer/profiles/driver_only/rosetta_200_0003db_00010048.xml +31 -0
- ffado_webmixer/profiles/driver_only/scs_1m_001260_00001000.xml +31 -0
- ffado_webmixer/profiles/driver_only/studio_konnekt_48_000166_00000022.xml +31 -0
- ffado_webmixer/profiles/driver_only/studiolive_1602_000a92_00000013.xml +31 -0
- ffado_webmixer/profiles/driver_only/studiolive_1642_000a92_00000010.xml +31 -0
- ffado_webmixer/profiles/driver_only/studiolive_2442_000a92_00000012.xml +31 -0
- ffado_webmixer/profiles/driver_only/studiolive_3242_mk2_000a92_00000014.xml +31 -0
- ffado_webmixer/profiles/driver_only/tag_dac1_001c6a_00000005.xml +31 -0
- ffado_webmixer/profiles/driver_only/ufx_1604_mixer_001564_00001604.xml +31 -0
- ffado_webmixer/profiles/driver_only/vesta_001c6a_00000002.xml +31 -0
- ffado_webmixer/profiles/driver_only/x32_001564_00000006.xml +31 -0
- ffado_webmixer/profiles/edirolfa101control/common.xml +10 -0
- ffado_webmixer/profiles/edirolfa101control/fa_101_0040ab_00010048.xml +31 -0
- ffado_webmixer/profiles/edirolfa101control/reference.xml +50 -0
- ffado_webmixer/profiles/edirolfa66control/common.xml +27 -0
- ffado_webmixer/profiles/edirolfa66control/fa_66_0040ab_00010049.xml +31 -0
- ffado_webmixer/profiles/edirolfa66control/reference.xml +61 -0
- ffado_webmixer/profiles/ffado_configuration +861 -0
- ffado_webmixer/profiles/generic_dice_eap/common.xml +15 -0
- ffado_webmixer/profiles/generic_dice_eap/firestudio_26x26_000a92_00000008.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/firestudio_mobile_000a92_00000011.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/firestudio_project_000a92_0000000b.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/firestudio_tube_000a92_0000000c.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/i_onix_fw810s_000fd7_00000001.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/onyx_blackbird_000ff2_00000007.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/reference.xml +18 -0
- ffado_webmixer/profiles/generic_dice_eap/venice_f32_10c73f_00000001.xml +31 -0
- ffado_webmixer/profiles/generic_dice_eap/zed_r16_0004c4_00000000.xml +31 -0
- ffado_webmixer/profiles/globalmixer/common.xml +11 -0
- ffado_webmixer/profiles/globalmixer/reference.xml +30 -0
- ffado_webmixer/profiles/loader.py +142 -0
- ffado_webmixer/profiles/mackieonyx/common.xml +4 -0
- ffado_webmixer/profiles/mackieonyx/onyx_firewire_00000f_00010067.xml +31 -0
- ffado_webmixer/profiles/mackieonyx/reference.xml +15 -0
- ffado_webmixer/profiles/maudio_bebob/common.xml +16 -0
- ffado_webmixer/profiles/maudio_bebob/fw_1814_000d6c_00010071.xml +31 -0
- ffado_webmixer/profiles/maudio_bebob/fw_410_000d6c_00010046.xml +31 -0
- ffado_webmixer/profiles/maudio_bebob/fw_audiophile_000d6c_00010060.xml +31 -0
- ffado_webmixer/profiles/maudio_bebob/fw_solo_000d6c_00010062.xml +31 -0
- ffado_webmixer/profiles/maudio_bebob/ozonic_000d6c_0000000a.xml +31 -0
- ffado_webmixer/profiles/maudio_bebob/projectmix_i_o_000d6c_00010091.xml +31 -0
- ffado_webmixer/profiles/maudio_bebob/reference.xml +57 -0
- ffado_webmixer/profiles/motu/all_pre_mark3_devices_0001f2_00000000.xml +31 -0
- ffado_webmixer/profiles/motu/common.xml +529 -0
- ffado_webmixer/profiles/motu/motu_model_1.xml +29 -0
- ffado_webmixer/profiles/motu/motu_model_2.xml +29 -0
- ffado_webmixer/profiles/motu/motu_model_4.xml +29 -0
- ffado_webmixer/profiles/motu/motu_model_6.xml +29 -0
- ffado_webmixer/profiles/motu/motu_ultralite.xml +39 -0
- ffado_webmixer/profiles/motu/reference.xml +1613 -0
- ffado_webmixer/profiles/motu_mark3/all_mark3_devices_0001f2_00000001.xml +31 -0
- ffado_webmixer/profiles/motu_mark3/common.xml +23 -0
- ffado_webmixer/profiles/motu_mark3/reference.xml +31 -0
- ffado_webmixer/profiles/phase24control/common.xml +32 -0
- ffado_webmixer/profiles/phase24control/phase_x24_fw_model_version_4_000aac_00000004.xml +31 -0
- ffado_webmixer/profiles/phase24control/phase_x24_fw_model_version_7_000aac_00000007.xml +31 -0
- ffado_webmixer/profiles/phase24control/reference.xml +72 -0
- ffado_webmixer/profiles/phase88control/common.xml +35 -0
- ffado_webmixer/profiles/phase88control/phase_88_fw_000aac_00000003.xml +31 -0
- ffado_webmixer/profiles/phase88control/reference.xml +123 -0
- ffado_webmixer/profiles/presonus_firebox/common.xml +14 -0
- ffado_webmixer/profiles/presonus_firebox/firebox_000a92_00010000.xml +31 -0
- ffado_webmixer/profiles/presonus_firebox/reference.xml +19 -0
- ffado_webmixer/profiles/presonus_fp10/common.xml +11 -0
- ffado_webmixer/profiles/presonus_fp10/firepod_000a92_00010066.xml +31 -0
- ffado_webmixer/profiles/presonus_fp10/reference.xml +14 -0
- ffado_webmixer/profiles/presonus_inspire1394/common.xml +13 -0
- ffado_webmixer/profiles/presonus_inspire1394/inspire1394_000a92_00010001.xml +31 -0
- ffado_webmixer/profiles/presonus_inspire1394/reference.xml +16 -0
- ffado_webmixer/profiles/profire2626/common.xml +10 -0
- ffado_webmixer/profiles/profire2626/profire_2626_000d6c_00000010.xml +31 -0
- ffado_webmixer/profiles/profire2626/profire_610_000d6c_00000011.xml +31 -0
- ffado_webmixer/profiles/profire2626/reference.xml +39 -0
- ffado_webmixer/profiles/quatafire/common.xml +30 -0
- ffado_webmixer/profiles/quatafire/quatafire_610_000f1b_00010064.xml +31 -0
- ffado_webmixer/profiles/quatafire/quatafire_610_variant_000f1b_00000210.xml +31 -0
- ffado_webmixer/profiles/quatafire/reference.xml +80 -0
- ffado_webmixer/profiles/resolver.py +242 -0
- ffado_webmixer/profiles/rme/common.xml +154 -0
- ffado_webmixer/profiles/rme/fireface400_000a35_00000002.xml +31 -0
- ffado_webmixer/profiles/rme/fireface800_000a35_00000001.xml +31 -0
- ffado_webmixer/profiles/rme/fireface_802_000a35_00000005.xml +31 -0
- ffado_webmixer/profiles/rme/fireface_ucx_000a35_00000004.xml +31 -0
- ffado_webmixer/profiles/rme/fireface_ufx_000a35_00000003.xml +31 -0
- ffado_webmixer/profiles/rme/reference.xml +355 -0
- ffado_webmixer/profiles/saffire/common.xml +290 -0
- ffado_webmixer/profiles/saffire/reference.xml +1023 -0
- ffado_webmixer/profiles/saffire/saffire_le_00130e_00000000.xml +31 -0
- ffado_webmixer/profiles/saffire_base/common.xml +4 -0
- ffado_webmixer/profiles/saffire_base/reference.xml +8 -0
- ffado_webmixer/profiles/saffire_dice/common.xml +72 -0
- ffado_webmixer/profiles/saffire_dice/reference.xml +98 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_14_00130e_00000009.xml +31 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_24_00130e_00000007.xml +31 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_24_dsp_00130e_00000008.xml +31 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_26_00130e_00000012.xml +31 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_40_00130e_00000005.xml +31 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_40_1_00130e_00000013.xml +31 -0
- ffado_webmixer/profiles/saffire_dice/saffire_pro_40_1_00130e_000000de.xml +31 -0
- ffado_webmixer/profiles/saffirepro/common.xml +261 -0
- ffado_webmixer/profiles/saffirepro/reference.xml +803 -0
- ffado_webmixer/profiles/saffirepro/saffire_pro10io_00130e_00000006.xml +31 -0
- ffado_webmixer/profiles/saffirepro/saffire_pro26io_00130e_00000003.xml +31 -0
- ffado_webmixer/profiles/yamahago/common.xml +29 -0
- ffado_webmixer/profiles/yamahago/go44_00a0de_0010000b.xml +31 -0
- ffado_webmixer/profiles/yamahago/go46_00a0de_0010000c.xml +31 -0
- ffado_webmixer/profiles/yamahago/reference.xml +94 -0
- ffado_webmixer/qjackctl.py +57 -0
- ffado_webmixer/remote_access.py +221 -0
- ffado_webmixer/server.py +196 -0
- ffado_webmixer/services/__init__.py +1 -0
- ffado_webmixer/services/events.py +48 -0
- ffado_webmixer/services/peak_stream.py +97 -0
- ffado_webmixer/services/stereo.py +121 -0
- ffado_webmixer/services/streams.py +46 -0
- ffado_webmixer/soft_mute.py +76 -0
- ffado_webmixer/theme_icons.py +100 -0
- ffado_webmixer/themes.py +19 -0
- ffado_webmixer-1.2.1.dist-info/METADATA +116 -0
- ffado_webmixer-1.2.1.dist-info/RECORD +243 -0
- ffado_webmixer-1.2.1.dist-info/WHEEL +5 -0
- ffado_webmixer-1.2.1.dist-info/entry_points.txt +2 -0
- ffado_webmixer-1.2.1.dist-info/licenses/LICENSE +338 -0
- ffado_webmixer-1.2.1.dist-info/top_level.txt +1 -0
ffado_webmixer/access.py
ADDED
|
@@ -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)
|