diff --git a/lib/wp/settings.c b/lib/wp/settings.c index 6b23a491..2900f638 100644 --- a/lib/wp/settings.c +++ b/lib/wp/settings.c @@ -475,18 +475,37 @@ parse_rule (const gchar *rule, const gchar *value) return r; } +static gboolean +is_rule (WpSpaJson *json) +{ + /* rule is an array and starts with an object */ + if (wp_spa_json_is_array (json)) { + g_autoptr (WpIterator) iter = wp_spa_json_new_iterator (json); + g_auto (GValue) item = G_VALUE_INIT; + + wp_iterator_next (iter, &item); + WpSpaJson *o = g_value_get_boxed (&item); + if (wp_spa_json_is_object (o)) + return TRUE; + } + return FALSE; +} static void parse_setting (const gchar *setting, const gchar *value, WpSettings *self) { g_autoptr (WpSpaJson) json = wp_spa_json_new_from_string (value); - if (!wp_spa_json_is_array (json)) - wp_properties_set (self->settings, setting, value); - else { + if (is_rule (json)) { Rule *r = parse_rule (setting, value); - g_ptr_array_add (self->rules, r); - wp_debug_object (self, "loaded (%d) matches for rule (%s)", - r->matches->len, r->rule); + if (r) + { + g_ptr_array_add (self->rules, r); + wp_debug_object (self, "loaded (%d) matches for rule (%s)", + r->matches->len, r->rule); + } + } + else { + wp_properties_set (self->settings, setting, value); } } diff --git a/src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua b/src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua index 839ec108..16230105 100644 --- a/src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua +++ b/src/config/bluetooth.lua.d/30-bluez-midi-monitor.lua @@ -3,14 +3,10 @@ bluez_midi_monitor.properties = {} bluez_midi_monitor.rules = {} function bluez_midi_monitor.enable() - if bluez_midi_monitor.enabled == false then - return - end - - load_monitor("bluez-midi", { - properties = bluez_midi_monitor.properties, - rules = bluez_midi_monitor.rules, - }) + --load_monitor("bluez-midi", { + -- properties = bluez_midi_monitor.properties, + -- rules = bluez_midi_monitor.rules, + --}) if bluez_midi_monitor.properties["with-logind"] then load_optional_module("logind") diff --git a/src/config/bluetooth.lua.d/30-bluez-monitor.lua b/src/config/bluetooth.lua.d/30-bluez-monitor.lua index a870aa5d..a1a7e073 100644 --- a/src/config/bluetooth.lua.d/30-bluez-monitor.lua +++ b/src/config/bluetooth.lua.d/30-bluez-monitor.lua @@ -3,14 +3,10 @@ bluez_monitor.properties = {} bluez_monitor.rules = {} function bluez_monitor.enable() - if bluez_monitor.enabled == false then - return - end - - load_monitor("bluez", { - properties = bluez_monitor.properties, - rules = bluez_monitor.rules, - }) + -- load_monitor("bluez", { + -- properties = bluez_monitor.properties, + -- rules = bluez_monitor.rules, + -- }) if bluez_monitor.properties["with-logind"] then load_optional_module("logind") diff --git a/src/config/bluetooth.lua.d/90-enable-all.lua b/src/config/bluetooth.lua.d/90-enable-all.lua index efa6bf54..68721d64 100644 --- a/src/config/bluetooth.lua.d/90-enable-all.lua +++ b/src/config/bluetooth.lua.d/90-enable-all.lua @@ -1,2 +1,2 @@ -bluez_monitor.enable() -bluez_midi_monitor.enable() +-- bluez_monitor.enable() +-- bluez_midi_monitor.enable() diff --git a/src/config/wireplumber.conf.d/bluetooth-settings.conf b/src/config/wireplumber.conf.d/bluetooth-settings.conf new file mode 100644 index 00000000..96860693 --- /dev/null +++ b/src/config/wireplumber.conf.d/bluetooth-settings.conf @@ -0,0 +1,175 @@ +wireplumber.components = [ + { name = monitors/bluez.lua, type = script/lua } + { name = monitors/bluez-midi.lua, type = script/lua } +] + +wireplumber.settings = { + # These features do not work on all headsets, so they are enabled + # by default based on the hardware database. They can also be + # forced on/off for all devices by the following options: + + # bluez5.enable-sbc-xq = true + # bluez5.enable-msbc = true + # bluez5.enable-hw-volume = true + + # See bluez-hardware.conf for the hardware database. + + # Enabled headset roles (default: [ hsp_hs hfp_ag ]), this + # property only applies to native backend. Currently some headsets + # (Sony WH-1000XM3) are not working with both hsp_ag and hfp_ag + # enabled, disable either hsp_ag or hfp_ag to work around it. + + # Supported headset roles: hsp_hs (HSP Headset), + # hsp_ag (HSP Audio Gateway), + # hfp_hf (HFP Hands-Free), + # hfp_ag (HFP Audio Gateway) + # bluez5.headset-roles = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]" + + # Enabled A2DP codecs (default: all). + # bluez5.codecs = "[ sbc sbc_xq aac ldac aptx aptx_hd aptx_ll aptx_ll_duplex faststream faststream_duplex ]" + + # HFP/HSP backend (default: native). + # Available values: any, none, hsphfpd, ofono, native + # bluez5.hfphsp-backend = "native" + + # HFP/HSP native backend modem (default: none). + # Available values: none, any or the modem device string as found in + # 'Device' property of org.freedesktop.ModemManager1.Modem interface + # bluez5.hfphsp-backend-native-modem = "none" + + # HFP/HSP hardware offload SCO support (default: false). + # bluez5.hw-offload-sco = false + + # Properties for the A2DP codec configuration + # bluez5.default.rate = 48000 + # bluez5.default.channels = 2 + + # Register dummy AVRCP player, required for AVRCP volume function. + # Disable if you are running mpris-proxy or equivalent. + # bluez5.dummy-avrcp-player = true + + # Opus Pro Audio mode settings + # bluez5.a2dp.opus.pro.channels = 3 + # bluez5.a2dp.opus.pro.coupled-streams = 1 + # bluez5.a2dp.opus.pro.locations = "FL,FR,LFE" + # bluez5.a2dp.opus.pro.max-bitrate = 600000 + # bluez5.a2dp.opus.pro.frame-dms = 50 + # bluez5.a2dp.opus.pro.bidi.channels = 1 + # bluez5.a2dp.opus.pro.bidi.coupled-streams = 0 + # bluez5.a2dp.opus.pro.bidi.locations = "FC" + # bluez5.a2dp.opus.pro.bidi.max-bitrate = 160000 + # bluez5.a2dp.opus.pro.bidi.frame-dms = 400 + + ## The properties used when constructing the 'api.bluez5.midi.enum' plugin + # monitor.bluetooth-midi.properties = {} + + ## List of MIDI server node names. Each node name given will create a new instance + ## of a BLE MIDI service. Typical BLE MIDI instruments have on service instance, + ## so adding more than one here may confuse some clients. The node property matching + ## rules below apply also to these servers. + monitor.bluetooth-midi.servers = [ "bluez_midi.server" ] + + # Enable the logind module, which arbitrates which user will be allowed + # to have bluetooth audio enabled at any given time (particularly useful + # if you are using GDM as a display manager, as the gdm user also launches + # pipewire and wireplumber). + # This requires access to the D-Bus user session; disable if you are running + # a system-wide instance of wireplumber. + with-logind = true + + bluez_monitor = [ + { + # Rules for matching a device or node. It is an array of + # properties that all need to match the regexp. If any of the + # matches work, the actions are executed for the object. + matches = [ + { + # This matches all cards. + device.name = "~bluez_card.*" + } + ] + actions = { + update-props = { + # Auto-connect device profiles on start up or when only partial + # profiles have connected. Disabled by default if the property + # is not specified. + # bluez5.auto-connect = "[ hfp_hf hsp_hs a2dp_sink hfp_ag hsp_ag a2dp_source ]" + bluez5.auto-connect = "[ hfp_hf hsp_hs a2dp_sink ]" + + # Hardware volume control (default: [ hfp_ag hsp_ag a2dp_source ]) + # bluez5.hw-volume = "[ hfp_hf hsp_hs a2dp_sink hfp_ag hsp_ag a2dp_source ]" + + # LDAC encoding quality + # Available values: auto (Adaptive Bitrate, default) + # hq (High Quality, 990/909kbps) + # sq (Standard Quality, 660/606kbps) + # mq (Mobile use Quality, 330/303kbps) + # bluez5.a2dp.ldac.quality = "auto" + + # AAC variable bitrate mode + # Available values: 0 (cbr, default), 1-5 (quality level) + # bluez5.a2dp.aac.bitratemode = 0 + + # Profile connected first + # Available values: a2dp-sink (default), headset-head-unit + # device.profile = "a2dp-sink" + + # Opus Pro Audio encoding mode: audio, voip, lowdelay + # bluez5.a2dp.opus.pro.application = "audio" + # bluez5.a2dp.opus.pro.bidi.application = "audio" + } + } + } + { + matches = [ + { + # Matches all sources. + node.name = "~bluez_input.*" + } + { + # Matches all sinks. + node.name = "~bluez_output.*" + } + ] + actions = { + update-props = { + # node.nick = "My Node" + # priority.driver = 100 + # priority.session = 100 + # node.pause-on-idle = false + # resample.quality = 4 + # channelmix.normalize = false + # channelmix.mix-lfe = false + # session.suspend-timeout-seconds = 5 + # monitor.channel-volumes = false + + # Media source role, "input" or "playback" + # Defaults to "playback", playing stream to speakers + # Set to "input" to use as an input for apps + # bluez5.media-source-role = "input" + } + } + } + ] + + ## The list of monitor MIDI rules + monitor.bluetooth-midi = [ + ## This rule example allows changing properties on all Bluetooth MIDI nodes. + # { + # matches = { + # { + # ## Matches all nodes. + # { "node.name", "matches", "bluez_midi.*" }, + # }, + # }, + # update-props = { + # node.nick = "My Node" + # priority.driver = 100 + # priority.session = 100 + # node.pause-on-idle = false + # session.suspend-timeout-seconds = 5 + # monitor.channel-volumes = false + # } + # } + ] +} diff --git a/src/scripts/monitors/bluez-midi.lua b/src/scripts/monitors/bluez-midi.lua index 32b886ec..fa1b8c5e 100644 --- a/src/scripts/monitors/bluez-midi.lua +++ b/src/scripts/monitors/bluez-midi.lua @@ -5,38 +5,29 @@ -- -- SPDX-License-Identifier: MIT -local config = ... or {} +local config = {} +config.properties = Settings.get_string ("monitor.bluetooth-midi.properties") +if config.properties == nil then + config.properties = {} +else + config.properties = Json.Raw (config.properties) +end +if config.servers == nil then + config.servers = Json.Raw (config.server) +else + config.servers = Settings.get_string ("monitor.bluetooth-midi.servers") +end -- unique device/node name tables node_names_table = nil id_to_name_table = nil --- preprocess rules and create Interest objects -for _, r in ipairs(config.rules or {}) do - r.interests = {} - for _, i in ipairs(r.matches) do - local interest_desc = { type = "properties" } - for _, c in ipairs(i) do - c.type = "pw" - table.insert(interest_desc, Constraint(c)) - end - local interest = Interest(interest_desc) - table.insert(r.interests, interest) - end - r.matches = nil -end +function rulesApplyProperties (properties) + local matched, mprops = Settings.apply_rule ("monitor.bluetooth-midi", properties) --- applies properties from config.rules when asked to -function rulesApplyProperties(properties) - for _, r in ipairs(config.rules or {}) do - if r.apply_properties then - for _, interest in ipairs(r.interests) do - if interest:matches(properties) then - for k, v in pairs(r.apply_properties) do - properties[k] = v - end - end - end + if (matched and mprops) then + for k, v in pairs(mprops) do + properties[k] = v end end end @@ -101,7 +92,6 @@ function createMonitor() for k, v in pairs(config.properties or {}) do monitor_props[k] = v end - monitor_props["server"] = nil monitor_props["api.glib.mainloop"] = "true" @@ -126,16 +116,10 @@ function createMonitor() end function createServers() - local props = config.properties or {} - - if not props["servers"] then - return nil - end - local servers = {} local i = 1 - for k, v in pairs(props["servers"]) do + for k, v in pairs(config.servers) do local node_props = { ["node.name"] = v, ["node.description"] = string.format(I18n.gettext("BLE MIDI %d"), i), diff --git a/src/scripts/monitors/bluez.lua b/src/scripts/monitors/bluez.lua index 357c1a08..13dbca04 100644 --- a/src/scripts/monitors/bluez.lua +++ b/src/scripts/monitors/bluez.lua @@ -5,9 +5,33 @@ -- -- SPDX-License-Identifier: MIT -local config = ... or {} local COMBINE_OFFSET = 64 +local config_settings = { + ["bluez5.enable-sbc-xq"] = + Settings.get_boolean ("bluez5.enable-sbc-xq"), + ["bluez5.enable-msbc"] = + Settings.get_boolean ("bluez5.enable-msbc"), + ["bluez5.enable-hw-volume"] = + Settings.get_boolean ("bluez5.enable-hw-volume"), + ["bluez5.headset-roles"] = + Settings.get_string ("bluez5.headset-roles"), + ["bluez5.codecs"] = + Settings.get_string ("bluez5.codecs"), + ["bluez5.hfphsp-backend"] = + Settings.get_string ("bluez5.hfphsp-backend"), + ["bluez5.hfphsp-backend-native-modem"] = + Settings.get_string ("bluez5.hfphsp-backend-native-modem"), + ["bluez5.hw-offload-sco"] = + Settings.get_boolean ("bluez5.hw-offload-sco"), + ["bluez5.default.rate"] = + Settings.get_int ("bluez5.default.rate"), + ["bluez5.default.channels"] = + Settings.get_int ("bluez5.default.channels"), + ["bluez5.dummy-avrcp-player"] = + Settings.get_boolean ("bluez5.dummy-avrcp-player"), +} + devices_om = ObjectManager { Interest { type = "device", @@ -22,34 +46,16 @@ nodes_om = ObjectManager { } } --- preprocess rules and create Interest objects -for _, r in ipairs(config.rules or {}) do - r.interests = {} - for _, i in ipairs(r.matches) do - local interest_desc = { type = "properties" } - for _, c in ipairs(i) do - c.type = "pw" - table.insert(interest_desc, Constraint(c)) - end - local interest = Interest(interest_desc) - table.insert(r.interests, interest) - end - r.matches = nil -end - -- applies rules from bluez-settings.conf when asked to function rulesApplyProperties(properties) - for _, r in ipairs(config.rules or {}) do - if r.apply_properties then - for _, interest in ipairs(r.interests) do - if interest:matches(properties) then - for k, v in pairs(r.apply_properties) do - properties[k] = v - end - end - end + local matched, mprops = Settings.apply_rule ("bluez_monitor", properties) + + if (matched and mprops) then + for k, v in pairs(mprops) do + properties[k] = v end end + end function setOffloadActive(device, value) @@ -252,7 +258,7 @@ end function createNode(parent, id, type, factory, properties) local dev_props = parent.properties - if config.properties["bluez5.hw-offload-sco"] and factory:find("sco") then + if config_settings["bluez5.hw-offload-sco"] and factory:find("sco") then createOffloadScoNode(parent, id, type, factory, properties) return end @@ -297,7 +303,7 @@ function createNode(parent, id, type, factory, properties) properties["node.autoconnect"] = true end - -- apply properties from config.rules + -- apply properties from bluetooth-settings.conf rulesApplyProperties(properties) -- create the node; bluez requires "local" nodes, i.e. ones that run in @@ -359,7 +365,7 @@ function createDevice(parent, id, type, factory, properties) properties["bluez5.profile"] = "off" properties["api.bluez5.id"] = id - -- apply properties from config.rules + -- apply properties from bluetooth-settings.conf rulesApplyProperties(properties) -- create the device @@ -387,7 +393,7 @@ function createDevice(parent, id, type, factory, properties) end function createMonitor() - local monitor_props = config.properties or {} + local monitor_props = config_settings or {} monitor_props["api.bluez5.connection-info"] = true local monitor = SpaDevice("api.bluez5.enum.dbus", monitor_props) diff --git a/tests/wp/settings.c b/tests/wp/settings.c index f9d64b09..c62a3d9c 100644 --- a/tests/wp/settings.c +++ b/tests/wp/settings.c @@ -112,7 +112,7 @@ test_parsing_setup (TestSettingsFixture *self, gconstpointer user_data) self->settings = g_steal_pointer (&settings); /* total no.of settings in the conf file */ - g_assert_cmpint (data.count, ==, 13); + g_assert_cmpint (data.count, ==, 14); } } @@ -129,7 +129,7 @@ static void test_parsing (TestSettingsFixture *self, gconstpointer data) { /* total no.of settings in the conf file */ - g_assert_cmpint (wp_properties_get_count(self->settings), ==, 13); + g_assert_cmpint (wp_properties_get_count(self->settings), ==, 14); } static void @@ -304,6 +304,10 @@ test_wpsettings (TestSettingsFixture *self, gconstpointer data) g_assert_true (wp_settings_get_string (s, "test-property3-int", &value)); g_assert_cmpstr (value, ==, "-20"); + + g_assert_true (wp_settings_get_string (s, "test-prop1-json", + &value)); + g_assert_cmpstr (value, ==, "[ a b c ]"); } { diff --git a/tests/wp/settings.conf b/tests/wp/settings.conf index 27f5b014..a918b23b 100644 --- a/tests/wp/settings.conf +++ b/tests/wp/settings.conf @@ -167,4 +167,5 @@ wireplumber.settings = { } } ] + test-prop1-json = "[ a b c ]" } diff --git a/tests/wplua/scripts/settings.lua b/tests/wplua/scripts/settings.lua index 205d6b4d..d7f0eafc 100644 --- a/tests/wplua/scripts/settings.lua +++ b/tests/wplua/scripts/settings.lua @@ -51,6 +51,9 @@ assert (value == "blahblah") value = Settings.get_string ("test-property3-int", "test-settings") assert (value == "-20") +value = Settings.get_string ("test-prop1-json", "test-settings") +assert (value == "[ a b c ]") + -- test settings _get_float () value = Settings.get_float ("test-property-undefined", "test-settings") assert (value == nil)