diff --git a/CMakeLists.txt b/CMakeLists.txt index 29cdc42..1f4963e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ message(STATUS "libudev ................ found (version ${UDEV_VERSION})") add_subdirectory(src/avahi) add_subdirectory(src/bluez) +add_subdirectory(src/connman) add_subdirectory(src/fwupd) add_subdirectory(src/geoclue2) add_subdirectory(src/hostname1) diff --git a/src/connman/CMakeLists.txt b/src/connman/CMakeLists.txt new file mode 100644 index 0000000..b6127b0 --- /dev/null +++ b/src/connman/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(connman_client + main.cc + connman_client.cc + connman_client.h +) + +target_link_libraries(connman_client + PUBLIC + utils + sdbus-c++ + spdlog::spdlog +) + +if (ENABLE_LTO AND IPO_SUPPORT_RESULT) + set_property(TARGET connman_client PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif () + +install(TARGETS connman_client + RUNTIME DESTINATION share/sdbus-cpp-examples) \ No newline at end of file diff --git a/src/connman/connman_client.cc b/src/connman/connman_client.cc new file mode 100644 index 0000000..f8830f6 --- /dev/null +++ b/src/connman/connman_client.cc @@ -0,0 +1,203 @@ +// Copyright (c) 2026 Jian De +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "connman_client.h" + +#include + +#include "../utils/logging.h" +#include "../utils/utils.h" + +// +// ConnmanManagerClient +// + +ConnmanManagerClient::ConnmanManagerClient(sdbus::IConnection& connection) + : ProxyInterfaces(connection, + sdbus::ServiceName{SERVICE_NAME}, + sdbus::ObjectPath{OBJECT_PATH}) { + registerProxy(); + + try { + // Load initial technologies + for (const auto& tech : GetTechnologies()) { + onTechnologyAdded(tech.get<0>(), tech.get<1>()); + } + + // Load initial services + for (const auto& service : GetServices()) { + const auto& path = service.get<0>(); + if (services_.find(path) == services_.end()) { + services_[path] = std::make_unique( + getProxy().getConnection(), path); + } + } + } catch (...) { + unregisterProxy(); + throw; + } +} + +ConnmanManagerClient::~ConnmanManagerClient() { + unregisterProxy(); +} + +void ConnmanManagerClient::onPropertyChanged(const std::string& name, + const sdbus::Variant& value) { + try { + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Manager PropertyChanged: {} = {}", name, os.str()); + } catch (const std::exception& e) { + LOG_ERROR("Exception in Manager onPropertyChanged: {}", e.what()); + } +} + +void ConnmanManagerClient::onTechnologyAdded( + const sdbus::ObjectPath& path, + const std::map& properties) { + (void)properties; + try { + LOG_INFO("Technology added: {}", path); + if (technologies_.find(path) == technologies_.end()) { + technologies_[path] = std::make_unique( + getProxy().getConnection(), path); + } + } catch (const std::exception& e) { + LOG_ERROR("Exception in onTechnologyAdded: {}", e.what()); + } +} + +void ConnmanManagerClient::onTechnologyRemoved(const sdbus::ObjectPath& path) { + try { + LOG_INFO("Technology removed: {}", path); + technologies_.erase(path); + } catch (const std::exception& e) { + LOG_ERROR("Exception in onTechnologyRemoved: {}", e.what()); + } +} + +void ConnmanManagerClient::onServicesChanged( + const std::vector>>& + changed, + const std::vector& removed) { + try { + for (const auto& service : changed) { + const auto& path = service.get<0>(); + const auto& props = service.get<1>(); + + if (services_.find(path) == services_.end()) { + LOG_INFO("Service added: {}", path); + services_[path] = std::make_unique( + getProxy().getConnection(), path); + } + + // Log WiFi info only when Type is explicitly "wifi" in this delta + bool is_wifi = false; + if (auto it = props.find("Type"); + it != props.end() && it->second.containsValueOfType()) { + is_wifi = (it->second.get() == "wifi"); + } + + if (is_wifi) { + std::string ssid = "Unknown"; + if (auto it = props.find("Name"); + it != props.end() && + it->second.containsValueOfType()) { + ssid = it->second.get(); + } + LOG_INFO(" WiFi Update: {} [{}]", ssid, path); + } + } + + for (const auto& path : removed) { + LOG_INFO(" Service removed: {}", path); + services_.erase(path); + } + } catch (const std::exception& e) { + LOG_ERROR("Exception in onServicesChanged: {}", e.what()); + } +} + +void ConnmanManagerClient::onPeersChanged( + const std::vector>>&, + const std::vector&) {} + +void ConnmanManagerClient::onTetheringClientsChanged( + const std::vector&, + const std::vector&) {} + +// +// ConnmanTechnologyClient +// + +ConnmanTechnologyClient::ConnmanTechnologyClient(sdbus::IConnection& connection, + sdbus::ObjectPath path) + : ProxyInterfaces(connection, + sdbus::ServiceName{SERVICE_NAME}, + std::move(path)) { + registerProxy(); +} + +ConnmanTechnologyClient::~ConnmanTechnologyClient() { + unregisterProxy(); +} + +void ConnmanTechnologyClient::onPropertyChanged(const std::string& name, + const sdbus::Variant& value) { + try { + if (name == "Scanning" && value.containsValueOfType()) { + bool scanning = value.get(); + LOG_INFO("Technology [{}] WiFi Scan: {}", getProxy().getObjectPath(), + scanning ? "STARTED" : "COMPLETED"); + } + + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Technology [{}] PropertyChanged: {} = {}", + getProxy().getObjectPath(), name, os.str()); + } catch (const std::exception& e) { + LOG_ERROR("Exception in Technology onPropertyChanged: {}", e.what()); + } +} + +// +// ConnmanServiceClient +// + +ConnmanServiceClient::ConnmanServiceClient(sdbus::IConnection& connection, + sdbus::ObjectPath path) + : ProxyInterfaces(connection, + sdbus::ServiceName{SERVICE_NAME}, + std::move(path)) { + registerProxy(); +} + +ConnmanServiceClient::~ConnmanServiceClient() { + unregisterProxy(); +} + +void ConnmanServiceClient::onPropertyChanged(const std::string& name, + const sdbus::Variant& value) { + try { + std::ostringstream os; + Utils::append_property(value, os); + LOG_INFO("Service [{}] PropertyChanged: {} = {}", + getProxy().getObjectPath(), name, os.str()); + } catch (const std::exception& e) { + LOG_ERROR("Exception in Service onPropertyChanged: {}", e.what()); + } +} diff --git a/src/connman/connman_client.h b/src/connman/connman_client.h new file mode 100644 index 0000000..2b39d5f --- /dev/null +++ b/src/connman/connman_client.h @@ -0,0 +1,95 @@ +// Copyright (c) 2026 Jian De +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_CONNMAN_CONNMAN_CLIENT_H +#define SRC_CONNMAN_CONNMAN_CLIENT_H + +#include +#include +#include +#include + +#include "../proxy/net/connman/Manager/manager_proxy.h" +#include "../proxy/net/connman/Service/service_proxy.h" +#include "../proxy/net/connman/Technology/technology_proxy.h" + +class ConnmanTechnologyClient; +class ConnmanServiceClient; + +class ConnmanManagerClient final + : public sdbus::ProxyInterfaces { + public: + static constexpr auto SERVICE_NAME = "net.connman"; + static constexpr auto OBJECT_PATH = "/"; + + explicit ConnmanManagerClient(sdbus::IConnection& connection); + ~ConnmanManagerClient(); + + void onPropertyChanged(const std::string& name, + const sdbus::Variant& value) override; + + void onTechnologyAdded( + const sdbus::ObjectPath& path, + const std::map& properties) override; + + void onTechnologyRemoved(const sdbus::ObjectPath& path) override; + + void onServicesChanged( + const std::vector>>& + changed, + const std::vector& removed) override; + + void onPeersChanged( + const std::vector>>& + changed, + const std::vector& removed) override; + + void onTetheringClientsChanged( + const std::vector& registered, + const std::vector& removed) override; + + private: + std::map> + technologies_; + std::map> services_; +}; + +class ConnmanTechnologyClient final + : public sdbus::ProxyInterfaces { + public: + static constexpr auto SERVICE_NAME = "net.connman"; + + ConnmanTechnologyClient(sdbus::IConnection& connection, + sdbus::ObjectPath path); + ~ConnmanTechnologyClient(); + + void onPropertyChanged(const std::string& name, + const sdbus::Variant& value) override; +}; + +class ConnmanServiceClient final + : public sdbus::ProxyInterfaces { + public: + static constexpr auto SERVICE_NAME = "net.connman"; + + ConnmanServiceClient(sdbus::IConnection& connection, sdbus::ObjectPath path); + ~ConnmanServiceClient(); + + void onPropertyChanged(const std::string& name, + const sdbus::Variant& value) override; +}; + +#endif // SRC_CONNMAN_CONNMAN_CLIENT_H \ No newline at end of file diff --git a/src/connman/main.cc b/src/connman/main.cc new file mode 100644 index 0000000..86238c4 --- /dev/null +++ b/src/connman/main.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2026 Jian De +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../utils/signal_handler.h" +#include "connman_client.h" + +inline constexpr auto kInitialReconnectDelay = std::chrono::seconds(1); +inline constexpr auto kMaxReconnectDelay = std::chrono::seconds(30); + +int main() { + logging_config::initializeLogging("connman_client"); + installSignalHandlers(); + + auto reconnect_delay = kInitialReconnectDelay; + + while (g_running) { + try { + auto connection = sdbus::createSystemBusConnection(); + connection->enterEventLoopAsync(); + + { + ConnmanManagerClient client(*connection); + + LOG_INFO("ConnMan monitor running - Press Ctrl+C to exit"); + reconnect_delay = kInitialReconnectDelay; + + auto result = monitorLoop(*connection); + + if (result) { + LOG_ERROR("Monitor loop: {}", *result); + } else { + LOG_INFO("Shutting down..."); + } + } // client destroyed before leaving event loop + + connection->leaveEventLoop(); + + if (!g_running) { + return 0; + } + + } catch (const sdbus::Error& e) { + LOG_ERROR("D-Bus error: {} - {}", e.getName(), e.getMessage()); + } catch (const std::exception& e) { + LOG_ERROR("Exception: {}", e.what()); + } + + if (!g_running) { + break; + } + + LOG_INFO("Reconnecting in {} seconds...", + std::chrono::duration_cast(reconnect_delay) + .count()); + std::this_thread::sleep_for(reconnect_delay); + reconnect_delay = std::min( + reconnect_delay * 2, + std::chrono::duration_cast(kMaxReconnectDelay)); + } + + return 1; +} \ No newline at end of file