diff --git a/examples/simplebluez/CMakeLists.txt b/examples/simplebluez/CMakeLists.txt index 85c55ca0..0571ad3c 100644 --- a/examples/simplebluez/CMakeLists.txt +++ b/examples/simplebluez/CMakeLists.txt @@ -23,3 +23,4 @@ add_subdirectory(pair) add_subdirectory(read) add_subdirectory(notify) add_subdirectory(ble_nus) +add_subdirectory(advertise) \ No newline at end of file diff --git a/examples/simplebluez/advertise/CMakeLists.txt b/examples/simplebluez/advertise/CMakeLists.txt new file mode 100644 index 00000000..e2e3d37b --- /dev/null +++ b/examples/simplebluez/advertise/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.21) + +project(EXAMPLE_ADVERTISE) + +message("-- [INFO] Building Example") +add_executable(example_advertise advertise.cpp) +target_link_libraries(example_advertise simplebluez::simplebluez pthread) diff --git a/examples/simplebluez/advertise/advertise.cpp b/examples/simplebluez/advertise/advertise.cpp new file mode 100644 index 00000000..cdc413da --- /dev/null +++ b/examples/simplebluez/advertise/advertise.cpp @@ -0,0 +1,63 @@ +#include + +#include +#include +#include +#include +#include +#include + +SimpleBluez::Bluez bluez; + +std::atomic_bool async_thread_active = true; +void async_thread_function() { + while (async_thread_active) { + bluez.run_async(); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } +} + +void millisecond_delay(int ms) { + for (int i = 0; i < ms; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +int main(int argc, char* argv[]) { + int selection = -1; + + bluez.init(); + std::thread* async_thread = new std::thread(async_thread_function); + + auto adapters = bluez.get_adapters(); + std::cout << "The following adapters were found:" << std::endl; + for (int i = 0; i < adapters.size(); i++) { + std::cout << "[" << i << "] " << adapters[i]->identifier() << " [" << adapters[i]->address() << "]" + << std::endl; + } + + // std::cout << "Please select an adapter to advertise: "; + // std::cin >> selection; + // if (selection < 0 || selection >= adapters.size()) { + // std::cout << "Invalid selection" << std::endl; + // return 1; + // } + + auto adapter = adapters[0]; + std::cout << "Advertising on " << adapter->identifier() << " [" << adapter->address() << "]" << std::endl; + + auto advertisement = bluez.make_le_advertisement("/potato"); + adapter->register_le_advertisement(advertisement); + + // Sleep for a bit to allow the adapter to stop discovering. + millisecond_delay(3000); + + async_thread_active = false; + while (!async_thread->joinable()) { + millisecond_delay(10); + } + async_thread->join(); + delete async_thread; + + return 0; +} diff --git a/simplebluez/CMakeLists.txt b/simplebluez/CMakeLists.txt index 53cd80d8..45599160 100644 --- a/simplebluez/CMakeLists.txt +++ b/simplebluez/CMakeLists.txt @@ -55,14 +55,17 @@ set(SIMPLEBLUEZ_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/Adapter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bluez.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Descriptor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/LEAdvertisement.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/Adapter1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/Agent1.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/GattDescriptor1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/AgentManager1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/Battery1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/Device1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/GattCharacteristic1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/GattDescriptor1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/GattService1.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/Device1.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/Battery1.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/AgentManager1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/LEAdvertisement1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/interfaces/LEAdvertisingManager1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../simpledbus/src/advanced/Interface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../simpledbus/src/advanced/Proxy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../simpledbus/src/base/Connection.cpp diff --git a/simplebluez/include/simplebluez/Adapter.h b/simplebluez/include/simplebluez/Adapter.h index 4bfee278..c91776eb 100644 --- a/simplebluez/include/simplebluez/Adapter.h +++ b/simplebluez/include/simplebluez/Adapter.h @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include @@ -33,11 +35,14 @@ class Adapter : public SimpleDBus::Proxy { void set_on_device_updated(std::function device)> callback); void clear_on_device_updated(); + void register_le_advertisement(std::shared_ptr advertisement); + private: std::shared_ptr path_create(const std::string& path) override; std::shared_ptr interfaces_create(const std::string& interface_name) override; std::shared_ptr adapter1(); + std::shared_ptr le_advertising_manager1(); }; } // namespace SimpleBluez diff --git a/simplebluez/include/simplebluez/Bluez.h b/simplebluez/include/simplebluez/Bluez.h index 2267dca1..b5fb8390 100644 --- a/simplebluez/include/simplebluez/Bluez.h +++ b/simplebluez/include/simplebluez/Bluez.h @@ -22,6 +22,8 @@ class Bluez : public SimpleDBus::Proxy { std::shared_ptr get_agent(); void register_agent(); + std::shared_ptr make_le_advertisement(const std::string& path); + private: std::shared_ptr path_create(const std::string& path) override; diff --git a/simplebluez/include/simplebluez/Characteristic.h b/simplebluez/include/simplebluez/Characteristic.h index 00e568e9..dcdc4427 100644 --- a/simplebluez/include/simplebluez/Characteristic.h +++ b/simplebluez/include/simplebluez/Characteristic.h @@ -38,8 +38,8 @@ class Characteristic : public SimpleDBus::Proxy { void clear_on_value_changed(); private: - std::shared_ptr path_create(const std::string& path) override; - std::shared_ptr interfaces_create(const std::string& interface_name) override; + std::shared_ptr path_create(const std::string& path) override; + std::shared_ptr interfaces_create(const std::string& interface_name) override; std::shared_ptr gattcharacteristic1(); }; diff --git a/simplebluez/include/simplebluez/Descriptor.h b/simplebluez/include/simplebluez/Descriptor.h index 8859bce6..3e38cfbf 100644 --- a/simplebluez/include/simplebluez/Descriptor.h +++ b/simplebluez/include/simplebluez/Descriptor.h @@ -27,7 +27,7 @@ class Descriptor : public SimpleDBus::Proxy { void clear_on_value_changed(); private: - std::shared_ptr interfaces_create(const std::string& interface_name) override; + std::shared_ptr interfaces_create(const std::string& interface_name) override; std::shared_ptr gattdescriptor1(); }; diff --git a/simplebluez/include/simplebluez/LEAdvertisement.h b/simplebluez/include/simplebluez/LEAdvertisement.h new file mode 100644 index 00000000..0ecadc19 --- /dev/null +++ b/simplebluez/include/simplebluez/LEAdvertisement.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace SimpleBluez { + +class LEAdvertisement : public SimpleDBus::Proxy { + public: + typedef enum { + DisplayOnly, + DisplayYesNo, + KeyboardOnly, + NoInputNoOutput, + KeyboardDisplay, + } Capabilities; + + LEAdvertisement(std::shared_ptr conn, const std::string& bus_name, const std::string& path); + virtual ~LEAdvertisement() = default; + + private: + std::shared_ptr interfaces_create(const std::string& interface_name) override; + + std::shared_ptr le_advertisement1(); +}; + +} // namespace SimpleBluez diff --git a/simplebluez/include/simplebluez/interfaces/LEAdvertisement1.h b/simplebluez/include/simplebluez/interfaces/LEAdvertisement1.h new file mode 100644 index 00000000..bdc26249 --- /dev/null +++ b/simplebluez/include/simplebluez/interfaces/LEAdvertisement1.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include +#include +#include + +namespace SimpleBluez { + +class LEAdvertisement1 : public SimpleDBus::Interface { + public: + // ----- TYPES ----- + + + // ----- CONSTRUCTORS ----- + LEAdvertisement1(std::shared_ptr conn, std::string path); + virtual ~LEAdvertisement1() = default; + + // ----- PROPERTIES ----- + void SetType(const std::string& type); + void SetServiceUUIDs(const std::vector& uuids); + void SetManufacturerData(const std::map>& data); + void SetServiceData(const std::map>& data); + void SetIncludeTxPower(bool include); + + // ----- METHODS ----- + void Release(); + + protected: + void property_changed(std::string option_name) override; + void message_handle(SimpleDBus::Message& msg) override; +}; + +} // namespace SimpleBluez \ No newline at end of file diff --git a/simplebluez/include/simplebluez/interfaces/LEAdvertisingManager1.h b/simplebluez/include/simplebluez/interfaces/LEAdvertisingManager1.h new file mode 100644 index 00000000..ce11165f --- /dev/null +++ b/simplebluez/include/simplebluez/interfaces/LEAdvertisingManager1.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include +#include + +namespace SimpleBluez { + +class LEAdvertisingManager1 : public SimpleDBus::Interface { + public: + // ----- TYPES ----- + + + // ----- CONSTRUCTORS ----- + LEAdvertisingManager1(std::shared_ptr conn, std::string path); + virtual ~LEAdvertisingManager1() = default; + + // ----- METHODS ----- + void RegisterAdvertisement(std::string advertisement_path); + void UnregisterAdvertisement(std::string advertisement_path); + + + // ----- PROPERTIES ----- + uint8_t ActiveInstances(bool refresh = true); + uint8_t SupportedInstances(bool refresh = true); + std::vector SupportedIncludes(bool refresh = true); + + protected: + void property_changed(std::string option_name) override; +}; + +} // namespace SimpleBluez \ No newline at end of file diff --git a/simplebluez/src/Adapter.cpp b/simplebluez/src/Adapter.cpp index 558b9b66..1b319973 100644 --- a/simplebluez/src/Adapter.cpp +++ b/simplebluez/src/Adapter.cpp @@ -2,6 +2,7 @@ #include #include +#include using namespace SimpleBluez; @@ -18,6 +19,8 @@ std::shared_ptr Adapter::path_create(const std::string& path) std::shared_ptr Adapter::interfaces_create(const std::string& interface_name) { if (interface_name == "org.bluez.Adapter1") { return std::static_pointer_cast(std::make_shared(_conn, _path)); + } else if (interface_name == "org.bluez.LEAdvertisingManager1") { + return std::static_pointer_cast(std::make_shared(_conn, _path)); } auto interface = std::make_shared(_conn, _bus_name, _path, interface_name); @@ -28,6 +31,10 @@ std::shared_ptr Adapter::adapter1() { return std::dynamic_pointer_cast(interface_get("org.bluez.Adapter1")); } +std::shared_ptr Adapter::le_advertising_manager1() { + return std::dynamic_pointer_cast(interface_get("org.bluez.LEAdvertisingManager1")); +} + std::string Adapter::identifier() const { std::size_t start = _path.find_last_of("/"); return _path.substr(start + 1); @@ -85,3 +92,7 @@ void Adapter::clear_on_device_updated() { on_child_created.unload(); on_child_signal_received.unload(); } + +void Adapter::register_le_advertisement(std::shared_ptr advertisement) { + le_advertising_manager1()->RegisterAdvertisement(advertisement->path()); +} diff --git a/simplebluez/src/Bluez.cpp b/simplebluez/src/Bluez.cpp index 8c2a5ecf..fe61f584 100644 --- a/simplebluez/src/Bluez.cpp +++ b/simplebluez/src/Bluez.cpp @@ -16,7 +16,10 @@ Bluez::Bluez() : Proxy(std::make_shared(DBUS_BUS), "org. _interfaces["org.freedesktop.DBus.ObjectManager"] = std::static_pointer_cast( std::make_shared(_conn, "org.bluez", "/")); - object_manager()->InterfacesAdded = [&](std::string path, SimpleDBus::Holder options) { path_add(path, options); }; + object_manager()->InterfacesAdded = [&](std::string path, SimpleDBus::Holder options) { + std::cout << "InterfacesAdded: " << path << std::endl; + path_add(path, options); + }; object_manager()->InterfacesRemoved = [&](std::string path, SimpleDBus::Holder options) { path_remove(path, options); }; @@ -48,6 +51,7 @@ void Bluez::run_async() { _conn->read_write(); SimpleDBus::Message message = _conn->pop_message(); while (message.is_valid()) { + std::cout << "Message: " << message.to_string() << std::endl; message_forward(message); message = _conn->pop_message(); } @@ -57,6 +61,12 @@ std::vector> Bluez::get_adapters() { return std::dynamic_pointer_cast(path_get("/org"))->get_adapters(); } +std::shared_ptr Bluez::make_le_advertisement(const std::string& path) { + std::shared_ptr advertisement = std::make_shared(_conn, "org.simpleble", path); + path_append_child(path, std::static_pointer_cast(advertisement)); + return advertisement; +} + std::shared_ptr Bluez::get_agent() { return std::dynamic_pointer_cast(path_get("/agent")); } void Bluez::register_agent() { std::dynamic_pointer_cast(path_get("/org"))->register_agent(_agent); } diff --git a/simplebluez/src/Descriptor.cpp b/simplebluez/src/Descriptor.cpp index a75ab5e7..96bb56e2 100644 --- a/simplebluez/src/Descriptor.cpp +++ b/simplebluez/src/Descriptor.cpp @@ -8,13 +8,13 @@ Descriptor::Descriptor(std::shared_ptr conn, const std:: Descriptor::~Descriptor() {} -std::shared_ptr Descriptor::interfaces_create(const std::string& interface_name) { +std::shared_ptr Descriptor::interfaces_create(const std::string& interface_name) { if (interface_name == "org.bluez.GattDescriptor1") { - return std::static_pointer_cast(std::make_shared(_conn, _path)); + return std::static_pointer_cast(std::make_shared(_conn, _path)); } auto interface = std::make_shared(_conn, _bus_name, _path, interface_name); - return std::static_pointer_cast(interface); + return std::static_pointer_cast(interface); } std::shared_ptr Descriptor::gattdescriptor1() { diff --git a/simplebluez/src/LEAdvertisement.cpp b/simplebluez/src/LEAdvertisement.cpp new file mode 100644 index 00000000..903eedbe --- /dev/null +++ b/simplebluez/src/LEAdvertisement.cpp @@ -0,0 +1,18 @@ +#include + +using namespace SimpleBluez; + +LEAdvertisement::LEAdvertisement(std::shared_ptr conn, const std::string& bus_name, + const std::string& path) + : Proxy(conn, bus_name, path) { + _interfaces.emplace(std::make_pair("org.bluez.LEAdvertisement1", interfaces_create("org.bluez.LEAdvertisement1"))); +} + +std::shared_ptr LEAdvertisement::interfaces_create(const std::string& interface_name) { + if (interface_name == "org.bluez.LEAdvertisement1") { + return std::static_pointer_cast(std::make_shared(_conn, _path)); + } + + auto interface = std::make_shared(_conn, _bus_name, _path, interface_name); + return std::static_pointer_cast(interface); +} diff --git a/simplebluez/src/interfaces/LEAdvertisement1.cpp b/simplebluez/src/interfaces/LEAdvertisement1.cpp new file mode 100644 index 00000000..5c1e9300 --- /dev/null +++ b/simplebluez/src/interfaces/LEAdvertisement1.cpp @@ -0,0 +1,86 @@ +#include "simplebluez/interfaces/LEAdvertisement1.h" + +using namespace SimpleBluez; + +LEAdvertisement1::LEAdvertisement1(std::shared_ptr conn, std::string path) + : SimpleDBus::Interface(conn, "org.bluez", path, "org.bluez.LEAdvertisement1") {} + +void LEAdvertisement1::SetType(const std::string& type) { + _properties["Type"] = SimpleDBus::Holder::create_string(type); +} + +void LEAdvertisement1::SetServiceUUIDs(const std::vector& uuids) { + SimpleDBus::Holder uuids_holder = SimpleDBus::Holder::create_array(); + for (const auto& uuid : uuids) { + uuids_holder.array_append(SimpleDBus::Holder::create_string(uuid)); + } + _properties["ServiceUUIDs"] = uuids_holder; +} + +void LEAdvertisement1::SetManufacturerData(const std::map>& data) { + SimpleDBus::Holder data_holder = SimpleDBus::Holder::create_dict(); + for (const auto& [key, value] : data) { + SimpleDBus::Holder value_holder = SimpleDBus::Holder::create_array(); + for (uint8_t byte : value) { + value_holder.array_append(SimpleDBus::Holder::create_byte(byte)); + } + data_holder.dict_append(SimpleDBus::Holder::Type::UINT16, key, value_holder); + } + _properties["ManufacturerData"] = data_holder; +} + +void LEAdvertisement1::SetServiceData(const std::map>& data) { + SimpleDBus::Holder data_holder = SimpleDBus::Holder::create_dict(); + for (const auto& [key, value] : data) { + SimpleDBus::Holder value_holder = SimpleDBus::Holder::create_array(); + for (uint8_t byte : value) { + value_holder.array_append(SimpleDBus::Holder::create_byte(byte)); + } + data_holder.dict_append(SimpleDBus::Holder::Type::STRING, key, value_holder); + } + _properties["ServiceData"] = data_holder; +} + +void LEAdvertisement1::SetIncludeTxPower(bool include) { + _properties["IncludeTxPower"] = SimpleDBus::Holder::create_boolean(include); +} + +void LEAdvertisement1::Release() { + // This method is called when the advertisement is unregistered + // Implement any cleanup logic here +} + +void LEAdvertisement1::property_changed(std::string option_name) {} + +#include + +void LEAdvertisement1::message_handle(SimpleDBus::Message& msg) { + std::cout << "LEAdvertisement1::message_handle: " << msg.to_string() << std::endl; + // if (msg.get_type() == SimpleDBus::Message::Type::METHOD_CALL) { + // SimpleDBus::Message reply = SimpleDBus::Message::create_method_return(msg); + + // if (msg.get_member() == "GetAll" && msg.get_interface() == "org.freedesktop.DBus.Properties") { + // SimpleDBus::Holder properties = SimpleDBus::Holder::create_dict(); + // for (const auto& [key, value] : _properties) { + // properties.dict_append(SimpleDBus::Holder::Type::STRING, key, value); + // } + // reply.append_argument(properties, "a{sv}"); + // } else if (msg.get_member() == "Get" && msg.get_interface() == "org.freedesktop.DBus.Properties") { + // std::string interface_name = msg.extract().get_string(); + // std::string property_name = msg.extract().get_string(); + // if (_properties.find(property_name) != _properties.end()) { + // reply.append_argument(_properties[property_name], "v"); + // } else { + // // Property not found, create an error reply + // reply = SimpleDBus::Message::create_error(msg, "org.freedesktop.DBus.Error.UnknownProperty", "Unknown property: " + property_name); + // } + // } else if (msg.get_member() == "Release") { + // Release(); + // } else { + // // Unknown method, create an error reply + // reply = SimpleDBus::Message::create_error(msg, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method: " + msg.get_member()); + // } + + // _conn->send(reply); + // } +} \ No newline at end of file diff --git a/simplebluez/src/interfaces/LEAdvertisingManager1.cpp b/simplebluez/src/interfaces/LEAdvertisingManager1.cpp new file mode 100644 index 00000000..4b821471 --- /dev/null +++ b/simplebluez/src/interfaces/LEAdvertisingManager1.cpp @@ -0,0 +1,58 @@ +#include "simplebluez/interfaces/LEAdvertisingManager1.h" + +using namespace SimpleBluez; + +LEAdvertisingManager1::LEAdvertisingManager1(std::shared_ptr conn, std::string path) + : SimpleDBus::Interface(conn, "org.bluez", path, "org.bluez.LEAdvertisingManager1") {} + +void LEAdvertisingManager1::RegisterAdvertisement(std::string advertisement_path) { + SimpleDBus::Holder properties = SimpleDBus::Holder::create_dict(); + + // NOTE: The current documentation doesn't specify any options. Using a placeholder for now. + + auto msg = create_method_call("RegisterAdvertisement"); + msg.append_argument(SimpleDBus::Holder::create_object_path(advertisement_path), "o"); + msg.append_argument(properties, "a{sv}"); + _conn->send(msg); +} + +void LEAdvertisingManager1::UnregisterAdvertisement(std::string advertisement_path) { + auto msg = create_method_call("UnregisterAdvertisement"); + msg.append_argument(SimpleDBus::Holder::create_object_path(advertisement_path), "o"); + _conn->send_with_reply_and_block(msg); +} + +uint8_t LEAdvertisingManager1::ActiveInstances(bool refresh) { + if (refresh) { + property_refresh("ActiveInstances"); + } + + std::scoped_lock lock(_property_update_mutex); + return _properties["ActiveInstances"].get_byte(); +} + +uint8_t LEAdvertisingManager1::SupportedInstances(bool refresh) { + if (refresh) { + property_refresh("SupportedInstances"); + } + + std::scoped_lock lock(_property_update_mutex); + return _properties["SupportedInstances"].get_byte(); +} + +std::vector LEAdvertisingManager1::SupportedIncludes(bool refresh) { + if (refresh) { + property_refresh("SupportedIncludes"); + } + + std::scoped_lock lock(_property_update_mutex); + std::vector result; + for (auto& item : _properties["SupportedIncludes"].get_array()) { + result.push_back(item.get_string()); + } + + return result; +} + + +void LEAdvertisingManager1::property_changed(std::string option_name) {} \ No newline at end of file diff --git a/simpledbus/CMakeLists.txt b/simpledbus/CMakeLists.txt index 8cf127a4..20a70c5e 100644 --- a/simpledbus/CMakeLists.txt +++ b/simpledbus/CMakeLists.txt @@ -34,7 +34,9 @@ set(SIMPLEDBUS_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include) set(SIMPLEDBUS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/advanced/Interface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/advanced/InterfaceBase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/advanced/Proxy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/advanced/ProxyBase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/base/Connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/base/Exceptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/base/Holder.cpp diff --git a/simpledbus/include/simpledbus/advanced/Interface.h b/simpledbus/include/simpledbus/advanced/Interface.h index da6ebaa9..c97c060c 100644 --- a/simpledbus/include/simpledbus/advanced/Interface.h +++ b/simpledbus/include/simpledbus/advanced/Interface.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -10,10 +11,9 @@ namespace SimpleDBus { -class Interface { +class Interface : public InterfaceBase { public: - Interface(std::shared_ptr conn, const std::string& bus_name, const std::string& path, - const std::string& interface_name); + Interface(std::shared_ptr conn, std::shared_ptr proxy, const std::string& interface_name); virtual ~Interface() = default; @@ -37,16 +37,11 @@ class Interface { void signal_property_changed(Holder changed_properties, Holder invalidated_properties); // ----- MESSAGES ----- - virtual void message_handle(Message& msg); + protected: std::atomic_bool _loaded{true}; - std::string _path; - std::string _bus_name; - std::string _interface_name; - std::shared_ptr _conn; - std::recursive_mutex _property_update_mutex; std::map _property_valid_map; diff --git a/simpledbus/include/simpledbus/advanced/InterfaceBase.h b/simpledbus/include/simpledbus/advanced/InterfaceBase.h new file mode 100644 index 00000000..2cb1b6a6 --- /dev/null +++ b/simpledbus/include/simpledbus/advanced/InterfaceBase.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace SimpleDBus { + +class ProxyBase; + +class InterfaceBase { + public: + InterfaceBase(std::shared_ptr conn, std::shared_ptr proxy, + const std::string& interface_name); + + virtual ~InterfaceBase() = default; + + // ----- METHODS ----- + + // ----- PROPERTIES ----- + + // ----- SIGNALS ----- + + // ----- MESSAGES ----- + virtual void message_handle(Message& msg); + + protected: + std::string _interface_name; + std::weak_ptr _proxy; + std::shared_ptr _conn; +}; + +} // namespace SimpleDBus diff --git a/simpledbus/include/simpledbus/advanced/Proxy.h b/simpledbus/include/simpledbus/advanced/Proxy.h index b0872b03..2237d8e7 100644 --- a/simpledbus/include/simpledbus/advanced/Proxy.h +++ b/simpledbus/include/simpledbus/advanced/Proxy.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -10,43 +11,24 @@ namespace SimpleDBus { -class Proxy { +class Proxy : public ProxyBase { public: Proxy(std::shared_ptr conn, const std::string& bus_name, const std::string& path); virtual ~Proxy(); - bool valid() const; - std::string path() const; - - bool path_exists(const std::string& path); - std::shared_ptr path_get(const std::string& path); - - bool interface_exists(const std::string& name); - std::shared_ptr interface_get(const std::string& name); - - const std::map>& children(); - const std::map>& interfaces(); - - virtual std::shared_ptr interfaces_create(const std::string& name); - virtual std::shared_ptr path_create(const std::string& path); - // ----- INTROSPECTION ----- std::string introspect(); // ----- INTERFACE HANDLING ----- - size_t interfaces_count(); - bool interfaces_loaded(); void interfaces_load(Holder managed_interfaces); - void interfaces_reload(Holder managed_interfaces); void interfaces_unload(Holder removed_interfaces); // ----- CHILD HANDLING ----- void path_add(const std::string& path, Holder managed_interfaces); bool path_remove(const std::string& path, Holder removed_interfaces); - bool path_prune(); - void path_append_child(const std::string& path, std::shared_ptr child); - + // ----- MESSAGE HANDLING ----- + virtual void message_handle(Message& msg); void message_forward(Message& msg); // ----- CALLBACKS ----- @@ -54,41 +36,9 @@ class Proxy { kvn::safe_callback on_child_signal_received; // ----- TEMPLATE METHODS ----- - template - std::vector> children_casted() { - std::vector> result; - std::scoped_lock lock(_child_access_mutex); - for (auto& [path, child] : _children) { - result.push_back(std::dynamic_pointer_cast(child)); - } - return result; - } - - template - std::vector> children_casted_with_prefix(const std::string& prefix) { - std::vector> result; - std::scoped_lock lock(_child_access_mutex); - for (auto& [path, child] : _children) { - const std::string next_child = SimpleDBus::Path::next_child_strip(_path, path); - if (next_child.find(prefix) == 0) { - result.push_back(std::dynamic_pointer_cast(child)); - } - } - return result; - } protected: - bool _valid; - std::string _path; - std::string _bus_name; - - std::shared_ptr _conn; - - std::map> _interfaces; - std::map> _children; - - std::recursive_mutex _interface_access_mutex; - std::recursive_mutex _child_access_mutex; + SimpleDBus::Holder _collect_managed_objects(const std::string& base_path); }; } // namespace SimpleDBus diff --git a/simpledbus/include/simpledbus/advanced/ProxyBase.h b/simpledbus/include/simpledbus/advanced/ProxyBase.h new file mode 100644 index 00000000..30dac56a --- /dev/null +++ b/simpledbus/include/simpledbus/advanced/ProxyBase.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace SimpleDBus { + +class InterfaceBase; + +class ProxyBase : public std::enable_shared_from_this { + public: + ProxyBase(std::shared_ptr conn, const std::string& bus_name, const std::string& path); + virtual ~ProxyBase(); + + bool valid() const; + std::string path() const; + + + const std::map>& children(); + const std::map>& interfaces(); + + virtual std::shared_ptr interfaces_create(const std::string& name); + virtual std::shared_ptr path_create(const std::string& path); + + // ----- INTERFACE HANDLING ----- + std::shared_ptr interface_get(const std::string& name); + bool interface_exists(const std::string& name); + void interface_insert(const std::string& name); + void interface_remove(const std::string& name); + bool interfaces_loaded(); + + // ----- CHILD HANDLING ----- + std::shared_ptr path_get(const std::string& path); + bool path_exists(const std::string& path); + bool path_prune(); + void path_append_child(const std::string& path, std::shared_ptr child); + + // ----- MESSAGE HANDLING ----- + void message_forward(Message& msg); + virtual void message_handle(Message& msg); + + // ----- CALLBACKS ----- + + // ----- TEMPLATE METHODS ----- + template + std::vector> children_casted() { + std::vector> result; + std::scoped_lock lock(_child_access_mutex); + for (auto& [path, child] : _children) { + result.push_back(std::dynamic_pointer_cast(child)); + } + return result; + } + + template + std::vector> children_casted_with_prefix(const std::string& prefix) { + std::vector> result; + std::scoped_lock lock(_child_access_mutex); + for (auto& [path, child] : _children) { + const std::string next_child = SimpleDBus::Path::next_child_strip(_path, path); + if (next_child.find(prefix) == 0) { + result.push_back(std::dynamic_pointer_cast(child)); + } + } + return result; + } + + protected: + bool _valid; + std::string _path; + std::string _bus_name; + + std::shared_ptr _conn; + + std::map> _interfaces; + std::map> _children; + + std::recursive_mutex _interface_access_mutex; + std::recursive_mutex _child_access_mutex; +}; + +} // namespace SimpleDBus diff --git a/simpledbus/include/simpledbus/base/Message.h b/simpledbus/include/simpledbus/base/Message.h index 23b816a2..993face4 100644 --- a/simpledbus/include/simpledbus/base/Message.h +++ b/simpledbus/include/simpledbus/base/Message.h @@ -51,6 +51,7 @@ class Message { Type get_type() const; bool is_signal(std::string interface, std::string signal_name); + bool is_method_call(const std::string& interface, const std::string& method); static Message create_method_call(std::string bus_name, std::string path, std::string interface, std::string method); diff --git a/simpledbus/src/advanced/Interface.cpp b/simpledbus/src/advanced/Interface.cpp index 603cc6a0..497662d8 100644 --- a/simpledbus/src/advanced/Interface.cpp +++ b/simpledbus/src/advanced/Interface.cpp @@ -131,4 +131,3 @@ void Interface::signal_property_changed(Holder changed_properties, Holder invali // ----- MESSAGES ----- -void Interface::message_handle(Message& msg) {} diff --git a/simpledbus/src/advanced/InterfaceBase.cpp b/simpledbus/src/advanced/InterfaceBase.cpp new file mode 100644 index 00000000..e35b90b9 --- /dev/null +++ b/simpledbus/src/advanced/InterfaceBase.cpp @@ -0,0 +1,23 @@ +#include +#include + +using namespace SimpleDBus; + +InterfaceBase::InterfaceBase(std::shared_ptr conn, std::shared_ptr proxy, + const std::string& interface_name) + : _conn(conn), _proxy(proxy), _interface_name(interface_name) {} + +// ----- LIFE CYCLE ----- + +// ----- METHODS ----- + + +// ----- PROPERTIES ----- + + +// ----- SIGNALS ----- + + +// ----- MESSAGES ----- + +void Interface::message_handle(Message& msg) {} diff --git a/simpledbus/src/advanced/Proxy.cpp b/simpledbus/src/advanced/Proxy.cpp index 89e32025..e08377ac 100644 --- a/simpledbus/src/advanced/Proxy.cpp +++ b/simpledbus/src/advanced/Proxy.cpp @@ -7,29 +7,13 @@ using namespace SimpleDBus; Proxy::Proxy(std::shared_ptr conn, const std::string& bus_name, const std::string& path) - : _conn(conn), _bus_name(bus_name), _path(path), _valid(true) {} + : ProxyBase(conn, bus_name, path) {} Proxy::~Proxy() { on_child_created.unload(); on_child_signal_received.unload(); } -std::shared_ptr Proxy::interfaces_create(const std::string& name) { - return std::make_unique(_conn, _bus_name, _path, name); -} - -std::shared_ptr Proxy::path_create(const std::string& path) { - return std::make_shared(_conn, _bus_name, path); -} - -bool Proxy::valid() const { return _valid; } - -std::string Proxy::path() const { return _path; } - -const std::map>& Proxy::children() { return _children; } - -const std::map>& Proxy::interfaces() { return _interfaces; } - // ----- INTROSPECTION ----- std::string Proxy::introspect() { auto query_msg = Message::create_method_call(_bus_name, _path, "org.freedesktop.DBus.Introspectable", "Introspect"); @@ -52,17 +36,6 @@ std::shared_ptr Proxy::interface_get(const std::string& name) { return _interfaces[name]; } -size_t Proxy::interfaces_count() { - size_t count = 0; - std::scoped_lock lock(_interface_access_mutex); - for (auto& [iface_name, interface] : _interfaces) { - if (interface->is_loaded()) { - count++; - } - } - return count; -} - void Proxy::interfaces_load(Holder managed_interfaces) { auto managed_interface = managed_interfaces.get_dict_string(); @@ -70,57 +43,23 @@ void Proxy::interfaces_load(Holder managed_interfaces) { for (auto& [iface_name, options] : managed_interface) { // If the interface has not been loaded, load it if (!interface_exists(iface_name)) { - _interfaces.emplace(std::make_pair(iface_name, interfaces_create(iface_name))); + interface_insert(iface_name); } _interfaces[iface_name]->load(options); } } -void Proxy::interfaces_reload(Holder managed_interfaces) { - std::scoped_lock lock(_interface_access_mutex); - for (auto& [iface_name, interface] : _interfaces) { - interface->unload(); - } - - interfaces_load(managed_interfaces); -} - void Proxy::interfaces_unload(SimpleDBus::Holder removed_interfaces) { std::scoped_lock lock(_interface_access_mutex); for (auto& option : removed_interfaces.get_array()) { std::string iface_name = option.get_string(); - if (interface_exists(iface_name)) { - _interfaces[iface_name]->unload(); - } + interface_remove(iface_name); } } -bool Proxy::interfaces_loaded() { - std::scoped_lock lock(_interface_access_mutex); - for (auto& [iface_name, interface] : _interfaces) { - if (interface->is_loaded()) { - return true; - } - } - return false; -} - // ----- CHILD HANDLING ----- -bool Proxy::path_exists(const std::string& path) { - std::scoped_lock lock(_child_access_mutex); - return _children.find(path) != _children.end(); -} - -std::shared_ptr Proxy::path_get(const std::string& path) { - std::scoped_lock lock(_child_access_mutex); - if (!path_exists(path)) { - throw Exception::PathNotFoundException(_path, path); - } - return _children[path]; -} - void Proxy::path_add(const std::string& path, SimpleDBus::Holder managed_interfaces) { // If the path is not a child of the current path, then we can't add it. if (!Path::is_descendant(_path, path)) { @@ -198,83 +137,72 @@ bool Proxy::path_remove(const std::string& path, SimpleDBus::Holder options) { return false; } -bool Proxy::path_prune() { - // As children will be extensively accessed, we need to lock the child access mutex. - std::scoped_lock lock(_child_access_mutex); - // For each child proxy, check if it can be pruned. - std::vector to_remove; - for (auto& [child_path, child] : _children) { - if (child->path_prune() && _children.at(child_path).use_count() == 1) { - to_remove.push_back(child_path); +#include + + +void Proxy::message_handle(Message& msg) { + // If the message is involves a property change, forward it to the correct interface. + if (msg.is_signal("org.freedesktop.DBus.Properties", "PropertiesChanged")) { + Holder interface_h = msg.extract(); + std::string iface_name = interface_h.get_string(); + msg.extract_next(); + Holder changed_properties = msg.extract(); + msg.extract_next(); + Holder invalidated_properties = msg.extract(); + + // If the interface is not loaded, then ignore the message. + if (!interface_exists(iface_name)) { + return; } - } - for (auto& child_path : to_remove) { - _children.erase(child_path); - } - // For self to be pruned, the following conditions must be met: - // 1. The proxy has no children - // 2. The proxy has no interfaces or all interfaces are disabled. - if (_children.empty() && !interfaces_loaded()) { - return true; - } + interface_get(iface_name)->signal_property_changed(changed_properties, invalidated_properties); - return false; -} + } else if (msg.is_method_call("org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) { + SimpleDBus::Holder result = _collect_managed_objects(_path); -void Proxy::path_append_child(const std::string& path, std::shared_ptr child) { - // If the provided path is not a child of the current path, return silently. - if (!Path::is_child(_path, path)) { - // TODO: Should an exception be thrown here? + std::cout << "Sending managed objects: " << result.represent() << std::endl; + + SimpleDBus::Message reply = SimpleDBus::Message::create_method_return(msg); + reply.append_argument(result, "a{oa{sa{sv}}}"); + _conn->send(reply); + + std::cout << "Sent managed objects" << std::endl; + return; + + } else if (msg.is_method_call("org.freedesktop.DBus.Properties", "GetAll")) { + std::cout << "Received GetAll: " << msg.to_string() << std::endl; return; + + } else if (interface_exists(msg.get_interface())) { + interface_get(msg.get_interface())->message_handle(msg); } - // As children will be extensively accessed, we need to lock the child access mutex. - std::scoped_lock lock(_child_access_mutex); - _children.emplace(std::make_pair(path, child)); + std::cout << "Message not handled by self: " << msg.to_string() << std::endl; + return; } -// ----- MESSAGE HANDLING ----- -void Proxy::message_forward(Message& msg) { - // If the message is for the current proxy, then forward it to the message handler. - if (msg.get_path() == _path) { - // If the message is involves a property change, forward it to the correct interface. - if (msg.is_signal("org.freedesktop.DBus.Properties", "PropertiesChanged")) { - Holder interface_h = msg.extract(); - std::string iface_name = interface_h.get_string(); - msg.extract_next(); - Holder changed_properties = msg.extract(); - msg.extract_next(); - Holder invalidated_properties = msg.extract(); - - // If the interface is not loaded, then ignore the message. - if (!interface_exists(iface_name)) { - return; - } - - interface_get(iface_name)->signal_property_changed(changed_properties, invalidated_properties); - - } else if (interface_exists(msg.get_interface())) { - interface_get(msg.get_interface())->message_handle(msg); - } - return; - } +SimpleDBus::Holder Proxy::_collect_managed_objects(const std::string& base_path) { + SimpleDBus::Holder result = SimpleDBus::Holder::create_dict(); + SimpleDBus::Holder interfaces = SimpleDBus::Holder::create_dict(); - // If the message is for a child proxy or a descendant, forward it to that child proxy. - for (auto& [child_path, child] : _children) { - if (child_path == msg.get_path()) { - child->message_forward(msg); + for (const auto& [interface_name, interface_ptr] : _interfaces) { + SimpleDBus::Holder properties = interface_ptr->property_get_all(); + interfaces.dict_append(SimpleDBus::Holder::Type::STRING, interface_name, std::move(properties)); + } - if (msg.get_type() == Message::Type::SIGNAL) { - on_child_signal_received(child_path); - } + if (!interfaces.get_dict_string().empty()) { + result.dict_append(SimpleDBus::Holder::Type::OBJ_PATH, _path, std::move(interfaces)); + } - return; - } else if (Path::is_descendant(child_path, msg.get_path())) { - child->message_forward(msg); - return; + for (const auto& [child_path, child] : _children) { + SimpleDBus::Holder child_result = child->_collect_managed_objects(base_path); + // Merge child_result into result + for (auto&& [path, child_interfaces] : child_result.get_dict_object_path()) { + result.dict_append(SimpleDBus::Holder::Type::OBJ_PATH, std::move(path), std::move(child_interfaces)); } } -} + + return std::move(result); +} \ No newline at end of file diff --git a/simpledbus/src/advanced/ProxyBase.cpp b/simpledbus/src/advanced/ProxyBase.cpp new file mode 100644 index 00000000..ae251ede --- /dev/null +++ b/simpledbus/src/advanced/ProxyBase.cpp @@ -0,0 +1,140 @@ +#include "simpledbus/advanced/ProxyBase.h" + +#include +#include +#include + +using namespace SimpleDBus; + +ProxyBase::ProxyBase(std::shared_ptr conn, const std::string& bus_name, const std::string& path) + : _conn(conn), _bus_name(bus_name), _path(path), _valid(true) {} + +ProxyBase::~ProxyBase() { +} + +std::shared_ptr ProxyBase::interfaces_create(const std::string& name) { + return std::make_unique(_conn, shared_from_this(), name); +} + +std::shared_ptr ProxyBase::path_create(const std::string& path) { + return std::make_shared(_conn, _bus_name, path); +} + +bool ProxyBase::valid() const { return _valid; } + +std::string ProxyBase::path() const { return _path; } + +const std::map>& ProxyBase::children() { return _children; } + +const std::map>& ProxyBase::interfaces() { return _interfaces; } + +// ----- INTERFACE HANDLING ----- + +bool ProxyBase::interface_exists(const std::string& name) { + std::scoped_lock lock(_interface_access_mutex); + return _interfaces.find(name) != _interfaces.end(); +} + +std::shared_ptr ProxyBase::interface_get(const std::string& name) { + std::scoped_lock lock(_interface_access_mutex); + if (!interface_exists(name)) { + throw Exception::InterfaceNotFoundException(_path, name); + } + return _interfaces[name]; +} + +void ProxyBase::interface_insert(const std::string& name) { + std::scoped_lock lock(_interface_access_mutex); + _interfaces.insert(std::make_pair(name, interfaces_create(name))); +} + +void ProxyBase::interface_remove(const std::string& name) { + std::scoped_lock lock(_interface_access_mutex); + _interfaces.erase(name); +} + +bool ProxyBase::interfaces_loaded() { + std::scoped_lock lock(_interface_access_mutex); + return _interfaces.size() > 0; +} + +// ----- CHILD HANDLING ----- + +bool ProxyBase::path_exists(const std::string& path) { + std::scoped_lock lock(_child_access_mutex); + return _children.find(path) != _children.end(); +} + +std::shared_ptr ProxyBase::path_get(const std::string& path) { + std::scoped_lock lock(_child_access_mutex); + if (!path_exists(path)) { + throw Exception::PathNotFoundException(_path, path); + } + return _children[path]; +} + +bool ProxyBase::path_prune() { + // As children will be extensively accessed, we need to lock the child access mutex. + std::scoped_lock lock(_child_access_mutex); + + // For each child proxy, check if it can be pruned. + std::vector to_remove; + for (auto& [child_path, child] : _children) { + if (child->path_prune() && _children.at(child_path).use_count() == 1) { + to_remove.push_back(child_path); + } + } + for (auto& child_path : to_remove) { + _children.erase(child_path); + } + + // For self to be pruned, the following conditions must be met: + // 1. The proxy has no children + // 2. The proxy has no interfaces or all interfaces are disabled. + if (_children.empty() && !interfaces_loaded()) { + return true; + } + + return false; +} + +void ProxyBase::path_append_child(const std::string& path, std::shared_ptr child) { + // If the provided path is not a child of the current path, return silently. + if (!Path::is_child(_path, path)) { + // TODO: Should an exception be thrown here? + return; + } + + // As children will be extensively accessed, we need to lock the child access mutex. + std::scoped_lock lock(_child_access_mutex); + _children.emplace(std::make_pair(path, child)); +} + +// ----- MESSAGE HANDLING ----- + +void ProxyBase::message_handle(Message& msg) {} + +void ProxyBase::message_forward(Message& msg) { + // If the message is for the current proxy, then forward it to the message handler. + if (msg.get_path() == _path) { + message_handle(msg); + return; + } + + // If the message is for a child proxy or a descendant, forward it to that child proxy. + for (auto& [child_path, child] : _children) { + if (child_path == msg.get_path()) { + child->message_forward(msg); + + if (msg.get_type() == Message::Type::SIGNAL) { + on_child_signal_received(child_path); + } + + return; + } else if (Path::is_descendant(child_path, msg.get_path())) { + child->message_forward(msg); + return; + } + } +} + diff --git a/simpledbus/src/base/Message.cpp b/simpledbus/src/base/Message.cpp index 9383ed8a..42bd2fb7 100644 --- a/simpledbus/src/base/Message.cpp +++ b/simpledbus/src/base/Message.cpp @@ -346,6 +346,10 @@ bool Message::is_signal(std::string interface, std::string signal_name) { return is_valid() && dbus_message_is_signal(_msg, interface.c_str(), signal_name.c_str()); } +bool Message::is_method_call(const std::string& interface, const std::string& method) { + return get_type() == Type::METHOD_CALL && get_interface() == interface && get_member() == method; +} + static const char* type_to_name(int message_type) { switch (message_type) { case DBUS_MESSAGE_TYPE_SIGNAL: