From 0ac39e0e5da0d73936d5b846a18315f83139064e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 1 Jul 2026 15:16:03 +0200 Subject: [PATCH 1/2] spa: utils: json: add `spa_json_encode_stringn()` Add the function `spa_json_encode_stringn()` that works very similarly to `spa_json_encode_string()`, with the difference that it works with non-null terminated strings (e.g. `std::string_view`). `spa_json_encode_string()` is then implemented with this new function. Additionally, since the input string is no longer null terminated, escape bytes of value 0. This is probably better that putting it into the result. --- spa/include/spa/utils/json-core.h | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index 9745000cf..49e6eb0b1 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -665,14 +665,18 @@ SPA_API_JSON int spa_json_parse_string(const char *val, int len, char *result) return spa_json_parse_stringn(val, len, result, len+1); } -SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val) +SPA_API_JSON size_t spa_json_encode_stringn(char *dst, size_t capacity, const char *src, size_t length) { - int len = 0; static const char hex[] = { "0123456789abcdef" }; -#define __PUT(c) { if (len < size) *str++ = c; len++; } + + size_t len = 0; +#define __PUT(c) do { if (len < capacity) *dst++ = c; len++; } while (0) + __PUT('"'); - while (*val) { - switch (*val) { + for (; length > 0; length--, src++) { + const char val = *src; + + switch (val) { case '\n': __PUT('\\'); __PUT('n'); break; @@ -690,19 +694,18 @@ SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val) break; case '\\': case '"': - __PUT('\\'); __PUT(*val); + __PUT('\\'); __PUT(val); break; default: - if (*val > 0 && *val < 0x20) { + if ((unsigned char) val < 0x20) { __PUT('\\'); __PUT('u'); __PUT('0'); __PUT('0'); - __PUT(hex[((*val)>>4)&0xf]); __PUT(hex[(*val)&0xf]); + __PUT(hex[(val>>4)&0xf]); __PUT(hex[val&0xf]); } else { - __PUT(*val); + __PUT(val); } break; } - val++; } __PUT('"'); __PUT('\0'); @@ -710,6 +713,11 @@ SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val) return len-1; } +SPA_API_JSON int spa_json_encode_string(char *dst, int capacity, const char *src) +{ + return spa_json_encode_stringn(dst, capacity, src, strlen(src)); +} + /** * \} */ From 151ebb8663b8ca0dab9a1301acd2e759e153a370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 19 Jan 2026 17:00:31 +0100 Subject: [PATCH 2/2] spa: libcamera: device: expose libcamera camera properties A libcamera camera has a set of static properties found in `Camera::properties()`. Some of the properties are already available for pipewire clients (Model, Rotation, Location), most of them are not. So serialize all properties into a strings and make them available on the pipewire device. The keys have the form "api.libcamera.property.." and the values are intended to be valid json values, parsable by python's `json.loads()` or qt's `QJsonValue::fromJson()`. Controls of type "rectangle", "size", or "point" are not supported for now since their json forms are not self-evident. They can be added when the need arises. --- spa/plugins/libcamera/libcamera-device.cpp | 158 ++++++++++++++++++++- spa/plugins/libcamera/libcamera.hpp | 1 + 2 files changed, 154 insertions(+), 5 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index a4691007b..1f96e0352 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -5,12 +5,20 @@ /* SPDX-License-Identifier: MIT */ #include +#include +#include +#include #include #include +#include +#include +#include +#include #include #include #include +#include #include #include #include @@ -43,6 +51,8 @@ struct impl { std::shared_ptr manager; std::shared_ptr camera; + std::vector> properties; + impl(spa_log *log, std::shared_ptr manager, std::shared_ptr camera, @@ -96,11 +106,144 @@ const char *cameraRot(const Camera& camera) return nullptr; } +std::optional +string_to_json(std::string_view str) +{ + std::string result(str.size() + 2, '\0'); + + auto l = spa_json_encode_stringn(result.data(), result.size(), str.data(), str.size()); + if (l > result.size()) { + result.resize(l); + + l = spa_json_encode_stringn(result.data(), result.size(), str.data(), str.size()); + if (l > result.size()) + return {}; + } + + result.resize(l); + return result; +} + +constexpr auto default_control_value_print = [](std::ostream& os, const auto& x) { os << x; }; + +template +[[nodiscard]] +bool +control_value_to_string(std::ostream& os, const libcamera::ControlValue& v, Print print = {}) +{ + if (v.isArray()) { + os << "[ "; + + bool first = true; + for (const auto& x : v.get>()) { + if (!first) + os << ", "; + + print(os, x); + first = false; + } + + os << " ]"; + } + else { + print(os, v.get()); + } + + return true; +} + +[[nodiscard]] +bool +control_value_to_string(std::ostream& os, const libcamera::ControlValue& v, + const libcamera::ControlId *cid) +{ + switch (v.type()) { + case libcamera::ControlTypeNone: + os << "null"; + return true; + case libcamera::ControlTypeBool: + return control_value_to_string(os, v); + case libcamera::ControlTypeByte: + return control_value_to_string(os, v); + case libcamera::ControlTypeUnsigned16: + return control_value_to_string(os, v); + case libcamera::ControlTypeUnsigned32: + return control_value_to_string(os, v); + case libcamera::ControlTypeInteger32: + return control_value_to_string(os, v, [&](std::ostream& os, int32_t x) { + if (cid) { + auto it = cid->enumerators().find(x); + if (it != cid->enumerators().end()) { + auto str = string_to_json(it->second); + if (str) { + os << *str; + return; + } + } + } + + os << x; + }); + case libcamera::ControlTypeInteger64: + return control_value_to_string(os, v); + case libcamera::ControlTypeFloat: + return control_value_to_string(os, v); + case libcamera::ControlTypeString: { + auto str = string_to_json(v.get()); + if (!str) + return false; + + os << *str; + return true; + } + } + + return false; +} + +[[nodiscard]] +std::vector> +control_list_to_dict(const libcamera::ControlList& list, std::string_view prefix) +{ + static const std::locale c_locale("C"); + + std::vector> res; + const auto *id_map = list.idMap(); + std::ostringstream value_s; + std::ostringstream key_s; + + spa_assert(id_map); + + key_s.imbue(c_locale); + + value_s.imbue(c_locale); + value_s << std::boolalpha; + + res.reserve(list.size()); + + for (const auto& [ id, value ] : list) { + auto it = id_map->find(id); + const auto *cid = it != id_map->end() ? it->second : nullptr; + + if (!control_value_to_string(value_s, value, cid)) + continue; + + key_s << prefix << '.'; + + if (cid) + key_s << cid->vendor() << '.' << cid->name(); + else + key_s << id; + + res.emplace_back(std::move(key_s).str(), std::move(value_s).str()); + } + + return res; +} + int emit_info(struct impl *impl, bool full) { - struct spa_dict_item items[12]; struct spa_dict dict; - uint32_t n_items = 0; struct spa_device_info info; Camera& camera = *impl->camera; @@ -108,7 +251,8 @@ int emit_info(struct impl *impl, bool full) info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; -#define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) + std::vector items; +#define ADD_ITEM(key, value) items.push_back({ key, value }) const auto path = "libcamera:" + impl->device_id; ADD_ITEM(SPA_KEY_OBJECT_PATH, path.c_str()); @@ -149,7 +293,10 @@ int emit_info(struct impl *impl, bool full) #undef ADD_ITEM - dict = SPA_DICT_INIT(items, n_items); + for (const auto& [ k, v ] : impl->properties) + items.push_back({ k.c_str(), v.c_str() }); + + dict = SPA_DICT_INIT(items.data(), uint32_t(items.size())); info.props = &dict; spa_device_emit_info(&impl->hooks, &info); @@ -252,7 +399,8 @@ impl::impl(spa_log *log, log(log), device_id(std::move(device_id)), manager(std::move(manager)), - camera(std::move(camera)) + camera(std::move(camera)), + properties(control_list_to_dict(this->camera->properties(), KEY_PROPERTY_PREFIX)) { libcamera_log_topic_init(log); diff --git a/spa/plugins/libcamera/libcamera.hpp b/spa/plugins/libcamera/libcamera.hpp index 7f21863a7..669ae1088 100644 --- a/spa/plugins/libcamera/libcamera.hpp +++ b/spa/plugins/libcamera/libcamera.hpp @@ -14,6 +14,7 @@ #define KEY_VERSION_LIBRARY "api.libcamera.version.library" #define KEY_VERSION_HEADER "api.libcamera.version.header" +#define KEY_PROPERTY_PREFIX "api.libcamera.property" extern "C" {