From dbef053249c4bb1d48b98dc681a13885d5e93c8b Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Tue, 12 May 2026 13:02:42 +0200 Subject: [PATCH 1/9] gconnman_service.cpp: Add connman misspelled word MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connman could set the IPv6·privacy·option as prefered and also preferred. Add the misspelled property value to the map for the same key. Signed-off-by: Eduardo Gonzalez --- src/dbus/gconnman_service.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dbus/gconnman_service.cpp b/src/dbus/gconnman_service.cpp index f5fbcd3..0a9d74d 100644 --- a/src/dbus/gconnman_service.cpp +++ b/src/dbus/gconnman_service.cpp @@ -79,10 +79,11 @@ static constexpr EnumStringMap IPV6_METHOD_MAP{ {IPv6::Method::Fixed, "fixed"}, {IPv6::Method::Auto, "auto"}}}}; -static constexpr EnumStringMap IPV6_PRIVACY_MAP{ +static constexpr EnumStringMap IPV6_PRIVACY_MAP{ {{{IPv6::Privacy::Disabled, "disabled"}, {IPv6::Privacy::Enabled, "enabled"}, - {IPv6::Privacy::Preferred, "preferred"}}}}; + {IPv6::Privacy::Preferred, "preferred"}, + {IPv6::Privacy::Preferred, "prefered"}}}}; Service::Service(DBus* dbus, const gchar* obj_path) : DBusProxy(dbus, SERVICE, obj_path, SERVICE_INTERFACE) {} From e0973c638a8a9b14f5132c421e20a90985f62a1c Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Fri, 8 May 2026 12:14:39 +0200 Subject: [PATCH 2/9] Remove use of cout and print Remove the print method of the different properties. The consumer should define the output stream to put the properties. Remove unused iostream header includes. Signed-off-by: Eduardo Gonzalez --- examples/connmanctl.cpp | 4 +- include/amarula/dbus/connman/gclock.hpp | 3 +- include/amarula/dbus/connman/gservice.hpp | 18 +- include/amarula/dbus/connman/gtechnology.hpp | 3 +- include/amarula/dbus/gproxy.hpp | 1 - src/dbus/gconnman_agent.cpp | 1 - src/dbus/gconnman_clock.cpp | 38 ++-- src/dbus/gconnman_service.cpp | 179 ++++++++++--------- src/dbus/gconnman_technology.cpp | 18 +- tests/gconnman_clock_test.cpp | 20 +-- tests/gconnman_serv_test.cpp | 18 +- tests/gconnman_tech_test.cpp | 8 +- 12 files changed, 158 insertions(+), 153 deletions(-) diff --git a/examples/connmanctl.cpp b/examples/connmanctl.cpp index 74b1965..60beabe 100644 --- a/examples/connmanctl.cpp +++ b/examples/connmanctl.cpp @@ -75,7 +75,7 @@ auto main() -> int { const auto props = tech->properties(); std::cout << "Technology: " << props.getName() << " " << tech->objPath() << "\n"; - props.print(); + std::cout << props; } } } @@ -128,7 +128,7 @@ auto main() -> int { const auto props = (*iterator)->properties(); std::cout << "Service: " << props.getName() << " " << (*iterator)->objPath() << "\n"; - props.print(); + std::cout << props; } else { std::cout << "Service not found: " << arg << "\n"; } diff --git a/include/amarula/dbus/connman/gclock.hpp b/include/amarula/dbus/connman/gclock.hpp index 18d005d..1c8787f 100644 --- a/include/amarula/dbus/connman/gclock.hpp +++ b/include/amarula/dbus/connman/gclock.hpp @@ -25,7 +25,8 @@ struct ClockProperties { return time_server_synced_; } - void print() const; + friend auto operator<<(std::ostream& ostr, + const ClockProperties& object) -> std::ostream&; private: uint64_t time_{0}; diff --git a/include/amarula/dbus/connman/gservice.hpp b/include/amarula/dbus/connman/gservice.hpp index 56b696e..dd4d9e9 100644 --- a/include/amarula/dbus/connman/gservice.hpp +++ b/include/amarula/dbus/connman/gservice.hpp @@ -37,7 +37,8 @@ class IPv4 : public GVariantParser { Auto, }; - void print() const; + friend auto operator<<(std::ostream& ostr, + const IPv4& object) -> std::ostream&; [[nodiscard]] auto getMethod() const { return method_; } [[nodiscard]] auto getAddress() const { return address_; } @@ -65,7 +66,8 @@ struct IPv6 : public GVariantParser { Auto, }; enum class Privacy : uint8_t { Disabled = 0, Enabled, Preferred }; - void print() const; + friend auto operator<<(std::ostream& ostr, + const IPv6& object) -> std::ostream&; [[nodiscard]] auto getMethod() const { return method_; } [[nodiscard]] auto getAddress() const { return address_; } [[nodiscard]] auto getGateway() const { return gateway_; } @@ -87,7 +89,8 @@ struct IPv6 : public GVariantParser { struct Ethernet : public GVariantParser { public: enum class Method : uint8_t { Manual = 0, Auto }; - void print() const; + friend auto operator<<(std::ostream& ostr, + const Ethernet& object) -> std::ostream&; [[nodiscard]] auto getMethod() const { return method_; } [[nodiscard]] auto getInterface() const { return interface_; } [[nodiscard]] auto getAddress() const { return address_; } @@ -106,7 +109,8 @@ struct Ethernet : public GVariantParser { struct Provider : public GVariantParser { public: - void print() const; + friend auto operator<<(std::ostream& ostr, + const Provider& object) -> std::ostream&; [[nodiscard]] auto getHost() const { return host_; } [[nodiscard]] auto getDomain() const { return domain_; } [[nodiscard]] auto getName() const { return name_; } @@ -126,7 +130,8 @@ struct Provider : public GVariantParser { struct Proxy : public GVariantParser { public: enum class Method : uint8_t { Direct = 0, Auto, Manual }; - void print() const; + friend auto operator<<(std::ostream& ostr, + const Proxy& object) -> std::ostream&; [[nodiscard]] auto getMethod() const { return method_; } [[nodiscard]] auto getUrl() const { return url_; } [[nodiscard]] auto getServers() const { return servers_; } @@ -186,7 +191,8 @@ struct ServProperties { OnlineCheckFailed }; - void print() const; + friend auto operator<<(std::ostream& ostr, + const ServProperties& object) -> std::ostream&; [[nodiscard]] auto getState() const { return state_; } [[nodiscard]] auto getType() const { return type_; } [[nodiscard]] auto getSecurity() const { return security_; } diff --git a/include/amarula/dbus/connman/gtechnology.hpp b/include/amarula/dbus/connman/gtechnology.hpp index c11e24f..53010a2 100644 --- a/include/amarula/dbus/connman/gtechnology.hpp +++ b/include/amarula/dbus/connman/gtechnology.hpp @@ -29,7 +29,8 @@ struct TechProperties { [[nodiscard]] auto isConnected() const { return connected_; } [[nodiscard]] auto isTethering() const { return tethering_; } [[nodiscard]] auto getTetheringFreq() const { return tethering_freq_; } - void print() const; + friend auto operator<<(std::ostream& ostr, + const TechProperties& object) -> std::ostream&; private: bool powered_ = false; diff --git a/include/amarula/dbus/gproxy.hpp b/include/amarula/dbus/gproxy.hpp index 57ef01e..95940c5 100644 --- a/include/amarula/dbus/gproxy.hpp +++ b/include/amarula/dbus/gproxy.hpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/src/dbus/gconnman_agent.cpp b/src/dbus/gconnman_agent.cpp index e032070..7d36400 100644 --- a/src/dbus/gconnman_agent.cpp +++ b/src/dbus/gconnman_agent.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include diff --git a/src/dbus/gconnman_clock.cpp b/src/dbus/gconnman_clock.cpp index 2cc45a9..45ee737 100644 --- a/src/dbus/gconnman_clock.cpp +++ b/src/dbus/gconnman_clock.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -97,26 +96,25 @@ void Clock::setTimeServers(const std::vector& servers, g_variant_builder_clear(&builder); } -void ClockProperties::print() const { - LCM_LOG("@@@@@@@@@@ ClockProperties: @@@@@@@@@@@@@@@\n"); - LCM_LOG(TIME_STR << ": " << time_ << " ("); - const auto time_value = static_cast(time_); - LCM_LOG(std::put_time(std::localtime(&time_value), "%Y-%m-%d %H:%M:%S") - << ")\n"); - LCM_LOG(TIMEUPDATES_STR << ": " << TIME_UPDATE_MAP.toString(time_updates_) - << '\n'); - LCM_LOG(TIMEZONE_STR << ": " << timezone_ << '\n'); - LCM_LOG(TIMEZONEUPDATES_STR - << ": " << TIME_ZONE_UPDATE_MAP.toString(timezone_updates_) - << '\n'); - LCM_LOG(TIMESERVERSYNCED_STR << ": " << std::boolalpha - << time_server_synced_ << '\n'); - LCM_LOG(TIMESERVERS_STR << ": "); - for (const auto& server : time_servers_) { - LCM_LOG(server << ' '); +auto operator<<(std::ostream& ost, + const ClockProperties& obj) -> std::ostream& { + ost << TIME_STR << ": " << obj.time_ << " ("; + const auto time_value = static_cast(obj.time_); + ost << std::put_time(std::localtime(&time_value), "%Y-%m-%d %H:%M:%S") + << ")\n"; + ost << TIMEUPDATES_STR << ": " + << TIME_UPDATE_MAP.toString(obj.time_updates_) << '\n'; + ost << TIMEZONE_STR << ": " << obj.timezone_ << '\n'; + ost << TIMEZONEUPDATES_STR << ": " + << TIME_ZONE_UPDATE_MAP.toString(obj.timezone_updates_) << '\n'; + ost << TIMESERVERSYNCED_STR << ": " << std::boolalpha + << obj.time_server_synced_ << '\n'; + ost << TIMESERVERS_STR << ": "; + for (const auto& server : obj.time_servers_) { + ost << server << ' '; } - LCM_LOG('\n'); - LCM_LOG("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + ost << '\n'; + return ost; } } // namespace Amarula::DBus::G::Connman diff --git a/src/dbus/gconnman_service.cpp b/src/dbus/gconnman_service.cpp index 0a9d74d..2da369b 100644 --- a/src/dbus/gconnman_service.cpp +++ b/src/dbus/gconnman_service.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -288,132 +287,136 @@ void ServProperties::update(const gchar* key, GVariant* value) { } } -void ServProperties::print() const { - LCM_LOG("@@@@@@@@@@ ServProperties: @@@@@@@@@@@@@@@\n"); - LCM_LOG("State: " << STATE_MAP.toString(state_) << '\n'); - if (error_ != Error::None) { - LCM_LOG("Error: " << ERROR_MAP.toString(error_) << '\n'); +auto operator<<(std::ostream& ost, const ServProperties& obj) -> std::ostream& { + ost << "State: " << STATE_MAP.toString(obj.state_) << '\n'; + if (obj.error_ != Error::None) { + ost << "Error: " << ERROR_MAP.toString(obj.error_) << '\n'; } - LCM_LOG("Name: " << name_ << '\n'); - LCM_LOG("Type: " << TYPE_MAP.toString(type_) << '\n'); - LCM_LOG("Strength: " << static_cast(strength_) << '\n'); - LCM_LOG("AutoConnect: " << std::boolalpha << autoconnect_ << '\n'); - LCM_LOG("mDNS: " << mdns_ << '\n'); - LCM_LOG("Favorite: " << favorite_ << '\n'); - LCM_LOG("Immutable: " << immutable_ << '\n'); - LCM_LOG("Roaming: " << roaming_ << '\n'); - - if (security_) { - LCM_LOG("Security: "); - for (const auto& sec : security_.value()) { - LCM_LOG(SECURITY_MAP.toString(sec) << ' '); + ost << "Name: " << obj.name_ << '\n'; + ost << "Type: " << TYPE_MAP.toString(obj.type_) << '\n'; + ost << "Strength: " << static_cast(obj.strength_) << '\n'; + ost << "AutoConnect: " << std::boolalpha << obj.autoconnect_ << '\n'; + ost << "mDNS: " << obj.mdns_ << '\n'; + ost << "Favorite: " << obj.favorite_ << '\n'; + ost << "Immutable: " << obj.immutable_ << '\n'; + ost << "Roaming: " << obj.roaming_ << '\n'; + + if (obj.security_) { + ost << "Security: "; + for (const auto& sec : obj.security_.value()) { + ost << SECURITY_MAP.toString(sec) << ' '; } - LCM_LOG('\n'); + ost << '\n'; } - if (name_servers_) { - LCM_LOG("Nameservers: "); - for (const auto& nserver : name_servers_.value()) { - LCM_LOG(nserver << ' '); + if (obj.name_servers_) { + ost << "Nameservers: "; + for (const auto& nserver : obj.name_servers_.value()) { + ost << nserver << ' '; } - LCM_LOG('\n'); + ost << '\n'; } - if (name_servers_conf_) { - LCM_LOG("Nameservers.Configuration: "); - for (const auto& nserver : name_servers_conf_.value()) { - LCM_LOG(nserver << ' '); + if (obj.name_servers_conf_) { + ost << "Nameservers.Configuration: "; + for (const auto& nserver : obj.name_servers_conf_.value()) { + ost << nserver << ' '; } - LCM_LOG('\n'); + ost << '\n'; } - if (domains_) { - LCM_LOG("Domains: "); - for (const auto& domain : domains_.value()) { - LCM_LOG(domain << ' '); + if (obj.domains_) { + ost << "Domains: "; + for (const auto& domain : obj.domains_.value()) { + ost << domain << ' '; } - LCM_LOG('\n'); + ost << '\n'; } - if (time_servers_) { - LCM_LOG("TimeServers: "); - for (const auto& tserver : time_servers_.value()) { - LCM_LOG(tserver << ' '); + if (obj.time_servers_) { + ost << "TimeServers: "; + for (const auto& tserver : obj.time_servers_.value()) { + ost << tserver << ' '; } - LCM_LOG('\n'); + ost << '\n'; } - if (ipv4_) { - ipv4_.value().print(); + if (obj.ipv4_) { + ost << obj.ipv4_.value(); } - if (ipv6_) { - ipv6_.value().print(); + if (obj.ipv6_) { + ost << obj.ipv6_.value(); } - if (ethernet_) { - ethernet_.value().print(); + if (obj.ethernet_) { + ost << obj.ethernet_.value(); } - if (provider_) { - provider_.value().print(); + if (obj.provider_) { + ost << obj.provider_.value(); } - if (proxy_) { - proxy_.value().print(); + if (obj.proxy_) { + ost << obj.proxy_.value(); } - LCM_LOG("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); + return ost; } -void Ethernet::print() const { - LCM_LOG("Ethernet:\n"); - LCM_LOG(" Method: " << ETHERNET_METHOD_MAP.toString(method_) << '\n'); - LCM_LOG(" Interface: " << interface_ << '\n'); - LCM_LOG(" Address: " << address_ << '\n'); - LCM_LOG(" MTU: " << mtu_ << '\n'); +auto operator<<(std::ostream& ost, const Ethernet& obj) -> std::ostream& { + ost << "Ethernet:\n"; + ost << " Method: " << ETHERNET_METHOD_MAP.toString(obj.method_) << '\n'; + ost << " Interface: " << obj.interface_ << '\n'; + ost << " Address: " << obj.address_ << '\n'; + ost << " MTU: " << obj.mtu_ << '\n'; + return ost; } -void IPv4::print() const { - LCM_LOG("IPv4:\n"); - LCM_LOG(" Method: " << IPV4_METHOD_MAP.toString(method_) << '\n'); - LCM_LOG(" Address: " << address_ << '\n'); - LCM_LOG(" Netmask: " << netmask_ << '\n'); - LCM_LOG(" Gateway: " << gateway_ << '\n'); +auto operator<<(std::ostream& ost, const IPv4& obj) -> std::ostream& { + ost << "IPv4:\n"; + ost << " Method: " << IPV4_METHOD_MAP.toString(obj.method_) << '\n'; + ost << " Address: " << obj.address_ << '\n'; + ost << " Netmask: " << obj.netmask_ << '\n'; + ost << " Gateway: " << obj.gateway_ << '\n'; + return ost; } -void Provider::print() const { - LCM_LOG("Provider:\n"); - LCM_LOG(" Host: " << host_ << '\n'); - LCM_LOG(" Domain: " << domain_ << '\n'); - LCM_LOG(" Name: " << name_ << '\n'); - LCM_LOG(" Type: " << type_ << '\n'); +auto operator<<(std::ostream& ost, const Provider& obj) -> std::ostream& { + ost << "Provider:\n"; + ost << " Host: " << obj.host_ << '\n'; + ost << " Domain: " << obj.domain_ << '\n'; + ost << " Name: " << obj.name_ << '\n'; + ost << " Type: " << obj.type_ << '\n'; + return ost; } -void IPv6::print() const { - LCM_LOG("IPv6:\n"); - LCM_LOG(" Method: " << IPV6_METHOD_MAP.toString(method_) << '\n'); - LCM_LOG(" Address: " << address_ << '\n'); - LCM_LOG(" Gateway: " << gateway_ << '\n'); - LCM_LOG(" Privacy: " << static_cast(privacy_) << '\n'); - LCM_LOG(" Prefix Length: " << static_cast(prefix_length_) << '\n'); +auto operator<<(std::ostream& ost, const IPv6& obj) -> std::ostream& { + ost << "IPv6:\n"; + ost << " Method: " << IPV6_METHOD_MAP.toString(obj.method_) << '\n'; + ost << " Address: " << obj.address_ << '\n'; + ost << " Gateway: " << obj.gateway_ << '\n'; + ost << " Privacy: " << static_cast(obj.privacy_) << '\n'; + ost << " Prefix Length: " << static_cast(obj.prefix_length_) << '\n'; + return ost; } -void Proxy::print() const { - LCM_LOG("Proxy:\n"); - LCM_LOG(" Method: " << PROXY_METHOD_MAP.toString(method_) << '\n'); - LCM_LOG(" URL: " << url_ << '\n'); - LCM_LOG(" Servers: "); - for (const auto& server : servers_) { - LCM_LOG(server << ' '); +auto operator<<(std::ostream& ost, const Proxy& obj) -> std::ostream& { + ost << "Proxy:\n"; + ost << " Method: " << PROXY_METHOD_MAP.toString(obj.method_) << '\n'; + ost << " URL: " << obj.url_ << '\n'; + ost << " Servers: "; + for (const auto& server : obj.servers_) { + ost << server << ' '; } - LCM_LOG('\n'); + ost << '\n'; - LCM_LOG(" Excludes: "); - for (const auto& exclude : excludes_) { - LCM_LOG(exclude << ' '); + ost << " Excludes: "; + for (const auto& exclude : obj.excludes_) { + ost << exclude << ' '; } - LCM_LOG('\n'); + ost << '\n'; + return ost; } } // namespace Amarula::DBus::G::Connman diff --git a/src/dbus/gconnman_technology.cpp b/src/dbus/gconnman_technology.cpp index d94a80b..15359ef 100644 --- a/src/dbus/gconnman_technology.cpp +++ b/src/dbus/gconnman_technology.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "gconnman_private.hpp" @@ -60,15 +59,14 @@ void TechProperties::update(const gchar* key, GVariant* value) { } } -void TechProperties::print() const { - LCM_LOG("@@@@@@@@@@ TechProperties: @@@@@@@@@@@@@@@\n"); - LCM_LOG(NAME_STR << ": " << name_ << '\n'); - LCM_LOG(TYPE_STR << ": " << TYPE_MAP.toString(type_) << '\n'); - LCM_LOG(POWERED_STR << ": " << std::boolalpha << powered_ << '\n'); - LCM_LOG(CONNECTED_STR << ": " << std::boolalpha << connected_ << '\n'); - LCM_LOG(TETHERING_STR << ": " << std::boolalpha << tethering_ << '\n'); - LCM_LOG(TETHERINGFREQ_STR << ": " << tethering_freq_ << " \n"); - LCM_LOG("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"); +auto operator<<(std::ostream& ost, const TechProperties& obj) -> std::ostream& { + ost << NAME_STR << ": " << obj.name_ << '\n'; + ost << TYPE_STR << ": " << TYPE_MAP.toString(obj.type_) << '\n'; + ost << POWERED_STR << ": " << std::boolalpha << obj.powered_ << '\n'; + ost << CONNECTED_STR << ": " << std::boolalpha << obj.connected_ << '\n'; + ost << TETHERING_STR << ": " << std::boolalpha << obj.tethering_ << '\n'; + ost << TETHERINGFREQ_STR << ": " << obj.tethering_freq_ << " \n"; + return ost; } } // namespace Amarula::DBus::G::Connman diff --git a/tests/gconnman_clock_test.cpp b/tests/gconnman_clock_test.cpp index c34ac0e..c76a6ef 100644 --- a/tests/gconnman_clock_test.cpp +++ b/tests/gconnman_clock_test.cpp @@ -27,7 +27,7 @@ TEST(Connman, ClockSetTimeUpdates) { EXPECT_NE(callback_tid, main_tid); EXPECT_NE(callback_tid, loop_tid); std::cout << "onPropertyChanged:\n"; - props.print(); + std::cout << props; }); connman.clock()->setTimeUpdates( TimeUpdate::Auto, [main_tid = thread_bundle.main_tid, @@ -45,7 +45,7 @@ TEST(Connman, ClockSetTime) { const guint time = TEST_TIME; connman.clock()->onPropertyChanged([](auto& props) { std::cout << "onPropertyChanged:\n"; - props.print(); + std::cout << props; }); connman.clock()->setTimeUpdates( TimeUpdate::Manual, [&connman](auto success) { @@ -54,7 +54,7 @@ TEST(Connman, ClockSetTime) { EXPECT_TRUE(success); connman.clock()->getProperties([](auto& props) { std::cout << "getProperties:\n"; - props.print(); + std::cout << props; }); }); }); @@ -65,7 +65,7 @@ TEST(Connman, ClockSetTimeServers1) { const Connman connman; connman.clock()->onPropertyChanged([](auto& props) { std::cout << "onPropertyChanged:\n"; - props.print(); + std::cout << props; }); const std::vector servers = {"time1.google.com", "time2.google.com"}; @@ -78,7 +78,7 @@ TEST(Connman, ClockSetTimeServers2) { const Connman connman; connman.clock()->onPropertyChanged([](auto& props) { std::cout << "onPropertyChanged:\n"; - props.print(); + std::cout << props; }); const std::vector servers = {"time1.example.com", "time2.example.com"}; @@ -86,7 +86,7 @@ TEST(Connman, ClockSetTimeServers2) { EXPECT_TRUE(success); connman.clock()->getProperties([](auto& props) { std::cout << "getProperties:\n"; - props.print(); + std::cout << props; }); }); } @@ -96,7 +96,7 @@ TEST(Connman, ClockSetTimeZoneUpdates) { const Connman connman; connman.clock()->onPropertyChanged([](auto& props) { std::cout << "onPropertyChanged:\n"; - props.print(); + std::cout << props; }); connman.clock()->setTimeZoneUpdates( TimeZoneUpdate::Auto, @@ -108,7 +108,7 @@ TEST(Connman, ClockSetTimeZone) { const Connman connman; connman.clock()->onPropertyChanged([](auto& props) { std::cout << "onPropertyChanged:\n"; - props.print(); + std::cout << props; }); connman.clock()->setTimeZoneUpdates( TimeZoneUpdate::Manual, [&connman](auto success) { @@ -118,7 +118,7 @@ TEST(Connman, ClockSetTimeZone) { EXPECT_TRUE(success); connman.clock()->getProperties([](auto& props) { std::cout << "getProperties:\n"; - props.print(); + std::cout << props; }); }); }); @@ -145,6 +145,6 @@ TEST(Connman, ClockGetProperties) { const auto callback_tid = std::this_thread::get_id(); EXPECT_NE(callback_tid, main_tid); EXPECT_NE(callback_tid, loop_tid); - props.print(); + std::cout << props; }); } diff --git a/tests/gconnman_serv_test.cpp b/tests/gconnman_serv_test.cpp index 580e1eb..0f9e5e4 100644 --- a/tests/gconnman_serv_test.cpp +++ b/tests/gconnman_serv_test.cpp @@ -40,7 +40,7 @@ TEST(Connman, getServs) { << "Technology " << prop.getName() << " was not powered ON"; std::cout << "onPropertyChanged:\n"; - prop.print(); + std::cout << prop; }); const auto prop = tech->properties(); const auto name = prop.getName(); @@ -67,7 +67,7 @@ TEST(Connman, getServs) { ASSERT_FALSE(services.empty()); for (const auto& serv : services) { const auto props = serv->properties(); - props.print(); + std::cout << props; } }); } @@ -92,10 +92,10 @@ TEST(Connman, setNameServers) { for (const auto& serv : services) { const auto props = serv->properties(); const auto name = props.getName(); - props.print(); + std::cout << props; serv->onPropertyChanged([](const auto& properties) { std::cout << "onPropertyChange:\n"; - properties.print(); + std::cout << properties; }); serv->setNameServers( {"8.8.8.8", "4.4.4.4"}, @@ -143,7 +143,7 @@ TEST(Connman, ForgetAndDisconnectService) { EXPECT_NE(callback_tid, loop_tid); EXPECT_TRUE(success); std::cout << "Service removed: " << name << '\n'; - serv->properties().print(); + std::cout << serv->properties(); }); } else if (state == State::Ready || state == State::Online) { std::cout << "Disconnecting service: " << name << '\n'; @@ -154,7 +154,7 @@ TEST(Connman, ForgetAndDisconnectService) { EXPECT_TRUE(success); std::cout << "Service disconnected: " << serv->objPath() << '\n'; - serv->properties().print(); + std::cout << serv->properties(); }); } } @@ -207,7 +207,7 @@ TEST(Connman, ConnectWifi) { if (state == State::Idle) { std::cout << "Connecting to service: " << name << '\n'; - props.print(); + std::cout << props; serv->onPropertyChanged( [main_tid, loop_tid](const auto& properties) { const auto callback_tid = @@ -215,7 +215,7 @@ TEST(Connman, ConnectWifi) { EXPECT_NE(callback_tid, main_tid); EXPECT_NE(callback_tid, loop_tid); std::cout << "onPropertyChange:\n"; - properties.print(); + std::cout << properties; }); manager->registerAgent( manager->internalAgentPath(), @@ -236,7 +236,7 @@ TEST(Connman, ConnectWifi) { std::cout << "Service connected successfully: " << success << '\n'; - serv->properties().print(); + std::cout << serv->properties(); manager->unregisterAgent( manager->internalAgentPath()); }); diff --git a/tests/gconnman_tech_test.cpp b/tests/gconnman_tech_test.cpp index 8c9ba7a..419e486 100644 --- a/tests/gconnman_tech_test.cpp +++ b/tests/gconnman_tech_test.cpp @@ -30,7 +30,7 @@ TEST(Connman, getTechs) { EXPECT_TRUE(props.isPowered()) << "Technology is connected but not powered"; } - props.print(); + std::cout << props; } }); } @@ -63,7 +63,7 @@ TEST(Connman, PowerOnAllTechnologies) { EXPECT_NE(callback_tid, main_tid); EXPECT_NE(callback_tid, loop_tid); std::cout << "onPropertyChanged:\n"; - prop.print(); + std::cout << prop; }); const auto prop = tech->properties(); const auto name = prop.getName(); @@ -141,7 +141,7 @@ TEST(Connman, PowerOffAllTechnologies) { << "Technology " << prop.getName() << " was not powered OFF"; std::cout << "onPropertyChanged:\n"; - prop.print(); + std::cout << prop; }); const auto prop = tech->properties(); const auto name = prop.getName(); @@ -160,4 +160,4 @@ TEST(Connman, PowerOffAllTechnologies) { }); } ASSERT_TRUE(called) << "setPowered callback was never called"; -} \ No newline at end of file +} From 42e85c18854697b0348c324ae1d037b6009c6156 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Fri, 8 May 2026 12:25:12 +0200 Subject: [PATCH 3/9] connmanctl.cpp: Fix logic jump Signed-off-by: Eduardo Gonzalez --- examples/connmanctl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/connmanctl.cpp b/examples/connmanctl.cpp index 60beabe..d85fbaa 100644 --- a/examples/connmanctl.cpp +++ b/examples/connmanctl.cpp @@ -79,7 +79,7 @@ auto main() -> int { } } } - if (cmd == "agent") { + else if (cmd == "agent") { auto on_register = [arg](const auto success) { if (success) { std::cout << "Agent " From b72234e5c3ee7ec3ef228eec62261bab23c5c60b Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Tue, 5 May 2026 16:04:47 +0200 Subject: [PATCH 4/9] Add readline in connmanctl example Add readline in the connmanctl_dbus example. Allows completion, line editing, and interactive history while using the app. Improve command string manipulation and align better on how original connmanctl works. Signed-off-by: Eduardo Gonzalez --- examples/CMakeLists.txt | 2 + examples/connmanctl.cpp | 463 +++++++++++++++++++++++++++++----------- 2 files changed, 338 insertions(+), 127 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c0a2070..cbba90c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,8 @@ if(BUILD_CONNMAN) add_executable(connmanctl_dbus connmanctl.cpp) target_link_libraries(connmanctl_dbus PRIVATE GConnmanDbus) + pkg_check_modules(READLINE REQUIRED IMPORTED_TARGET readline) + target_link_libraries(connmanctl_dbus PRIVATE PkgConfig::READLINE) install( TARGETS connmanctl_dbus diff --git a/examples/connmanctl.cpp b/examples/connmanctl.cpp index d85fbaa..6fc2050 100644 --- a/examples/connmanctl.cpp +++ b/examples/connmanctl.cpp @@ -1,3 +1,10 @@ +// clang-format off +#include +#include +#include +#include +// clang-format on + #include #include #include @@ -13,39 +20,235 @@ using Amarula::DBus::G::Connman::Connman; using Amarula::DBus::G::Connman::TechProperties; using State = Amarula::DBus::G::Connman::ServProperties::State; +using TechnologyType = TechProperties::Type; + +std::atomic has_message{false}; +std::ostringstream message; +std::mutex message_mutex; +std::mutex hash_mutex; +std::mutex cin_mutex; +std::condition_variable cin_cv; + +std::string passphrase; +std::string connecting_name; +std::atomic connecting = false; + +auto event_hook() -> int { + if (has_message.exchange(false)) { + rl_clear_visible_line(); + { + std::lock_guard lock(message_mutex); + std::cout << message.str(); + message.str(""); + message.clear(); + } + rl_on_new_line(); + rl_redisplay(); + } + + if (connecting) { + std::string prompt = "Enter passphrase for " + connecting_name + ": "; + + rl_set_prompt(prompt.c_str()); + rl_redisplay(); + } + return 0; +} + +enum class Command : std::uint8_t { + Technologies, + Services, + Scan, + Enable, + Disable, + Connect, + Disconnect, + Remove, + Agent, + Quit +}; + +inline const std::unordered_map command_map = { + {Command::Technologies, "technologies"}, + {Command::Services, "services"}, + {Command::Scan, "scan"}, + {Command::Enable, "enable"}, + {Command::Disable, "disable"}, + {Command::Connect, "connect"}, + {Command::Disconnect, "disconnect"}, + {Command::Remove, "remove"}, + {Command::Agent, "agent"}, + {Command::Quit, "quit"}}; + +inline const std::unordered_map tech_map = { + {TechnologyType::Ethernet, "ethernet"}, + {TechnologyType::Wifi, "wifi"}, + {TechnologyType::Cellular, "cellular"}, + {TechnologyType::Bluetooth, "bluetooth"}, + {TechnologyType::Vpn, "vpn"}, + {TechnologyType::Wired, "wired"}, + {TechnologyType::P2p, "p2p"}, + {TechnologyType::Gps, "gps"}, + {TechnologyType::Gadget, "gadget"}}; + +inline const std::vector commands_container = [] { + std::vector string_vector; + string_vector.reserve(command_map.size()); + + for (const auto& [cmd, name] : command_map) { + string_vector.emplace_back(name); + } + + return string_vector; +}(); + +inline const std::vector on_off_container{"on", "off"}; +inline const std::vector enable_disable_container{"enable", + "disable"}; + +inline std::vector services_container; +inline std::vector technologies_container; + +void printContainer(const std::vector& container) { + for (size_t i = 0; i < container.size(); ++i) { + std::cout << container[i]; + if (i + 1 < container.size()) { + std::cout << ", "; + } + } + std::cout << "\n"; +} + +enum class Mode : std::uint8_t { Command, ServiceHash, TechHash, OnOff }; + +inline Mode completion_mode = Mode::Command; + +auto command_generator(const char* text, int state) -> char* { + static size_t index; + static size_t len; + static std::vector variable_container; + + if (state == 0) { + index = 0; + len = strlen(text); + if (completion_mode == Mode::Command) { + variable_container = commands_container; + } else if (completion_mode == Mode::ServiceHash) { + std::lock_guard lock(hash_mutex); + variable_container = services_container; + } else if (completion_mode == Mode::TechHash) { + std::lock_guard lock(hash_mutex); + variable_container = technologies_container; + } else if (completion_mode == Mode::OnOff) { + variable_container = on_off_container; + } + } + + while (index < variable_container.size()) { + const auto cmd = variable_container[index++]; + + if (cmd.compare(0, len, text) == 0) { + return strdup(cmd.c_str()); + } + } + + return nullptr; +} + +auto completion(const char* text, int start, int end) -> char** { + (void)start; + (void)end; + if (start == 0) { + completion_mode = Mode::Command; + } else { + std::string line = rl_line_buffer; + std::istringstream iss(line); + std::string command; + std::string arg; + iss >> command >> arg; + if (command == command_map.at(Command::Agent)) { + completion_mode = Mode::OnOff; + } else if (command == command_map.at(Command::Enable) || + command == command_map.at(Command::Disable) || + command == command_map.at(Command::Scan)) { + completion_mode = Mode::TechHash; + } else if (command == command_map.at(Command::Connect) || + command == command_map.at(Command::Disconnect) || + command == command_map.at(Command::Remove) || + command == command_map.at(Command::Services)) { + completion_mode = Mode::ServiceHash; + } else { + return nullptr; + } + if (!arg.empty() && rl_point > 0 && rl_point == line.size() && + line[rl_point - 1] == ' ') { + return nullptr; + } + } + + return rl_completion_matches(text, command_generator); +} auto main() -> int { - std::mutex cin_mutex; - std::condition_variable cin_cv; - bool connecting = false; - Amarula::Log::enable(true); + rl_event_hook = event_hook; + rl_attempted_completion_function = completion; Connman connman; const auto manager = connman.manager(); manager->onRequestInputPassphrase([&](const auto& service) -> auto { const auto name = service->properties().getName(); - - std::string passphrase; - { - const std::unique_lock lock(cin_mutex); - std::cout << "Enter passphrase for " << name << " service\n"; - std::getline(std::cin, passphrase); - connecting = false; - } - cin_cv.notify_one(); + std::unique_lock lock(cin_mutex); + connecting = true; + connecting_name = name; + cin_cv.wait(lock, [] { return !connecting; }); return std::pair{true, passphrase}; }); - constexpr int CIN_WAIT_TIMEOUT_SECONDS = 30; + manager->onServicesChanged([](const auto& services) { + std::lock_guard lock(hash_mutex); + services_container.clear(); + for (const auto& serv : services) { + const auto path = serv->objPath(); + services_container.push_back( + path.substr(path.find_last_of('/') + 1)); + const auto props = serv->properties(); + const auto name = props.getName(); + if (!name.empty()) { + services_container.push_back(name); + } + } + }); + manager->onTechnologiesChanged([](const auto& technologies) { + std::lock_guard lock(hash_mutex); + technologies_container.clear(); + for (const auto& tech : technologies) { + const auto props = tech->properties(); + technologies_container.push_back( + tech_map.at(props.getType()).data()); + } + }); + std::string line; while (true) { - std::cout << "connmanctl> "; - { - std::unique_lock lock(cin_mutex); - cin_cv.wait_for(lock, - std::chrono::seconds(CIN_WAIT_TIMEOUT_SECONDS), - [&connecting] { return !connecting; }); - if (!std::getline(std::cin, line)) { - break; // EOF or error + char* raw_line = readline("connmanctl> "); + if (raw_line == nullptr) { + break; + } + + line = std::string(raw_line); + free(raw_line); + + if (connecting) { + { + std::lock_guard lock(cin_mutex); + passphrase = line; + connecting = false; + cin_cv.notify_all(); } + rl_set_prompt("connmanctl> "); + continue; + } + + if (!line.empty()) { + add_history(line.c_str()); } // Trim whitespace line.erase(line.begin(), std::ranges::find_if(line, [](int character) { @@ -57,16 +260,23 @@ auto main() -> int { }) .base(), line.end()); - std::istringstream iss(line); std::string cmd; std::string arg; iss >> cmd >> arg; + auto match_service = [&arg](const auto& service) { + const auto props = service->properties(); + const auto name = props.getName(); + const auto path = service->objPath(); + const auto last = path.substr(path.find_last_of('/') + 1); + + return last == arg || name == arg; + }; - if (cmd == "exit" || cmd == "quit") { + if (cmd == command_map.at(Command::Quit)) { break; } - if (cmd == "technologies") { + if (cmd == command_map.at(Command::Technologies)) { const auto techs = manager->technologies(); if (techs.empty()) { std::cout << "No technologies available.\n"; @@ -78,14 +288,17 @@ auto main() -> int { std::cout << props; } } - } - else if (cmd == "agent") { + } else if (cmd == command_map.at(Command::Agent)) { auto on_register = [arg](const auto success) { if (success) { - std::cout << "Agent " - << ((arg == "on") ? "registered" : "unregistered") - << "\n"; + std::lock_guard lock(message_mutex); + message + << "Agent " + << ((arg == on_off_container.at(0)) ? "registered" + : "unregistered") + << "\n"; } + has_message = true; }; if (arg == "on") { manager->registerAgent(manager->internalAgentPath(), @@ -94,9 +307,10 @@ auto main() -> int { manager->unregisterAgent(manager->internalAgentPath(), on_register); } else { - std::cout << "Usage: agent on/off\n"; + std::cout << "Usage: agent " << on_off_container.at(0) << "/" + << on_off_container.at(1) << "\n"; } - } else if (cmd == "services") { + } else if (cmd == command_map.at(Command::Services)) { const auto services = manager->services(); if (services.empty()) { std::cout << "No services available.\n"; @@ -115,15 +329,14 @@ auto main() -> int { } else if (props.getState() == State::Ready) { std::cout << "R"; } + const auto path = service->objPath(); std::cout << " " << props.getName() << " " - << service->objPath() << "\n"; + << path.substr(path.find_last_of('/') + 1) + << "\n"; } } else { - auto iterator = std::ranges::find_if( - services, [&arg](const auto& service) { - return service->objPath() == arg || - service->properties().getName() == arg; - }); + auto iterator = + std::ranges::find_if(services, match_service); if (iterator != services.end()) { const auto props = (*iterator)->properties(); std::cout << "Service: " << props.getName() << " " @@ -134,82 +347,87 @@ auto main() -> int { } } } - } else if (cmd == "scan") { - if (arg.empty() || - (arg != "wifi" && arg != "ethernet" && arg != "bluetooth" && - arg != "cellular" && arg != "vpn")) { - std::cout << "Usage: scan \n"; - std::cout << "Available technologies: wifi, ethernet, " - "bluetooth, cellular, vpn\n"; - continue; + } else if (cmd == command_map.at(Command::Scan)) { + { + std::lock_guard lock(hash_mutex); + if (arg.empty() || + std::find(technologies_container.begin(), + technologies_container.end(), + arg) == technologies_container.end()) { + std::cout << "Usage: scan \n"; + std::cout << "Available technologies: "; + printContainer(technologies_container); + continue; + } } const auto techs = manager->technologies(); for (const auto& tech : techs) { const auto props = tech->properties(); const auto name = props.getName(); - if ((props.getType() == TechProperties::Type::Wifi && - arg == "wifi") || - (props.getType() == TechProperties::Type::Ethernet && - arg == "ethernet") || - (props.getType() == TechProperties::Type::Bluetooth && - arg == "bluetooth") || - (props.getType() == TechProperties::Type::Cellular && - arg == "cellular") || - (props.getType() == TechProperties::Type::Vpn && - arg == "vpn")) { + if (tech_map.at(props.getType()) == arg) { std::cout << "Scanning " << name << "...\n"; tech->scan([name](bool success) { - if (success) { - std::cout << "Technology " << name - << " scanned successfully.\n"; - } else { - std::cout << "Failed to scan technology " << name - << ".\n"; + { + std::lock_guard lock(message_mutex); + if (success) { + message << "Technology " << name + << " scanned successfully.\n"; + } else { + message << "Failed to scan technology " << name + << ".\n"; + } } + has_message = true; }); break; } } - } else if (cmd == "enable" || cmd == "disable") { - if (arg.empty() || - (arg != "wifi" && arg != "ethernet" && arg != "bluetooth" && - arg != "cellular" && arg != "vpn")) { - std::cout << "Usage: enable/disable \n"; - std::cout << "Available technologies: wifi, ethernet, " - "bluetooth, cellular, vpn\n"; - continue; + } else if (cmd == command_map.at(Command::Enable) || + cmd == command_map.at(Command::Disable)) { + { + std::lock_guard lock(hash_mutex); + if (arg.empty() || + std::find(technologies_container.begin(), + technologies_container.end(), + arg) == technologies_container.end()) { + std::cout << "Usage: " << enable_disable_container[0] << "/" + << enable_disable_container[1] + << " \n"; + std::cout << "Available technologies:"; + printContainer(technologies_container); + continue; + } } - const bool enable = (cmd == "enable"); + const bool enable = (cmd == enable_disable_container.at(0)); std::cout << (enable ? "Enabling" : "Disabling") << " technology: " << arg << "\n"; const auto techs = manager->technologies(); for (const auto& tech : techs) { const auto props = tech->properties(); const auto name = props.getName(); - if ((props.getType() == TechProperties::Type::Wifi && - arg == "wifi") || - (props.getType() == TechProperties::Type::Ethernet && - arg == "ethernet") || - (props.getType() == TechProperties::Type::Bluetooth && - arg == "bluetooth") || - (props.getType() == TechProperties::Type::Cellular && - arg == "cellular") || - (props.getType() == TechProperties::Type::Vpn && - arg == "vpn")) { - if ((!props.isPowered() && cmd == "enable") || - (props.isPowered() && cmd == "disable")) { + if (tech_map.at(props.getType()) == arg) { + if ((!props.isPowered() && + cmd == enable_disable_container.at(0)) || + (props.isPowered() && + cmd == enable_disable_container.at(1))) { std::cout << (enable ? "Enabling " : "Disabling ") << name << "...\n"; tech->setPowered(enable, [name, enable](bool success) { - if (success) { - std::cout << "Technology " << name - << (enable ? " enabled" : " disabled") - << " successfully.\n"; - } else { - std::cout << "Failed to " - << (enable ? " enable" : " disable") - << " technology " << name << "\n"; + { + std::lock_guard lock(message_mutex); + if (success) { + message + << "Technology " << name + << (enable ? " enabled" : " disabled") + << " successfully.\n"; + } else { + message << "Failed to " + << (enable ? " enable" : " disable") + << " technology " << name << "\n"; + } } + + has_message = true; }); } else { @@ -218,18 +436,15 @@ auto main() -> int { } } } - } else if (cmd == "connect" || cmd == "disconnect") { + } else if (cmd == command_map.at(Command::Connect) || + cmd == command_map.at(Command::Disconnect)) { if (arg.empty()) { std::cout << "Usage: connect/disconnect \n"; continue; } - const bool connect = (cmd == "connect"); + const bool connect = (cmd == command_map.at(Command::Connect)); const auto services = manager->services(); - auto iterator = - std::ranges::find_if(services, [&arg](const auto& service) { - return service->objPath() == arg || - service->properties().getName() == arg; - }); + auto iterator = std::ranges::find_if(services, match_service); if (iterator != services.end()) { const auto name = (*iterator)->properties().getName(); @@ -240,28 +455,23 @@ auto main() -> int { !connect) { std::cout << (connect ? "Connecting" : "Disconnecting") << " to service: " << name << "\n"; - const auto on_connect = [name, connect, &connecting, - &cin_mutex, - &cin_cv](bool success) { + const auto on_connect = [name, connect](bool success) { + std::lock_guard lock_message(message_mutex); if (success) { - std::cout + message << "Service " << name << (connect ? " connected" : " disconnected") << " successfully.\n"; } else { - std::cout << "Failed to " - << (connect ? " connect" : " disconnect") - << " to service " << name << "\n"; - } - { - const std::unique_lock lock(cin_mutex); - connecting = false; + message << "Failed to " + << (connect ? " connect" : " disconnect") + << " to service " << name << "\n"; } - cin_cv.notify_one(); + has_message = true; }; if (connect) { (*iterator)->connect(on_connect); - connecting = true; + } else { (*iterator)->disconnect(on_connect); } @@ -273,39 +483,38 @@ auto main() -> int { } else { std::cout << "Service " << arg << " not available.\n"; } - } else if (cmd == "remove") { + } else if (cmd == command_map.at(Command::Remove)) { if (arg.empty()) { std::cout << "Usage: remove \n"; continue; } const auto services = manager->services(); - auto iterator = - std::ranges::find_if(services, [&arg](const auto& service) { - return service->objPath() == arg || - service->properties().getName() == arg; - }); + auto iterator = std::ranges::find_if(services, match_service); if (iterator != services.end()) { const auto name = (*iterator)->properties().getName(); std::cout << "Removing service: " << name << "\n"; (*iterator)->remove([name](bool success) { - if (success) { - std::cout << "Service " << name - << " removed successfully.\n"; - } else { - std::cout << "Failed to remove service " << name - << "\n"; + { + std::lock_guard lock(message_mutex); + if (success) { + message << "Service " << name + << " removed successfully.\n"; + } else { + message << "Failed to remove service " << name + << "\n"; + } } + has_message = true; }); } else { std::cout << "Service " << arg << " not available.\n"; } } else { if (!cmd.empty()) { - std::cout - << "Unknown command: " << cmd - << ". Available commands: technologies, services, " - "scan, enable, disable, connect, disconnect, remove.\n"; + std::cout << "Unknown command: " << cmd + << ".\n Available commands: "; + printContainer(commands_container); } } } From a277db5604bfbcc8f180a75aa5bf605fab1c3b49 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Tue, 12 May 2026 13:52:04 +0200 Subject: [PATCH 5/9] conf-build-test.yml: Install readline dependency Signed-off-by: Eduardo Gonzalez --- .github/workflows/conf-build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conf-build-test.yml b/.github/workflows/conf-build-test.yml index f42376c..fad795b 100644 --- a/.github/workflows/conf-build-test.yml +++ b/.github/workflows/conf-build-test.yml @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake ninja-build doxygen clang-tidy python3-pip libglib2.0-dev + sudo apt-get install -y cmake ninja-build doxygen clang-tidy python3-pip libglib2.0-dev libreadline-dev - name: Check if formatted_code branch exists if: ${{ github.event_name == 'pull_request' && startsWith(github.base_ref, 'main') && github.head_ref == 'develop' }} From 3510b77fc59cc652809c89109e55584e2abe8dd6 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Tue, 12 May 2026 16:30:04 +0200 Subject: [PATCH 6/9] connman:technology: Add tethering methods Add tethering-properties setting methods for the technologies. Signed-off-by: Eduardo Gonzalez --- include/amarula/dbus/connman/gtechnology.hpp | 15 +++++++ src/dbus/gconnman_technology.cpp | 43 +++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/include/amarula/dbus/connman/gtechnology.hpp b/include/amarula/dbus/connman/gtechnology.hpp index 53010a2..47cde71 100644 --- a/include/amarula/dbus/connman/gtechnology.hpp +++ b/include/amarula/dbus/connman/gtechnology.hpp @@ -29,6 +29,12 @@ struct TechProperties { [[nodiscard]] auto isConnected() const { return connected_; } [[nodiscard]] auto isTethering() const { return tethering_; } [[nodiscard]] auto getTetheringFreq() const { return tethering_freq_; } + [[nodiscard]] auto getTetheringIdentifier() const { + return tethering_identifier_; + } + [[nodiscard]] auto getTetheringPassphrase() const { + return tethering_passphrase_; + } friend auto operator<<(std::ostream& ostr, const TechProperties& object) -> std::ostream&; @@ -37,6 +43,8 @@ struct TechProperties { bool connected_ = false; bool tethering_ = false; std::string name_; + std::string tethering_identifier_; + std::string tethering_passphrase_; Type type_; int tethering_freq_{0}; @@ -54,6 +62,13 @@ class Technology : public DBusProxy { public: using Properties = TechProperties; void setPowered(bool powered, PropertiesSetCallback callback = nullptr); + void setTethering(bool tethering, PropertiesSetCallback callback = nullptr); + void setTetheringIdentifier(const std::string& identifier, + PropertiesSetCallback callback = nullptr); + void setTetheringPassphrase(const std::string& passphrase, + PropertiesSetCallback callback = nullptr); + void setTetheringFreq(int frequency, + PropertiesSetCallback callback = nullptr); void scan(PropertiesSetCallback callback = nullptr); friend class Manager; diff --git a/src/dbus/gconnman_technology.cpp b/src/dbus/gconnman_technology.cpp index 15359ef..ccb79be 100644 --- a/src/dbus/gconnman_technology.cpp +++ b/src/dbus/gconnman_technology.cpp @@ -34,6 +34,36 @@ void Technology::setPowered(bool powered, PropertiesSetCallback callback) { &Technology::finishAsyncCall, data.release()); } +void Technology::setTethering(bool tethering, PropertiesSetCallback callback) { + auto data = prepareCallback(std::move(callback)); + setProperty(TETHERING_STR, + g_variant_new_boolean(static_cast(tethering)), + nullptr, &Technology::finishAsyncCall, data.release()); +} + +void Technology::setTetheringIdentifier(const std::string& identifier, + PropertiesSetCallback callback) { + auto data = prepareCallback(std::move(callback)); + setProperty(TETHERINGIDENTIFIER_STR, + g_variant_new_string(identifier.c_str()), nullptr, + &Technology::finishAsyncCall, data.release()); +} + +void Technology::setTetheringPassphrase(const std::string& passphrase, + PropertiesSetCallback callback) { + auto data = prepareCallback(std::move(callback)); + setProperty(TETHERINGPASSPHRASE_STR, + g_variant_new_string(passphrase.c_str()), nullptr, + &Technology::finishAsyncCall, data.release()); +} + +void Technology::setTetheringFreq(const int frequency, + PropertiesSetCallback callback) { + auto data = prepareCallback(std::move(callback)); + setProperty(TETHERINGFREQ_STR, g_variant_new_int32(frequency), nullptr, + &Technology::finishAsyncCall, data.release()); +} + void Technology::scan(PropertiesSetCallback callback) { auto data = prepareCallback(std::move(callback)); callMethod(nullptr, SCAN_STR, nullptr, &Technology::finishAsyncCall, @@ -45,7 +75,6 @@ void TechProperties::update(const gchar* key, GVariant* value) { name_ = g_variant_get_string(value, nullptr); } else if (g_strcmp0(key, TYPE_STR) == 0U) { type_ = TYPE_MAP.fromString(g_variant_get_string(value, nullptr)); - } else if (g_strcmp0(key, POWERED_STR) == 0U) { powered_ = g_variant_get_boolean(value) == 1U; } else if (g_strcmp0(key, CONNECTED_STR) == 0U) { @@ -54,6 +83,10 @@ void TechProperties::update(const gchar* key, GVariant* value) { tethering_ = g_variant_get_boolean(value) == 1U; } else if (g_strcmp0(key, TETHERINGFREQ_STR) == 0U) { tethering_freq_ = g_variant_get_int32(value); + } else if (g_strcmp0(key, TETHERINGIDENTIFIER_STR) == 0U) { + tethering_identifier_ = g_variant_get_string(value, nullptr); + } else if (g_strcmp0(key, TETHERINGPASSPHRASE_STR) == 0U) { + tethering_passphrase_ = g_variant_get_string(value, nullptr); } else { LCM_LOG("Unknown property for Technology: " << key << '\n'); } @@ -65,7 +98,13 @@ auto operator<<(std::ostream& ost, const TechProperties& obj) -> std::ostream& { ost << POWERED_STR << ": " << std::boolalpha << obj.powered_ << '\n'; ost << CONNECTED_STR << ": " << std::boolalpha << obj.connected_ << '\n'; ost << TETHERING_STR << ": " << std::boolalpha << obj.tethering_ << '\n'; - ost << TETHERINGFREQ_STR << ": " << obj.tethering_freq_ << " \n"; + if (obj.type_ == TechProperties::Type::Wifi) { + ost << TETHERINGIDENTIFIER_STR << ": " << obj.tethering_identifier_ + << '\n'; + ost << TETHERINGPASSPHRASE_STR << ": " << obj.tethering_passphrase_ + << '\n'; + ost << TETHERINGFREQ_STR << ": " << obj.tethering_freq_ << " \n"; + } return ost; } From 7d708b5562b3485774123c7dd6ecc9568b9e227c Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Tue, 12 May 2026 17:11:43 +0200 Subject: [PATCH 7/9] connmanctl.cpp: Stop using loop for technologies type match Signed-off-by: Eduardo Gonzalez --- examples/connmanctl.cpp | 90 +++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/examples/connmanctl.cpp b/examples/connmanctl.cpp index 6fc2050..ee8b6df 100644 --- a/examples/connmanctl.cpp +++ b/examples/connmanctl.cpp @@ -264,6 +264,10 @@ auto main() -> int { std::string cmd; std::string arg; iss >> cmd >> arg; + auto match_technology = [&arg](const auto& technology) { + const auto props = technology->properties(); + return tech_map.at(props.getType()) == arg; + }; auto match_service = [&arg](const auto& service) { const auto props = service->properties(); const auto name = props.getName(); @@ -361,26 +365,24 @@ auto main() -> int { } } const auto techs = manager->technologies(); - for (const auto& tech : techs) { - const auto props = tech->properties(); + auto iterator = std::ranges::find_if(techs, match_technology); + if (iterator != techs.end()) { + const auto props = (*iterator)->properties(); const auto name = props.getName(); - if (tech_map.at(props.getType()) == arg) { - std::cout << "Scanning " << name << "...\n"; - tech->scan([name](bool success) { - { - std::lock_guard lock(message_mutex); - if (success) { - message << "Technology " << name - << " scanned successfully.\n"; - } else { - message << "Failed to scan technology " << name - << ".\n"; - } + std::cout << "Scanning " << name << "...\n"; + (*iterator)->scan([name](bool success) { + { + std::lock_guard lock(message_mutex); + if (success) { + message << "Technology " << name + << " scanned successfully.\n"; + } else { + message << "Failed to scan technology " << name + << ".\n"; } - has_message = true; - }); - break; - } + } + has_message = true; + }); } } else if (cmd == command_map.at(Command::Enable) || cmd == command_map.at(Command::Disable)) { @@ -402,40 +404,40 @@ auto main() -> int { std::cout << (enable ? "Enabling" : "Disabling") << " technology: " << arg << "\n"; const auto techs = manager->technologies(); - for (const auto& tech : techs) { - const auto props = tech->properties(); + auto iterator = std::ranges::find_if(techs, match_technology); + if (iterator != techs.end()) { + const auto props = (*iterator)->properties(); const auto name = props.getName(); - if (tech_map.at(props.getType()) == arg) { - if ((!props.isPowered() && - cmd == enable_disable_container.at(0)) || - (props.isPowered() && - cmd == enable_disable_container.at(1))) { - std::cout << (enable ? "Enabling " : "Disabling ") - << name << "...\n"; - tech->setPowered(enable, [name, enable](bool success) { - { - std::lock_guard lock(message_mutex); - if (success) { - message - << "Technology " << name + if ((!props.isPowered() && + cmd == enable_disable_container.at(0)) || + (props.isPowered() && + cmd == enable_disable_container.at(1))) { + std::cout << (enable ? "Enabling " : "Disabling ") << name + << "...\n"; + (*iterator)->setPowered(enable, [name, + enable](bool success) { + { + std::lock_guard lock(message_mutex); + if (success) { + message << "Technology " << name << (enable ? " enabled" : " disabled") << " successfully.\n"; - } else { - message << "Failed to " - << (enable ? " enable" : " disable") - << " technology " << name << "\n"; - } + } else { + message << "Failed to " + << (enable ? " enable" : " disable") + << " technology " << name << "\n"; } + } - has_message = true; - }); + has_message = true; + }); - } else { - std::cout << "Technology " << name << " is already " - << (enable ? "enabled" : "disabled") << "\n"; - } + } else { + std::cout << "Technology " << name << " is already " + << (enable ? "enabled" : "disabled") << "\n"; } } + } else if (cmd == command_map.at(Command::Connect) || cmd == command_map.at(Command::Disconnect)) { if (arg.empty()) { From 77bc0c1a4bb166ce6230fcd4ded42b8f00fae85f Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Tue, 12 May 2026 17:44:49 +0200 Subject: [PATCH 8/9] connmanctl.cpp: Add tether command Signed-off-by: Eduardo Gonzalez --- examples/connmanctl.cpp | 83 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/examples/connmanctl.cpp b/examples/connmanctl.cpp index ee8b6df..ff97d4a 100644 --- a/examples/connmanctl.cpp +++ b/examples/connmanctl.cpp @@ -22,6 +22,8 @@ using Amarula::DBus::G::Connman::TechProperties; using State = Amarula::DBus::G::Connman::ServProperties::State; using TechnologyType = TechProperties::Type; +constexpr std::size_t MIN_WIFI_PASSPHRASE_LENGTH = 8; + std::atomic has_message{false}; std::ostringstream message; std::mutex message_mutex; @@ -65,7 +67,8 @@ enum class Command : std::uint8_t { Disconnect, Remove, Agent, - Quit + Quit, + Tethering }; inline const std::unordered_map command_map = { @@ -78,7 +81,8 @@ inline const std::unordered_map command_map = { {Command::Disconnect, "disconnect"}, {Command::Remove, "remove"}, {Command::Agent, "agent"}, - {Command::Quit, "quit"}}; + {Command::Quit, "quit"}, + {Command::Tethering, "tether"}}; inline const std::unordered_map tech_map = { {TechnologyType::Ethernet, "ethernet"}, @@ -170,7 +174,8 @@ auto completion(const char* text, int start, int end) -> char** { completion_mode = Mode::OnOff; } else if (command == command_map.at(Command::Enable) || command == command_map.at(Command::Disable) || - command == command_map.at(Command::Scan)) { + command == command_map.at(Command::Scan) || + command == command_map.at(Command::Tethering)) { completion_mode = Mode::TechHash; } else if (command == command_map.at(Command::Connect) || command == command_map.at(Command::Disconnect) || @@ -438,6 +443,78 @@ auto main() -> int { } } + } else if (cmd == command_map.at(Command::Tethering)) { + std::string arg1; + std::string arg2; + iss >> arg1 >> arg2; + { + std::lock_guard lock(hash_mutex); + if (arg.empty() || + std::find(technologies_container.begin(), + technologies_container.end(), + arg) == technologies_container.end() || + arg1.empty() || + (arg1 != on_off_container[0] && + arg1 != on_off_container[1]) || + (arg == tech_map.at(TechnologyType::Wifi) && + arg1 == on_off_container[0] && arg2.empty())) { + std::cout << "Usage: tether " + << on_off_container[0] << "/" + << on_off_container[1] + << " \n"; + std::cout << "Available technologies:"; + printContainer(technologies_container); + continue; + } + } + const auto techs = manager->technologies(); + auto iterator = std::ranges::find_if(techs, match_technology); + if (iterator != techs.end()) { + if (arg == tech_map.at(TechnologyType::Wifi)) { + std::string arg3; + std::string arg4; + iss >> arg3 >> arg4; + (*iterator)->setTetheringIdentifier(arg2); + + if (!arg3.empty()) { + if (arg3.size() >= MIN_WIFI_PASSPHRASE_LENGTH) { + (*iterator)->setTetheringPassphrase(arg3); + } else { + std::cout << "Error setting wifi passphrase\n" + << "Passphrase must be at least " + << MIN_WIFI_PASSPHRASE_LENGTH + << " characters long\n"; + continue; + } + } + + if (!arg4.empty() && + std::all_of(arg4.begin(), arg4.end(), ::isdigit)) { + (*iterator)->setTetheringFreq(std::stoi(arg4)); + } + } + + const auto enable = arg1 == on_off_container[0]; + std::cout << (enable ? "Enabling" : "Disabling") + << " tethering on " << arg << "\n"; + (*iterator)->setTethering(enable, [arg, enable](bool success) { + { + std::lock_guard lock(message_mutex); + if (success) { + message << "Tethering on technology " << arg + << (enable ? " enabled" : " disabled") + << " successfully.\n"; + } else { + message << "Failed to" + << (enable ? " enable" : " disable") + << " tethering on " + "technology " + << arg << "\n"; + } + } + has_message = true; + }); + } } else if (cmd == command_map.at(Command::Connect) || cmd == command_map.at(Command::Disconnect)) { if (arg.empty()) { From 29e8b6c6993f9b4c8150a50b54cf8dd27bde2096 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Thu, 14 May 2026 09:50:02 +0200 Subject: [PATCH 9/9] gconnman_tech_test.cpp: Add tethering tests Signed-off-by: Eduardo Gonzalez --- tests/gconnman_tech_test.cpp | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tests/gconnman_tech_test.cpp b/tests/gconnman_tech_test.cpp index 419e486..d09989b 100644 --- a/tests/gconnman_tech_test.cpp +++ b/tests/gconnman_tech_test.cpp @@ -8,6 +8,8 @@ using Amarula::DBus::G::Connman::Connman; using Type = Amarula::DBus::G::Connman::TechProperties::Type; +constexpr uint32_t WIFI_FREQ_2412_MHZ = 2412; + TEST(Connman, getTechs) { bool called = false; { @@ -125,6 +127,113 @@ TEST(Connman, ScanWifiTechnology) { ASSERT_TRUE(called) << "TechnologiesChanged callback was never called"; } +TEST(Connman, SetTetheringOn) { + bool called = false; + { + const ThreadBundle thread_bundle; + Connman connman; + const auto manager = connman.manager(); + + manager->onTechnologiesChanged( + [&called, main_tid = thread_bundle.main_tid, + loop_tid = thread_bundle.loop_tid](const auto& technologies) { + ASSERT_FALSE(technologies.empty()) + << "No technologies returned"; + + for (const auto& tech : technologies) { + const auto props = tech->properties(); + const auto name = props.getName(); + + if (props.getType() == Type::Wifi) { // test only wifi + std::cout << "Setting tethering properties for " << name + << "\n"; + tech->setTetheringIdentifier( + "AmarulaTestSSID", + [name, main_tid, loop_tid](bool success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Failed to set tethering identifier for " + << name; + }); + tech->setTetheringPassphrase( + "AmarulaTestPassphrase", + [name, main_tid, loop_tid](bool success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Failed to set tethering passphrase for " + << name; + }); + + tech->setTetheringFreq( + WIFI_FREQ_2412_MHZ, + [name, main_tid, loop_tid](bool success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Failed to set tethering frequency for " + << name; + }); + tech->setTethering(true, [&called, name, main_tid, + loop_tid](bool success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Failed to set tethering for " << name; + called = true; + }); + } + } + }); + } + ASSERT_TRUE(called) << "setTethering callback was never called"; +} + +TEST(Connman, SetTetheringOff) { + bool called = false; + { + const ThreadBundle thread_bundle; + Connman connman; + const auto manager = connman.manager(); + + manager->onTechnologiesChanged( + [&called, main_tid = thread_bundle.main_tid, + loop_tid = thread_bundle.loop_tid](const auto& technologies) { + ASSERT_FALSE(technologies.empty()) + << "No technologies returned"; + + for (const auto& tech : technologies) { + const auto props = tech->properties(); + const auto name = props.getName(); + + if (props.getType() == Type::Wifi) { // test only wifi + std::cout << "Disable tethering for " << name << "\n"; + tech->setTethering(false, [&called, name, main_tid, + loop_tid](bool success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Failed to unset tethering for " << name; + called = true; + }); + } + } + }); + } + ASSERT_TRUE(called) << "setTethering callback was never called"; +} + TEST(Connman, PowerOffAllTechnologies) { bool called = false; {